ffmpeg-4.2.2:视频解码流程(h.264解码成yuv)

基于FFMPEG的视频解码器,可以将h264压缩编码数据解码成yuv视频元数据。
主要是记录一下自己学习FFMPEG时总结的视频解码流程。
ffmpeg版本:ffmpeg-4.2.2
libx264版本:x264-snapshot-20191023-2245-stable

流程图ffmpeg-4.2.2:视频解码流程(h.264解码成yuv)_第1张图片

简单介绍下各个函数的功能:
avcodec_find_decoder():通过解码器ID查找解码器
avcodec_alloc_context3():初始化AVCodecContext
avcodec_open2():打开解码器
av_packet_alloc():初始化AVPacket
av_frame_alloc():初始化AVFrame
avformat_open_input():打开输入的视频流
avformat_find_stream_info():查找流的信息,填充AVFormatContext
av_read_frame():从流中读取一个AVPacket包数据
avcodec_send_packet():视频解码:发送一个AVPacker数据包给解码器
avcodec_receive_frame():视频解码:接收解码完成的一帧视频数据

代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static void decode(AVCodecContext *cdc_ctx, AVFrame *frame, AVPacket *pkt, FILE *fp_out)
{
	int ret = 0;
	int y;

	if ((ret = avcodec_send_packet(cdc_ctx, pkt)) < 0)
	{
		fprintf(stderr, "avcodec_send_packet failed.\n");
		exit(1);
	}

	while ((ret = avcodec_receive_frame(cdc_ctx, frame)) >= 0)
	{
		printf("Write 1 frame.\n");

		for (y = 0; y < frame->height; y++)
			fwrite(&frame->data[0][y * frame->linesize[0]], 1, frame->width, fp_out);
		for (y = 0; y < frame->height / 2; y++)
			fwrite(&frame->data[1][y * frame->linesize[1]], 1, frame->width / 2, fp_out);
		for (y = 0; y < frame->height / 2; y++)
			fwrite(&frame->data[2][y * frame->linesize[2]], 1, frame->width / 2, fp_out);
	}

	if ((ret != AVERROR(EAGAIN)) && (ret != AVERROR_EOF))
	{
		fprintf(stderr, "avcodec_receive_packet failed.\n");
		exit(1);
	}
}

