ffmpeg解码H264裸流为YUV数据

视频画面的传输中,由于原始数据过大,实际传输的数据是已经编码好的数据,一般是H264, 当客户端收到后就需要解码并显示出来。

裸流解析成AVPacket

AVCodecParser

AVCodecParser用于解析输入的数据流并把它分成一帧一帧的压缩编码数据。就像是你把肉塞进火腿,再交给它负责帮你切片,够一个完整的帧就返回给你处理。

使用

int parser_len = 0;

while(parser_len < buffer.length()){
    parser_len += av_parser_parse2(m_pVideoParserContext, m_pVideoDecoder->GetCodecContext(),
                                   &pParsePacket->data, &pParsePacket->size,
                                   (uint8_t *)(buffer.data() + parser_len), buffer.length() - parser_len,
                                   AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);

    if(0 == pParsePacket->size){
        continue;
    }

    switch(m_pVideoParserContext->pict_type){
        case AV_PICTURE_TYPE_I: qDebug("Type:I\t");break;
        case AV_PICTURE_TYPE_P: qDebug("Type:P\t");break;
        case AV_PICTURE_TYPE_B: qDebug("Type:B\t");break;
    default: qDebug("Type:Other\t");break;
}
    //pParsePacket就是一帧的数据包AVPacket,这里可以保存作为录像
    NotifyReceiveVideoPacket(pParsePacket);

    //丢给解码器处理
    m_pVideoDecoder->Decode(pParsePacket);

    av_packet_unref(pParsePacket);
} // parse H264 packet while

每次塞给AVCodecParser的数据是buffer中的数据,其中可能包含多个帧,所以内部会有一个while循环,parser_len是已经解析了的数据的长度,直到解析完一次的buffer,再塞新的数据到buffer继续处理。

解码为AVFrame

if(0 != avcodec_send_packet(pCodecContext, pPacket)){
    qDebug("avcodec_send_packet failed");
    return false;
}

while(0 == avcodec_receive_frame(pCodecContext, pFrame)){
    //pFrame包含了解码后的YUV数据
    ...
}

由于AVFrame是ffmpeg的数据结构,要分别提取出Y、U、V三个通道的数据才能用于显示。

YUV数据格式

RGB来表示颜色大家都不陌生,R(红色)、G(绿色)、B(蓝色),通过这三基色就可以组合成其他需要的颜色。YUV也是一种表示颜色的方式,其中Y(亮度)、U(色度)、V(浓度)。YUV根据采样方式的不同又有YUV444、YUV422、YUV420等多种格式。这里主要介绍YUV420采样格式。

YUV格式

YUV420

YUV420格式是指,每个像素都保留一个Y分量 (亮度全抽样),而在水平方向上,不是每行都取U和V分量,而是一行只取U分量,接着一行就只取V分量,以此重复(即4:2:0, 4:0:2, 4:2:0, 4:0:2 .......)。所以420不是指没有V,而是指一行采样只取U,另一行采样只取V。从4x4矩阵列来看,每4个矩阵点Y区域中,只有一个U和V,所以它们的比值是4:1。

数据大小

对于1个像素的信息存储

  1. RGB格式:R、G、B各占8位,共24位,即3byte
  2. YUV420格式: Y占8位,U、V每4个点共有一个,共8 + 8 / 4 + 8 / 4 = 12位,即3 / 2byte

对于一张图像的数据大小

  1. RGB格式:width * height * 3byte
  2. YUV420格式:width * height * 3 / 2 byte

所以采取YUV420来存储图像数据比RGB格式节省了一半的空间。

YUV420P

采样好了数据,在存储YUV数据的时候,对于YUV不同的存储方式又有YUV420P(YV12)、YUV420SP(NV12)等分类。

yv12
nv12

从图上可以看出,YUV420P和NV12的区别就是一个是UV交替存储,一个是先存U再存V。这个在解码的时候就看你解码格式指定的是哪个了,默认解码是YUV420P。

从AVFrame中获取YUV数据

YUV420P在AVFrame中的存储,data[0]存Y分量,data[1]存U分量,data[2]存V分量。其中,图像每一行Y、U、V数据的大小分别是linesize[0]、linesize[1]、linesize[2],除了实际的图像数据,还有一些填充数据是不需要的。填充数据应该是为了内存对齐,保留的话可能会导致花屏,具体可以看这里的分析

所以,获取实际的图像YUV数据代码为:

for(int i = 0; i < pFrame->height; i++)
  memcpy(m_pBuffer + pFrame->width * i, pFrame->data[0]  + pFrame->linesize[0] * i, pFrame->width);

for(int j = 0; j < pFrame->height / 2; j++)
  memcpy(m_pBuffer + pFrame->width / 2 * j + ysize, pFrame->data[1]  + pFrame->linesize[1] * j, pFrame->width / 2);

for(int k = 0; k < pFrame->height / 2; k++)
  memcpy(m_pBuffer + pFrame->width / 2 * k + ysize * 5 / 4, pFrame->data[2]  + pFrame->linesize[2] * k, pFrame->width / 2);

我是直接用一个pBuffer来保存YUV数据了,记住偏移量就行,想分开存也没问题。这里的数据就可以直接交给渲染部分来显示图像了。

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前一阵子在梳理以前文章的时候,发现自己虽然总结了各种视音频应用程序,却还缺少一个适合无视音频背景人员学习的“最基础...
    视频音频小白阅读 2,073评论 1 3
  • 转自:http://www.cnblogs.com/azraelly/archive/2013/01/01/284...
    rickytang0阅读 905评论 0 1
  • FFmpeg的视频编解码流程: 第一步:组册组件 av_register_all() 例如:编码器、解码器等等… ...
    iOS小肖阅读 7,710评论 0 2
  • 放假后基本每一天都在熬夜,本来还算规律的作息被搅的乱七八糟。不得不说夜半简直太适合多愁善感。通俗来讲,每到这个时间...
    静文谢爱我阅读 424评论 0 0
  • 炎热的盛夏,兰开始了艰苦的军训生活。每天衣服被汗水浸透,回到宿舍洗漱过后,就开始煲电话,一个是妈妈,还有就是笑。妈...
    阿信说阅读 290评论 0 1