FFmpeg中的解码模块和编码模块的流程其实差不多,只是比编码模块多了一个从二进制流中解析出Packet包的接口而已,做了封装的非常完善的解码模块,它的API也是非常的简洁完美。
最近尝试使用MarkDown写博客,发现还是蛮好用的,推荐大家写文档的时候可以参考一下,视频解码的API如下所示,其实实际上真正进行转换的API就那么两个:
视频解码
- 将未解码的Packet数据送给ffmpeg的解码器
- 从ffmpeg解码器中拿到已经解码完成的H264等数据
static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
const char *filename)
{
ret = avcodec_send_packet(dec_ctx, pkt);//将存储未解码视频数据的pkt包送到解码器上下文
ret = avcodec_receive_frame(dec_ctx, frame);//从解码器上下文中读取已经解码好的视频流
fflush(stdout);
snprintf(buf, sizeof(buf), "%s-%d", filename, dec_ctx->frame_number);
pgm_save(frame->data[0], frame->linesize[0],frame->width, frame->height, buf);///将解码好的视频流存储到文件中。
}
解码流程
下面代码的作用就是做好初始化工作,把视频流数据从文件中读取出来,然后送到上面的decode代码中去解码,这也体现了一种封装的思想,通过C语言编写面向对象的设计方式,有助于程序的健壮性。
pkt = av_packet_alloc(); // 申请packet结构,用于存储解码前的数据
codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO); //根据id 找到解码器
parser = av_parser_init(codec->id); //创建码流解析器,作用:从一串二进制码流中解析出pak码流包
c = avcodec_alloc_context3(codec);//根据解码器创建解码器上下文
avcodec_open2(c, codec, NULL)//初始化解码器上下文,并给AVCodecContext上下文数据成员分配存储空间
f = fopen(filename, "rb");//打开输入文件
frame = av_frame_alloc();//申请frame结构体,用于存储解码后的数据
data_size = fread(inbuf, 1, INBUF_SIZE, f);//从输入文件中读取二进制码流
ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);//从二进制码流中解析出packet相关的数据,并存放到pkt结构体中
decode(c, frame, pkt, outfilename);//解码
/* flush the decoder */
decode(c, frame, NULL, outfilename);//刷新解码缓冲区
fclose(f);//关闭输入文件
av_parser_close(parser);//关闭码流解析器
avcodec_free_context(&c);//释放解码器上下文和解码器
av_frame_free(&frame);//释放frame结构对象
av_packet_free(&pkt);//释放packet结构对象
总结
上面就是一个ffmpeg中简单的解码模块流程,我在这边再做一个总结:
- 从输入文件中读取未解码的二进制码流到buf缓冲区
- 通过码流解析器从二进制码流中解析出符合Packet结构的未解码视频数据
- 将未解码视频数据送到解码器去解码
- 从解码器中读取已经解码好的视频数据
- 将视频数据写入到输出文件中
这是一个非常普适且简单的流程,放到任何一个解码器中,都应该是差不多的。当然,流程是一样的,但是内部的算法称得上是千差万别,就像是吃饭一样,有些人每天粗茶淡饭,有些人每天食不果腹,吃的能一样吗?