FFmpeg 视频编码
FFmpeg 视频编码
一、什么是视频编码?
视频编码的主要作用是将视频像素数据(RGB,YUV等)压缩成为视频码流,从而降低视频的数据量。如果视频不经过压缩编码的话,体积通常是非常大的,一部电影可能就要上百G的空间。视频编码是视音频技术中最重要的技术之一。视频码流的数据量占了视音频总数据量的绝大部分。高效率的视频编码在同等的码率下,可以获得更高的视频质量。
二、FFmpeg开发中的视频编码流程(rgb–>mp4)
这图绝了
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;
注:理解FFmpeg中的结构体和函数并不容易,可以参见博主其他文章。
链接: FFmpeg 音视频解封装.
链接: FFmpeg 音视频解码.
链接: FFmpeg 内存模型.
链接: FFmpeg 结构体关系分析.
下一篇: 视频的熵编码