• 音视频开发中如何使用ffmpeg 一帧H264解码YUV420P?


    作为在音视频行业持续发力多年的视频服务厂商,TSINGSEE青犀视频研发了开源平台EasyDarwin,还有多款音视频流媒体平台,我们开发流媒体平台基本都要使用ffmpeg,在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初始化:
    代码如下:

    typedef struct __DECODER_OBJ
    {
    	AVCodec	 *pVideoCodec;
    	AVCodecContext *pVideoCodecCtx;
    	AVFrame *mVideoFrame420;            ///< 视频帧
    	AVPicture pYuvFrame;
    	struct SwsContext *pSws_ctx;
    	uint8_t	 *pBuffYuv420;
    
    	int		codec;
    	int		width;
    	int		height;
    	int		outputFormat;
    	int		frameType;
    	int		numBytes;
    
    	bool	isInit;
    }DECODER_OBJ;
    
    avfilter_register_all();
    avcodec_register_all();/*注册所有的编码解码器*/
    av_register_all();// //注册所有可解码类型
    
    
    decoderObj.pVideoCodec = avcodec_find_decoder(avCodecId);//H264
    	if (NULL == decoderObj.pVideoCodec) {
    		ReleaseVideoDecoder();
    		return -3;
    	}
    
    	decoderObj.pVideoCodecCtx = avcodec_alloc_context3(decoderObj.pVideoCodec);
    	if (NULL == decoderObj.pVideoCodecCtx) {
    		ReleaseVideoDecoder();
    		return -4;
    	}
    
    	AVDictionary *opts = NULL;
    	int ret = avcodec_open2(decoderObj.pVideoCodecCtx, decoderObj.pVideoCodec, &opts);
    	if (ret < 0) {
    		ReleaseVideoDecoder();
    		return -5;
    	}
    	decoderObj.mVideoFrame420 = av_frame_alloc();
    	if (NULL == decoderObj.mVideoFrame420) {
    		ReleaseVideoDecoder();
    		return -6;
    	}
    	avpicture_alloc(&decoderObj.pYuvFrame, AV_PIX_FMT_YUV420P, width, height);
    
    	decoderObj.numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 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);
    

    拿到的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
    

    在TSINGSEE青犀视频开发的流媒体平台中,EasyNVR、EasyDSS都已经是成熟稳定的视频流媒体平台,可以直接下载测试,EasyRTC的重制版还正在开发当中,其架构有了新的方向,在不久之后新的版本也会上线和大家见面,TSINGSEE青犀视频云边端架构全平台都欢迎大家测试和了解。

  • 相关阅读:
    Queue
    List
    面试1
    野指针和空指针
    指针的定义和使用
    多文件编程
    函数声明
    函数样式
    字符串比较
    函数的定义和使用
  • 原文地址:https://www.cnblogs.com/TSINGSEE/p/15099871.html
Copyright © 2020-2023  润新知