在FFmpeg中,H264在编码前必须要转换成YUV420P,本文就分享一下怎么将h264转成YUV420P。

以下就是yuv420:

八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3][Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]
码流为:Y0 U0 Y1 Y2 U2 Y3 Y5 V5 Y6 Y7 V7 Y8
映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7][Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7]

注意:码流12字节个代表8个像素

理解需要画矩阵,如下:

码流数据:(4:2:0 ~ 4:0:2)

Y0 U0
Y1
Y2 U2
Y3
 
Y5     V5
Y6
Y7	   V7
Y8

映射像素:

Y0 U0 V5
Y1 U0 V5
Y2 U2 V7
Y3 U2 V7
 
Y5 U0 V5
Y6 U0 V5
Y7 U2 V7
Y8 U2 V7

YUV 4:2:0采样,每四个Y共用一组UV分量。

所以要把H264解码YUV420。首先需要把ffmpeg初始化:

代码如下:

//下面初始化h264解码库  
avcodec_init();  
av_register_all();  
  
AVFrame *pFrame_ = NULL;  
  
AVCodecContext *codec_ = avcodec_alloc_context();  
  
/* find the video encoder */  
AVCodec *videoCodec = avcodec_find_decoder(CODEC_ID_H264);  
  
if (!videoCodec)   
{  
    cout << "codec not found!" << endl;  
    return -1;  
}  
  
//初始化参数,下面的参数应该由具体的业务决定  
codec_->time_base.num = 1;  
codec_->frame_number = 1; //每包一个视频帧  
codec_->codec_type = AVMEDIA_TYPE_VIDEO;  
codec_->bit_rate = 0;  
codec_->time_base.den = 30;//帧率  
codec_->width = 1280;//视频宽  
codec_->height = 720;//视频高  
  
if(avcodec_open(codec_, videoCodec) >= 0)  
    pFrame_ = avcodec_alloc_frame();// Allocate video frame  
else  
    return -1;  

初始化完成,然后就需要把h264帧传进去进行解码出YUV420:
代码如下:

AVPacket pAvPacket = { 0 };
	decoderObj.mVideoFrame420->pict_type = picType;
	pAvPacket.data = buf;
	pAvPacket.size = size;
 
	int res = 0;
	int gotPic = 0;
	res = avcodec_decode_video2(decoderObj.pVideoCodecCtx, decoderObj.mVideoFrame420, &gotPic, &pAvPacket);
	if (!gotPic) return -9;
 
	decoderObj.pSws_ctx = sws_getContext(decoderObj.pVideoCodecCtx->width, decoderObj.pVideoCodecCtx->height,
		decoderObj.pVideoCodecCtx->pix_fmt, decoderObj.pVideoCodecCtx->width, decoderObj.pVideoCodecCtx->height,
		AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL);
	sws_scale(decoderObj.pSws_ctx, decoderObj.mVideoFrame420->data, decoderObj.mVideoFrame420->linesize, 0,
		decoderObj.mVideoFrame420->height, decoderObj.pYuvFrame.data, decoderObj.pYuvFrame.linesize);

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

拿到的decoderObj.pYuvFrame.data[0]就是YUV420数据。
最后也不要忘记释放内存。
代码如下:

if (NULL != decoderObj.mVideoFrame420)
	{
		av_frame_free(&decoderObj.mVideoFrame420);
		decoderObj.mVideoFrame420 = NULL;
	}
	if (NULL != decoderObj.pVideoCodecCtx)
	{
		avcodec_close(decoderObj.pVideoCodecCtx);
		if (NULL != decoderObj.pVideoCodecCtx->priv_data)	free(decoderObj.pVideoCodecCtx->priv_data);
		if (NULL != decoderObj.pVideoCodecCtx->extradata)	free(decoderObj.pVideoCodecCtx->extradata);
		avcodec_free_context(&decoderObj.pVideoCodecCtx);
		decoderObj.pVideoCodecCtx = NULL;
	}
	if (NULL != &decoderObj.pYuvFrame)
	{
		avpicture_free(&decoderObj.pYuvFrame);
		//decoderObj.pYuvFrame = NULL;
	}
	if (NULL != decoderObj.pSws_ctx)
	{
		sws_freeContext(decoderObj.pSws_ctx);
		decoderObj.pSws_ctx = NULL;
	}
	if (NULL != decoderObj.pVideoCodec)
	{
		decoderObj.pVideoCodec = NULL;
	}
	if (NULL != decoderObj.pBuffYuv420)
	{
		av_free(decoderObj.pBuffYuv420);
		decoderObj.pBuffYuv420 = NULL;
	}
	if (decoderObj.pSws_ctx) {
		sws_freeContext(decoderObj.pSws_ctx);
		decoderObj.pSws_ctx = NULL;
	}

 最终效果:使用ffplay指令播放yuv一帧数据

ffplay -i -video_size 700*700 $FILE
PS:avcodec_decode_video2这个函数会修改codec_里面的参数的,也就是说如果原来里面填的分别率是1280X720,运行avcodec_decode_video2后codec_里面会变成实际视频的分辨率。
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