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

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

流程图

ffmpeg-4.2.2:视频编码流程(yuv编码h.264)_第1张图片简单介绍下各个函数的功能:

avcodec_find_encoder_by_name():通过编码器的名字查找编码器
avcodec_alloc_context3():初始化AVCodecContext
avcodec_open2():打开编码器
av_packet_alloc():初始化AVPacket
av_frame_alloc():初始化AVFrame
av_frame_get_buffer():为AVFrame->data等分配内存
av_frame_make_writable():检查AVFrame->data是否可写
avcodec_send_frame():编码视频:将一帧视频元数据发送给编码器
avcodec_receive_packet():编码视频:接收编码完成的AVPacket数据包


代码


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

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

	if (frame != NULL)
		printf("Send %d frame.\n", frame->pts);

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

	while ((ret = avcodec_receive_packet(cdc_ctx, pkt)) >= 0)
	{
		printf("Write %d packet.\n", pkt->pts);
		fwrite(pkt->data, 1, pkt->size, fp_out);
		av_packet_unref(pkt);
	}

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

void encode_video(const char *input_file, const char *output_file, const char *encoder_name)
{
	int ret = 0;
	int i = 0;
	AVCodec *codec = NULL;
	AVCodecContext *cdc_ctx = NULL;
	AVPacket *pkt = NULL;
	AVFrame *frame = NULL;
	FILE *fp_in, *fp_out;

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

	if ((cdc_ctx = avcodec_alloc_context3(codec)) == NULL)
	{
		fprintf(stderr, "avcodec_alloc_context3 failed.\n");
		goto ret1;
	}
	cdc_ctx->bit_rate = 400000;
	cdc_ctx->width = 352;
	cdc_ctx->height = 288;
	cdc_ctx->time_base = (AVRational){1,25};
	cdc_ctx->framerate = (AVRational){25,1};
	cdc_ctx->gop_size = 10;
	cdc_ctx->max_b_frames = 1;
	cdc_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
	if (codec->id == AV_CODEC_ID_H264)
		av_opt_set(cdc_ctx->priv_data, "preset", "slow", 0);

	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;
	}
	frame->format = cdc_ctx->pix_fmt;
	frame->width = cdc_ctx->width;
	frame->height = cdc_ctx->height;

	if ((ret = av_frame_get_buffer(frame, 0)) < 0)
	{
		fprintf(stderr, "av_frame_get_buffer failed.\n");
		goto ret5;
	}

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

	while (feof(fp_in) == 0)
	{
		int y = 0;

		if ((ret = av_frame_make_writable(frame)) < 0)
		{
			fprintf(stderr, "frame is not writable.\n");
			goto ret7;
		}

		/*y*/
		for (y = 0; y < frame->height; y++)
			fread(&frame->data[0][y * frame->linesize[0]], 1, frame->width, fp_in);
		/*u*/
		for (y = 0; y < frame->height / 2; y++)
			fread(&frame->data[1][y * frame->linesize[1]], 1, frame->width / 2, fp_in);
		/*v*/
		for (y = 0; y < frame->height / 2; y++)
			fread(&frame->data[2][y * frame->linesize[2]], 1, frame->width / 2, fp_in);

		frame->pts = i++;

		encode(cdc_ctx, frame, pkt, fp_out);
	}

	/*flush buffer*/
	encode(cdc_ctx, NULL, pkt, fp_out);


	fclose(fp_out);
	fclose(fp_in);
	av_frame_free(&frame);
	av_packet_free(&pkt);
	avcodec_close(cdc_ctx);
	avcodec_free_context(&cdc_ctx);
	return;
ret7:
	fclose(fp_out);
ret6:
	fclose(fp_in);
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 < 4)
	{
		fprintf(stderr, "Uage:  \n");
		exit(0);
	}

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

注:

  1. 查找编码器也可以通过编码器ID使用以下函数查找:
    codec = avcode_find_encoder(AV_CODEC_ID_H264);
  2. 在循环读取yuv文件时,不能直接把一帧的y数据全读出来拷贝给AVFrame->data[0],因为yuv文件中y值一行的大小是分辨率的width,而data[0]中一行是AVFrame->linesize[0],AVFrame->linesize[0]略大于width,所以只能一行一行读取拷贝,u、v同理
  3. avcodec_send_frame()的第二个参数可以传入NULL,代表的是刷新包,表示流的结束
  4. flush buffer冲洗缓冲区其实就是在执行一次编码过程,但是avcodec_send_frame()第二个参数传入的是NULL,然后在循环调用avcodec_receive_packet接口时,如果编码器有缓冲AVPacket数据,就会返回它们

下载

项目主页

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

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