再深入FFMPEG
AVCodecContext
AVCodecContext和AVCodec
AVCodecContext
和AVCodec
是FFmpeg库中的两个重要的结构体,它们在视频和音频编解码过程中起着关键的作用。
AVCodec
是一个编解码器,它定义了一个特定的编解码算法,如H.264、AAC等。AVCodec
结构体主要包含了编解码器的基本信息和函数指针,如编解码器的名称、类型、ID、初始化和解码函数等。用户通常不直接修改AVCodec
的属性,而是通过选择不同的AVCodec
来使用不同的编解码算法。
AVCodecContext
是编解码器的上下文,它包含了编解码过程中需要的所有信息。用户可以通过设置AVCodecContext
的属性来影响编解码的过程。例如,用户可以设置bit_rate
来控制输出的比特率,设置gop_size
来控制I帧的间隔,设置pix_fmt
来控制输出的像素格式等。
总的来说,AVCodec
和AVCodecContext
的主要区别在于,AVCodec
定义了编解码的算法,而AVCodecContext
提供了执行这个算法所需要的上下文环境。用户可以通过设置AVCodecContext
的属性来自定义编解码的过程,而AVCodec
的选择则决定了使用哪种编解码算法。
AVCodec
AVCodec
代表了一个特定的编解码器,它是编解码器的抽象,包含了关于编解码器的静态信息,比如名字、类型、ID等。它不包含编解码过程中的具体参数。AVCodec
结构体主要用于描述编解码器的能力和提供编解码器的实现。
AVCodec
的属性主要包括:
name
:编解码器的名称。long_name
:编解码器的长名称。type
:编解码器的类型(音频、视频、字幕)。id
:编解码器的ID,是一个枚举值,如AV_CODEC_ID_H264
。capabilities
:编解码器的能力标志,比如是否支持硬件加速。
AVCodec
结构体不是由用户直接分配和管理的;它是由FFmpeg内部提供,并通过调用例如 avcodec_find_encoder_by_name()
或 avcodec_find_decoder_by_name()
等函数来获取。
AVCodecContext
AVCodecContext
包含了编解码过程中需要的参数和状态信息。它是编解码过程的上下文,包含了用于编解码的具体设置,比如比特率、帧率、宽高、采样率等。AVCodecContext
在编解码过程中用于存储所有相关的信息。
AVCodecContext
的属性主要包括:
bit_rate
:视频的比特率。width
、height
:视频的宽度和高度。gop_size
:关键帧间的帧数。max_b_frames
:B帧的最大数目。pix_fmt
:像素格式。sample_rate
:音频的采样率。channel_layout
:音频的通道布局。
AVCodecContext
是由用户通过 avcodec_alloc_context3()
分配的,并在编解码过程中填充和使用。在编解码完成后,用户需要调用 avcodec_free_context()
来释放上下文。
max_b_frames
是视频编码的一个参数,它指定了在两个参考帧(I帧或P帧)之间可以插入的B帧的最大数量。B帧是双向预测帧,它们可以从它们之前和之后的帧中获取信息来进行编码。这种类型的帧通常比I帧(关键帧)和P帧(向前预测帧)更加高效,因为它们可以从两个方向进行预测,从而通常能够以更低的比特率实现更好的压缩效率。
在视频编码中,B帧的使用可以显著减少所需的数据量,从而减小视频文件的大小,同时保持相对较高的图像质量。然而,使用B帧也会增加编码和解码的复杂性,因为解码B帧需要访问它前后的帧。这可能会增加编解码器的延迟,并且在快速搜索视频时可能会出现问题,因为解码器可能需要跳转到其他帧以获取解码B帧所需的信息。
max_b_frames
参数的设置取决于多种因素,包括:
- 编码延迟: 如果编码延迟是一个问题,比如在实时通信中,可能会选择较少的B帧甚至不使用B帧。
- 压缩效率: 如果文件大小是一个重要因素,增加B帧的数量可以提高压缩效率。
- 处理能力: 如果编码和解码设备的处理能力有限,可能会选择减少B帧的数量以减轻处理负担。
设置 max_b_frames
的一个合理值可以在压缩效率和编码复杂性之间取得平衡。在某些情况下,比如直播流或者对延迟敏感的应用,可能会将 max_b_frames
设置为0,以禁用B帧并最小化延迟。
实例和代码
假设我们想要编码一个H.264视频流,我们需要创建一个 AVCodecContext
并设置相关的参数:
// 查找H.264编码器
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!codec) {
fprintf(stderr, "Codec not found\n");
exit(1);
}
// 创建编码器上下文
AVCodecContext* codec_context = avcodec_alloc_context3(codec);
if (!codec_context) {
fprintf(stderr, "Could not allocate video codec context\n");
exit(1);
}
// 设置编码器上下文的参数
codec_context->bit_rate = 400000;
codec_context->width = 1920;
codec_context->height = 1080;
codec_context->time_base = (AVRational){1, 25};
codec_context->framerate = (AVRational){25, 1};
codec_context->gop_size = 10;
codec_context->max_b_frames = 1;
codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
// 打开编码器
if (avcodec_open2(codec_context, codec, NULL) < 0) {
fprintf(stderr, "Could not open codec\n");
exit(1);
}
// ... 编码过程 ...
// 释放编码器上下文
avcodec_free_context(&codec_context);
在这个例子中,AVCodec
用于获取H.264编码器的信息,而 AVCodecContext
用于设置编码过程中的具体参数和管理编码状态。通过设置这些参数,我们能够控制编码输出的视频流的质量和特性。在编码完成后,我们需要释放 AVCodecContext
来避免内存泄漏。
gop_size
在FFmpeg中,gop_size
是在AVCodecContext
结构体中定义的一个参数,它代表了Group of Pictures的大小。Group of Pictures(GOP)是视频编码中的一个概念,它是指连续的图片序列,这些图片在编码和解码时可以被视为一个组。
gop_size
的值决定了两个I帧之间的最大间隔,也就是一个GOP的长度。I帧是完整的图像帧,不依赖于其他帧就可以完全解码。在一个GOP中,第一个帧通常是I帧,后续的帧可能是P帧或B帧,这些帧是依赖于其他帧的。
设置gop_size
的值可以影响视频的压缩效率和质量。较大的gop_size
值可以提高视频的压缩效率,因为P帧和B帧通常比I帧更小。但是,如果gop_size
太大,可能会影响视频的质量,因为P帧和B帧的质量通常比I帧低。此外,如果视频中有很多场景切换,较小的gop_size
可能会更好,因为场景切换后通常需要一个新的I帧来提供清晰的图像。
总的来说,gop_size
是一个重要的参数,需要根据具体的应用场景和需求来设置。
写到文件
用到的函数介绍
写文件头 avformat_write_header
avformat_write_header
函数在 FFmpeg 库中用于初始化媒体文件的写入过程。具体来说,它负责写入输出文件的文件头部分,这通常包括格式特定的元数据,如编解码器信息、比特率、帧率等,以及可能的一些全局容器格式信息,如MP4的ftyp和moov atom等。
这个函数通常在设置好编码器和打开输出文件之后调用,但在实际写入媒体帧之前调用。
函数原型如下:
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
参数说明:
s
: 指向AVFormatContext
结构的指针,该结构包含了输出文件的信息。options
: 指向一个AVDictionary
的指针,这可以用来传递额外的选项给muxer(封装器)。例如,你可以通过这个字典来设置一些特定的文件头信息。在函数调用后,muxer会读取这些选项并将它们应用于输出文件。调用完成后,这个字典会被更新以反映哪些选项被使用了。
avformat_write_header
的返回值是一个整数,如果初始化成功,返回值为零或正数;如果失败,则返回一个负数,表示错误代码。错误代码可以用 av_strerror
函数转换为可读的字符串,以便于调试。
例子:
AVFormatContext *oc = NULL;
// ... 初始化oc,添加流,打开输出文件等 ...
// 使用默认选项写入文件头
int ret = avformat_write_header(oc, NULL);
if (ret < 0) {
// 错误处理
}
在调用 avformat_write_header
之后,你通常会进入一个循环,开始编码每一帧并使用 av_interleaved_write_frame
或 av_write_frame
写入它们。在所有帧都写入完成后,你需要调用 av_write_trailer
来结束写入过程,这会写入任何必要的文件尾部信息并清理相关资源。
写文件尾 av_write_trailer
av_write_trailer
函数是 FFmpeg 库中的一个函数,它用于结束写入一个媒体文件。当你使用 FFmpeg 的 libavformat 库创建和写入媒体文件时,这个函数会在写入过程的最后被调用。
在写入媒体文件时,通常会遵循以下步骤:
- 使用
avformat_alloc_output_context2
函数来分配输出上下文。 - 设置必要的参数和格式。
- 打开输出文件(如果需要)。
- 写入文件头信息(使用
avformat_write_header
)。 - 写入媒体数据(音频帧、视频帧等)。
- 写入文件尾信息(使用
av_write_trailer
)。
av_write_trailer
的作用是写入任何必要的尾部信息,以确保媒体文件是完整的,并且可以被播放器正确解析。这可能包括索引信息、元数据、最后一个字节的位置等。在某些容器格式中,这个步骤是必需的,因为它们需要文件的尾部信息来标记文件结束或存储重要的索引信息。
写入视频帧 av_write_frame
int av_write_frame(AVFormatContext *s, AVPacket *pkt);
在写入该帧之前,需要确保传入的AVPacket是有效的
一般步骤是这样的:
- 通过SwsContext转换视频帧格式和宽高
- frame是一个新创建的frame的话,需要设置该frame的宽高和pts,pts需要转换时间基的话就要用到av_rescale_q函数
- 赋值SwsContext转换后的视频帧给frame->data,要注意linesize,u、v分量的宽高是y分量的1/2
- 向编码器传入视频帧,avcodec_send_frame
- 接收到编码好的视频帧avcodec_receive_packet
- 写入视频帧 av_write_frame
转换时间基av_rescale_q
这里是如何使用 av_rescale_q
函数的例子:
// 假设你有一个时间戳 pts_in 和它的时间基准 in_tb
int64_t pts_in; // 输入的时间戳
AVRational in_tb; // 输入的时间基准
// 你想要转换到的目标时间基准 out_tb
AVRational out_tb; // 输出的时间基准
// 使用 av_rescale_q 转换时间戳
int64_t pts_out = av_rescale_q(pts_in, in_tb, out_tb);
在这个例子中,pts_in
是原始时间戳,in_tb
是原始时间戳的时间基准,out_tb
是你想要转换到的时间基准。函数 av_rescale_q
会根据这些参数计算出新的时间戳 pts_out
。
尽管我们在文件解析之前可以设置AVStream的time_base bit_rate,但是在ffmpeg写入视频帧时,还未按时视频帧的格式和文件的输出格式来调整到正确的输出参数。