FFmpeg 视频编码

一、什么是视频编码?

视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。视频编码是视音频技术中最重要的技术之一。视频码流的数据量占了视音频总数据量的绝大部分。高效率的视频编码在同等的码率下,可以获得更高的视频质量。

二、FFmpeg开发中的视频编码流程(rgb–>mp4)

这图绝了

Android常见的视频编码有哪些种类_ffmpeg


1.创建解码器

//1、创建编码器
AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
AVCodecContext *c = avcodec_alloc_context3(codec);
//打开编码器、关联编码器上下文
int ret = avcodec_open2(c, codec, NULL);

在这一步中我们需要给解码器上下文数据赋值:
c->bit_rate = 4000000; //压缩比特率
c->width = width;
c->height = height;
c->time_base = { 1, fps };
c->framerate = { fps,1 };
c->gop_size = 50; //每50帧有一个关键帧,画面组大小
c->max_b_frames = 0; //b帧数量
c->pix_fmt = AV_PIX_FMT_YUV420P;
c->codec_id = AV_CODEC_ID_H264; //编码器id
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //全局的编码信息
c->thread_count = 8; //线程数

2.创建输出上下文空间

//2、输出上下文
AVFormatContext *oc = NULL;
//为输出格式开辟上下文空间
avformat_alloc_output_context2(&oc, 0, 0, outfile);
  • 开辟的AVFormatContext空间需要在7中释放!

3.向输出上下文空间空间中添加视频流信息

//3、添加视频流信息
AVStream *st = avformat_new_stream(oc, NULL);
//将解码器上下文中的流信息拷贝入AVStream
avcodec_parameters_from_context(st->codecpar, c);
av_dump_format(oc, 0, outfile, 1);  //打印

我们需要传入的流信息:
st->id = 0;
st->codecpar->codec_tag = 0;

4.创建将rgb数据转换为yuv数据的空间

//4、rgb -》yuv 装换
SwsContext* ctx = NULL;
ctx = sws_getCachedContext(ctx,        //更新
	width, height, AV_PIX_FMT_BGRA,    //输入
	width, height, AV_PIX_FMT_YUV420P, //输出
	SWS_BICUBIC, //转换算法
	NULL, NULL, NULL
	);  //多次调用时如果相同则不会重复创建
//输入的空间
unsigned char* rgb = new unsigned char[width*height * 4];
//输出的空间
AVFrame* yuv = av_frame_alloc();
ret = av_frame_get_buffer(yuv, 32);
  • 视频图像重采样空间ctx、yuv和rgb的储存空间均需要释放!

5.将头信息写入输出文件(.mp4)

//5、write MP4 head输入头信息
ret = avio_open(&oc->pb, outfile, AVIO_FLAG_WRITE);
ret = avformat_write_header(oc, NULL);

5.逐帧将rgb数据转换为yuv数据,并进行编码

for(;;)
{
	int len = fread(rgb, 1, width*height * 4, fp);
	if (len <= 0) break;
	uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
	indata[0] = rgb;
	int inlinesize[AV_NUM_DATA_POINTERS] = { 0 };
	inlinesize[0] = width * 4; //一行字节数大小
	int h = sws_scale(ctx, indata, inlinesize, 0, height,
	yuv->data, yuv->linesize
	); //返回输出height

上述部分可能略微晦涩,请参见FFmpeg视频图像重采样(rgb转yuv).

//将yuv数据发送进编码器上下文进行编码
	ret = avcodec_send_frame(c, yuv);
	AVPacket pkt;
	av_init_packet(&pkt);
	//将编码器上下文中已成功编码的数据发送至AVPacket
	ret = avcodec_receive_packet(c, &pkt);
	av_write_frame(oc, &pkt);
	//减引用计数
	av_packet_unref(&pkt);
	//av_interleaved_write_frame(oc, &pkt); //按dts排序
}
  • 开辟的AVPacket空间需要在7中释放!

6.写入视频索引

//写入视频索引
av_write_trailer(oc);

7.删除或关闭上述操作中开辟的空间

//关闭视频输出io
avio_close(oc->pb);
//清理封装输出上下文
avformat_free_context(oc);
//关闭编码器
avcodec_close(c);
//清理编码器上下文
avcodec_free_context(&c);
//清理视频重采样上下文
sws_freeContext(ctx);
//释放rgb空间
delete[] rgb;