void decode_audio(const char *input_file, const char *output_file)
{
	int ret = 0;
	AVCodec *codec = NULL;
	AVCodecContext *cdc_ctx = NULL;
	AVPacket *pkt = NULL;
	AVFrame *frame = NULL;
	FILE *fp_in, *fp_out;
	AVFormatContext *fmt_ctx = NULL;
	AVCodecParserContext *parser = NULL;

	if ((codec = avcodec_find_decoder(AV_CODEC_ID_H264)) == NULL)
	{
		fprintf(stderr, "avcodec_find_decoder failed.\n");
		goto ret1;
	}

	if ((cdc_ctx = avcodec_alloc_context3(codec)) == NULL)
	{
		fprintf(stderr, "avcodec_alloc_context3 failed.\n");
		goto ret1;
	}

	if ((ret = avcodec_open2(cdc_ctx, codec, NULL)) < 0)
	{
		fprintf(stderr, "avcodec_open2 failed.\n");
		goto ret2;
	}

	if ((pkt = av_packet_alloc()) == NULL)
	{
		fprintf(stderr, "av_packet_alloc failed.\n");
		goto ret3;
	}

	if ((frame = av_frame_alloc()) == NULL)
	{
		fprintf(stderr, "av_frame_alloc failed.\n");
		goto ret4;
	}

	if ((fp_out = fopen(output_file, "wb")) == NULL)
	{
		fprintf(stderr, "fopen %s failed.\n", output_file);
		goto ret5;
	}

#if 0 	/*从.h264文件中取出一个AVPacket可以这样*/
	if ((fp_in = fopen(input_file, "rb")) == NULL)
	{
		fprintf(stderr, "fopen %s failed.\n", input_file);
		goto ret7;
	}

	if ((parser = av_parser_init(codec->id)) == NULL)
	{
		fprintf(stderr, "av_parser_init failed.\n");
		goto ret8;
	}

	while (feof(fp_in) == 0)
	{
		char inbuf[1024] = {0};
		char *data = inbuf;
		int data_size = 0;

		data_size = fread(inbuf, 1, 1024, fp_in);

		while (data_size > 0)
		{
			/*只能传入视频数据*/
			if ((ret = av_parser_parse2(parser, cdc_ctx, &pkt->data, &pkt->size, 
							data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0)) < 0)

			{
				fprintf(stderr, "av_parser_parse2 failed.\n");
				goto ret8;
			}
			data += ret;
			data_size -= ret;

			if (pkt->size > 0)
				decode(cdc_ctx, frame, pkt, fp_out);
		}
	}

#else 	/*从.h264文件中取出一个AVPacket还可以这样*/
	if ((fmt_ctx = avformat_alloc_context()) == NULL)
	{
		fprintf(stderr, "avformat_alloc_context failed.\n");
		goto ret7;
	}

	if ((ret = avformat_open_input(&fmt_ctx, input_file, NULL, NULL)) < 0)
	{
		fprintf(stderr, "avformat_open_input failed.\n");
		goto ret8;
	}

	if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0)
	{
		fprintf(stderr, "avformat_find_stream_info failed.\n");
		goto ret9;
	}

	while ((ret = av_read_frame(fmt_ctx, pkt)) == 0)
	{
		if (pkt->size > 0)
			decode(cdc_ctx, frame, pkt, fp_out);
	}
#endif

	decode(cdc_ctx, frame, NULL, fp_out);


#if 0
	fclose(fp_in);
#else
	avformat_close_input(&fmt_ctx);
	avformat_free_context(fmt_ctx);
#endif
	fclose(fp_out);
	av_frame_free(&frame);
	av_packet_free(&pkt);
	avcodec_close(cdc_ctx);
	avcodec_free_context(&cdc_ctx);
	return;

#if 0
ret8:
	fclose(fp_in);
#else
ret9:
	avformat_close_input(&fmt_ctx);
ret8:
	avformat_free_context(fmt_ctx);
#endif
ret7:
	fclose(fp_out);
ret5:
	av_frame_free(&frame);
ret4:
	av_packet_free(&pkt);
ret3:
	avcodec_close(cdc_ctx);
ret2:
	avcodec_free_context(&cdc_ctx);
ret1:
	exit(1);
}

int main(int argc, const char *argv[])
{
	if (argc < 3)
	{
		fprintf(stderr, "Uage: \n");
		exit(0);
	}

	decode_audio(argv[1], argv[2]);
	
	return 0;
}

注:

  1. 解码出来的数据在AVFrame中,和视频编码一样,AVframe->data[0]中一行的大小为AVFrame->linesize[0],比width略大,需要一行一行的写
  2. 读取一个AVPacket包,可以使用解析器:av_parser_init(),av_parser_parse2()。但是av_parser_parse2()只能传入视频数据
  3. av_parser_parse2():返回值为使用了的字节数。当我们传入了一定字节的数据,如果不够一个AVPacket,会保存这一次传入的数据,返回所有的字节数,不会回填AVPacket,pkt->size == 0;如果够了一个AVPacket,则返回使用了的字节数,同时会把够数的AVPacket回填,pkt->size == AVPacket包的大小,剩余的数据不会保存,需要再次调用接口传入
  4. avformat_open_input():如果第一个参数为NULL,则会在内部调用avformat_alloc_context(),所以没有必要自己提前调用初始化一个AVFormatContext;但是传入的指针必须要初始化为NULL

下载

项目主页

Github:https://github.com/newbie-plan/decode_video

你可能感兴趣的:(ffmpeg-4.2.2:视频解码流程(h.264解码成yuv))