FFMPEG学习笔记---实现H264与Hevc解码保存YUV文件

学习自雷神博客:https://blog.csdn.net/leixiaohua1020/article/details/42181571

        整篇文章不同于解码一般视频文件在于,视频文件(Mp4/AVI/MKV)一般是在码流(裸流)的基础上又封装了一层,这里解析的文件为裸流文件。重点函数为:av_parser_parse2(),解析数据获得一个Packet, 从输入的数据流中分离出一帧一帧的压缩编码数据。av_read_frame():获取媒体的一帧压缩编码数据。其中调用了av_parser_parse2()。

      另外在视频一般封装视频文件时,avformat_open_input()和avformat_find_stream_info()就可以解析出输入视频的信息(例如视频的宽、高)并且赋值给相关的结构体。因此我们在初始化的时候就可以通过读取相应的字段获取到这些信息。但解码裸流文件时只有在成功解码一帧数据后(avcodec_decode_video2())才能获取相应字段(视频宽高等)。

流程图:

FFMPEG学习笔记---实现H264与Hevc解码保存YUV文件_第1张图片

代码:

#define USE_SWSCALE 0

//test different codec
#define TEST_H264  1
#define TEST_HEVC  0

int _tmain(int argc, _TCHAR* argv[])
{
	AVCodec *pCodec;  //编解码器结构体
    AVCodecContext *pCodecCtx= NULL;//编解码器信息头
	AVCodecParserContext *pCodecParserCtx=NULL;//解析输入的数据流信息头
	AVPacket packet;  //存储一帧压缩编码数据包
	AVFrame	*pFrame;  //存储一帧解码后像素数据
    
	FILE *fp_in;
	FILE *fp_out;
  
	const int in_buffer_size=4096; //一次文件读取长度
	uint8_t in_buffer[in_buffer_size + FF_INPUT_BUFFER_PADDING_SIZE]={0};//FF_INPUT_BUFFER_PADDING_SIZE:在解码的输入比特流的末尾所需的额外分配字节数
	uint8_t *cur_ptr;  //数据指针
	int cur_size;
  
	int ret, got_picture;
	int y_size;


#if TEST_HEVC
	enum AVCodecID codec_id=AV_CODEC_ID_HEVC;
	char filepath_in[]="bigbuckbunny_480x272.hevc";
#elif TEST_H264
	AVCodecID codec_id=AV_CODEC_ID_H264;   //解码器设置为h264
	char filepath_in[]="../bigbuckbunny_480x272.h264";
#else
	AVCodecID codec_id=AV_CODEC_ID_MPEG2VIDEO;
	char filepath_in[]="bigbuckbunny_480x272.m2v";
#endif

	char filepath_out[]="../bigbuckbunny_480x272.yuv";//输出文件
	int first_time=1;

#if USE_SWSCALE
	struct SwsContext *img_convert_ctx;
	AVFrame	*pFrameYUV;
	uint8_t *out_buffer;

#endif
	//av_log_set_level(AV_LOG_DEBUG);
	
	avcodec_register_all();	//注册所有编解码器

    pCodec = avcodec_find_decoder(codec_id);   //查找编解码器
    if (!pCodec) {
        printf("Codec not found\n");
        return -1;
    }
    pCodecCtx = avcodec_alloc_context3(pCodec);//为编解码信息头结构体分配内存
    if (!pCodecCtx){
        printf("Could not allocate video codec context\n");
        return -1;
    }

	pCodecParserCtx=av_parser_init(codec_id);  //初始化AVCodecParserContext。
	if (!pCodecParserCtx){
		printf("Could not allocate video parser context\n");
		return -1;
	}

    //if(pCodec->capabilities&CODEC_CAP_TRUNCATED)
    //    pCodecCtx->flags|= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */
    
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {//打开编解码器
        printf("Could not open codec\n");
        return -1;
    }
	//Input File
    fp_in = fopen(filepath_in, "rb");
    if (!fp_in) {
        printf("Could not open input stream\n");
        return -1;
    }
	//Output File
	fp_out = fopen(filepath_out, "wb");
	if (!fp_out) {
		printf("Could not open output YUV file\n");
		return -1;
	}

    pFrame = av_frame_alloc(); //分配像素存储内存
	av_init_packet(&packet);	//初始化压缩数据包

	while (1) {
		
        cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);//读取文件
        if (cur_size == 0)
            break;
        cur_ptr=in_buffer;

        while (cur_size>0){
							//主要是通过av_parser_parse2拿到AVPaket数据,跟av_read_frame类似。
							//输入必须是只包含视频编码数据“裸流”(例如H.264、HEVC码流文件),而不能是包含封装格式的媒体数据(例如AVI、MKV、MP4)。
			int len = av_parser_parse2(		//获取编码数据包
				pCodecParserCtx, pCodecCtx,
				&packet.data, &packet.size,
				cur_ptr , cur_size ,
				AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
			 
			cur_ptr += len;
			cur_size -= len;

			if(packet.size==0)
				continue;

			//Some Info from AVCodecParserContext
			printf("[Packet]Size:%6d\t",packet.size);
			switch(pCodecParserCtx->pict_type){
				case AV_PICTURE_TYPE_I: printf("Type:I\t");break;
				case AV_PICTURE_TYPE_P: printf("Type:P\t");break;
				case AV_PICTURE_TYPE_B: printf("Type:B\t");break;
				default: printf("Type:Other\t");break;
			}
			printf("Number:%4d\n",pCodecParserCtx->output_picture_number);

			ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet); //解码像素数据
			if (ret < 0) {
				printf("Decode Error.\n");
				return ret;
			}
			if (got_picture) {		  //这里解码出来的像素数据可以用SDL直接刷新显示播放出来
#if USE_SWSCALE
				if(first_time){	 //获取到相应的视频信息 长宽等
					printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
					printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
					//SwsContext
					img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, 
						pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 

					pFrameYUV=av_frame_alloc();
					out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
					avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);

					y_size=pCodecCtx->width*pCodecCtx->height;	  //视频大小

					first_time=0;
				}
				sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, 
					pFrameYUV->data, pFrameYUV->linesize);

				fwrite(pFrameYUV->data[0],1,y_size,fp_out);     //Y 
				fwrite(pFrameYUV->data[1],1,y_size/4,fp_out);   //U
				fwrite(pFrameYUV->data[2],1,y_size/4,fp_out);   //V
#else
				int i=0;							 //写入文件数据 YUV  比例为4:1:1
				unsigned char* tempptr=NULL;
				tempptr=pFrame->data[0];
				for(i=0;iheight;i++){
					fwrite(tempptr,1,pFrame->width,fp_out);     //Y 
					tempptr+=pFrame->linesize[0];
				}
				tempptr=pFrame->data[1];
				for(i=0;iheight/2;i++){
					fwrite(tempptr,1,pFrame->width/2,fp_out);   //U
					tempptr+=pFrame->linesize[1];
				}
				tempptr=pFrame->data[2];
				for(i=0;iheight/2;i++){
					fwrite(tempptr,1,pFrame->width/2,fp_out);   //V
					tempptr+=pFrame->linesize[2];
				}
#endif

				printf("Succeed to decode 1 frame!\n");
			}
		}

    }

	//Flush Decoder	   //清理解码器中的剩余数据
    packet.data = NULL;
    packet.size = 0;
	while(1){
		ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
		if (ret < 0) {
			printf("Decode Error.\n");
			return ret;
		}
		if (!got_picture)
			break;
		if (got_picture) {
			
#if USE_SWSCALE
			sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, 
				pFrameYUV->data, pFrameYUV->linesize);

			fwrite(pFrameYUV->data[0],1,y_size,fp_out);     //Y
			fwrite(pFrameYUV->data[1],1,y_size/4,fp_out);   //U
			fwrite(pFrameYUV->data[2],1,y_size/4,fp_out);   //V
#else
			int i=0;
			unsigned char* tempptr=NULL;
			tempptr=pFrame->data[0];
			for(i=0;iheight;i++){
				fwrite(tempptr,1,pFrame->width,fp_out);     //Y 
				tempptr+=pFrame->linesize[0];
			}
			tempptr=pFrame->data[1];
			for(i=0;iheight/2;i++){
				fwrite(tempptr,1,pFrame->width/2,fp_out);   //U
				tempptr+=pFrame->linesize[1];
			}
			tempptr=pFrame->data[2];
			for(i=0;iheight/2;i++){
				fwrite(tempptr,1,pFrame->width/2,fp_out);   //V
				tempptr+=pFrame->linesize[2];
			}
#endif
			printf("Flush Decoder: Succeed to decode 1 frame!\n");
		}
	}

    fclose(fp_in);
	fclose(fp_out);

#if USE_SWSCALE
	sws_freeContext(img_convert_ctx);
	av_frame_free(&pFrameYUV);
#endif

	av_parser_close(pCodecParserCtx);

	av_frame_free(&pFrame);
	avcodec_close(pCodecCtx);
	av_free(pCodecCtx);
	system("pause");
	return 0;
}

上述代码解析成YUV文件过程中,在获取出图片数据(pFrame.data())即可以直接用SDL将图片显示出来。

你可能感兴趣的:(音视频)