FFmpeg如何使用解码器
首先介绍一下与解码相关的结构体:
1,AVCodecContext,这个结构体可以是 编码器 的上下文,也可以是 解码器 的上下文,两者使用的是同一种数据结构。
2,AVCodec,编解码信息。
3,AVCodecParameters,编解码参数。
4,AVPacket ,数据包(已编码压缩),这里面的数据通常是一帧视频的数据,或者一帧音频的数据。AVPacket 他本身是没有编码数据的,他只是管理编码数据。
5,AVFrame ,解码之后的 YUV 数据。AVFrame 跟 AVPacket 类似,都是一个管理数据的结构体,他们本身是没有数据的,只是引用了数据。
然后介绍与解码相关的函数有哪些:
1,avcodec_alloc_context3,通过传递 AVCodec 编解码信息来初始化上下文。
2,avcodec_parameters_to_context,把流的 AVCodecParameters 里面的 编解码参数 复制到 AVCodecContext 。
3,avcodec_open2,打开一个编码器 或者 解码器。
4,avcodec_is_open,判断 一个编码器 或者 解码器 是否打开
4,avcodec_send_packet,往 AVCodecContext 解码器 发送一个 AVPacket 。
5,avcodec_receive_frame,从 AVCodecContext 解码器 读取一个 AVFrame。
AVCodec 跟 AVCodecParameters 这两个结构体比较重要。
1,AVCodec 里面放的是 编解码信息 。
2,AVCodecParameters 里面放的是 编解码参数。
怎么理解 编解码信息 跟 编解码参数?
通常 AVCodec 是使用 avcodec_find_decoder 函数找出来的,你给这个函数一个 AVCodecID,他就能返回一个解码器指针给你。这是 引入 FFmpeg 库的时候,他初始化了一堆静态的编解码变量给你。
例如 AVCodecID 是 AV_CODEC_ID_H263 ,就会返回 263 相关的 AVCodec 指针, AVCodecID 是 AV_CODEC_ID_H264 ,就会返回 264 相关的 AVCodec 指针。
只要是用 H264 编码 的视频,使用的解码器信息都是一样的,用的是同一个 AVCodec,但是不同的视频文件,宽高,采样这些信息,肯定会有点不一样。
这些不一样的东西放在哪里呢?就是 AVCodecParameters。
当 avformat_open_input 函数打开一个 MP4 的时候,编码器参数就会放在 codecpar 字段里,如下:
fmt_ctx->streams[0]->codecpar
上面的 codecpar 就是一个 AVCodecParameters,只需要用 avcodec_parameters_to_context 函数把 codecpar 的参数复制给 AVCodecContext 即可。
概括视频解码流程如下:
往一个解码器发一个 AVPacket ,不一定立马就能拿到 一个 AVFrame,因为视频可能有 B 帧,不了解 B 帧的请自行 Google。
所以上面的代码逻辑是,发一个 AVPacket,就 死循环不断的读解码器,直到 返回 EAGAIN,循环是因为有可能有多个 AVFrame 需要读取。
如果返回 EAGAIN ,那就代表解码器需要输入更多的 AVPacket ,才能解码出 AVFrame。
如果已经读到 文件末尾,没有 AVPacket 能从容器里面读出来了,怎么办?
这时候就需要 往 解码器发一个 size 跟 data 都是 0 的 AVPacket ,这样解码器就会把它内部剩余的帧,全部都刷出来。
当解码器完全没有帧可以输出的时候,就会返回 AVERROR_EOF。
所以,avcodec_receive_frame() 主要有两个返回值,如下:
1,EAGAIN,代表解码器需要输入更多的 AVPacket ,才能解码出 AVFrame。
2,AVERROR_EOF,代表解码器结束了,只有你给他发了 NULL 的 AVPacket 才会返回 AVERROR_EOF,通常只有在读取完文件内容,才会发送 NULL 的 AVPacket 给解码器。
音频解码器也是一样的流程。