FFmpeg 框架
核心组件
libavcodec
:一个编解码库,包含了众多的编码器和解码器用于编码和解码音视频流。libavformat
:一个封装格式库,用于处理各种音视频封装格式。libavutil
:一个工具库,提供了常见功能的简化接口,如数学计算、内存管理等。libavfilter
:滤镜库,用于对音视频数据进行过滤处理。libavdevice
:提供了捕捉与输出多媒体设备内容的接口。libswscale
:用于处理图像缩放以及色彩空间转换。libswresample
:用于处理音频采样数据的重采样、格式转换等。
FFmpeg工具集
ffmpeg
:此命令行工具用于快速音视频转码、封装格式转换。ffplay
:一个简单的媒体播放器,基于SDL和FFmpeg库。ffprobe
:用于分析多媒体流信息的命令行工具。
关键功能
- 转码:转换音视频编码格式,如将AVI转换为MP4。
- 解码:根据编码格式将数据解码为原始音视频帧。
- 编码:将原始音视频数据编码为特定格式。
- 封装:将编码后的音视频数据封装到指定的容器格式。
- 流化:处理音视频数据以适合网络流传输。
- 滤镜处理:改变音视频数据的属性,如调整大小,裁剪,去除噪点等。
FFmpeg 内存引用计数模型
FFmpeg中的内存引用计数模型是一个核心的内存管理机制,用于确保音视频数据(如帧、包、缓冲区等)在多个组件之间安全地共享与释放。这个机制基于引用计数,可以防止资源的提早释放或内存泄漏。
AvBufferRef
在FFmpeg中,AvBufferRef
类型的引用在各种数据结构(如AVFrame, AVPacket等)中被用作指向实际数据的指针。使用这种引用计数模型能够在不同的上下文中共享相同的数据,而无需复制数据。
引用计数工作原理
- 初始化: 当数据被分配时,它被包装在“buffer”的数据结构中,并且引用计数设置为1。
- 增加引用: 当另一个组件需要使用相同的数据时,引用计数会增加。
- 减少引用: 当组件完成对数据的使用,它将减少引用计数。这通常通过调用
av_buffer_unref()
或类似的函数完成。 - 释放内存: 当最后一个引用
av_buffer_unref()
被调用时,引用计数归零,相关数据结构与其所占用的内存将被释放。
为了使这个模型有效,所有组件在处理通过引用计数共享的数据时,都必须遵守增加和减少引用的规则,这确保了内存的正确管理。
这种模型在视频解码过程中尤为重要,因为解码视频通常会产生大量的视频帧数据,如果不进行合理管理,很容易造成内存不足的问题。使用AvBufferRef
,多个组件可以同时访问帧数据而不需要进行数据的复制,这既减少了延迟也节约了资源。
解复用相关 AVFormat XXX等
AVFormatContext
AVFormatContext
结构体是FFmpeg库中一个核心的结构体,用于存储媒体文件或媒体流的信息以及对它们进行操作时所需的上下文信息。
AVFormatContext
结构体中关键的成员:
-
av_class
: 一个指向AVClass
的指针,用于日志记录和avoptions
。通过它可以访问类的相关信息和方法。 -
iformat
: 指向一个AVInputFormat
结构体的指针。它描述了用于读取媒体文件的解复用器。 -
oformat
: 指向一个AVOutputFormat
结构体的指针。它描述了用于写入媒体文件的复用器。 -
streams
: 指向AVStream
结构体指针数组的一个指针,存储了文件中所有媒体流的信息。 -
nb_streams
: 流的数量,说明streams
数组含有多少个元素。 -
url
: 一个表示媒体文件名或流的URL的字符串。 -
pb
: 指向AVIOContext
的一个指针,用于输入输出操作的抽象。 -
duration
: 媒体文件的总时长。 -
bit_rate
: 总比特率。 -
packet_size
: 数据包的大小。 -
max_delay
: 最大延迟。 -
flags
: 各种标志,定义了上下文的额外属性。 -
probesize
: 在读取流信息时确定要探测多少字节。 -
max_analyze_duration
: 在读取流信息时设置最大分析时长。 -
metadata
: 指向AVDictionary
的指针,包含了文件的元数据,例如标题、作者、专辑等信息。 -
start_time
: 流的开始时间。 -
pb
: 指向一个AVIOContext
结构体的指针,它表示内部I/O上下文,用于读写文件。
这些成员提供了对媒体文件和流的综合管理,包括读写、寻找、元数据处理和流信息获取等。
AVInputFormat
AVInputFormat
结构体在FFmpeg中定义了一个特定媒体文件输入格式的解复用器(demuxer)接口。它包含了一系列的函数指针和变量,这些成员用于实现解复用的各个阶段功能,如打开文件、读取帧内容、寻找流信息等。
AVInputFormat
结构体中的关键成员:
-
name
: 指向一个字符串的指针,用来表示输入格式的名称,比如 “mp4” 或 “mpegts”。 -
long_name
: 一个较为详细的格式描述,它通常是格式的完整名称。 -
flags
: 标识输入格式的能力和限制,这可能包括对自定义IO支持、对查找操作的支持等。 -
extensions
: 支持的文件扩展名,使用逗号分隔的字符串列表。 -
mime_type
: 与输入格式相关的MIME类型。 -
priv_data_size
: 针对此输入格式的私有数据结构的大小。 -
read_probe
: 函数指针,用于探测输入格式是否支持处理给定的数据。 -
read_header
: 函数指针,用于读取媒体文件的头部信息,设置AVFormatContext
结构的其他成员。 -
read_packet
: 函数指针,用于从媒体中读取一帧数据。 -
read_close
: 函数指针,用于关闭解复用器并清理资源。 -
read_seek
: 函数指针,用于支持在媒体文件中查找。 -
read_timestamp
: 函数指针,用于获取特定位置的时间戳。 -
priv_class
: 指向AVClass
结构体的指针,用于支持私有选项设置。
AVInputFormat
为不同的媒体文件类型提供了一组标准化的解复用API。不同的文件格式实现了不同的AVInputFormat
实例,FFmpeg使用这些实例来实现对多种文件格式的支持。当用户打开一个媒体文件时,FFmpeg将会根据文件扩展名、文件内容等来选择合适的AVInputFormat
结构体实例,以进行数据的解复用处理。
AVStream
AVStream
结构体是FFmpeg中用于存储与特定媒体流(如视频流、音频流)相关的信息。它包含了一系列关键成员,这些成员提供了流的元数据和其他必要的信息,用于解复用和解码等操作。
AVStream
结构体中的关键成员:
-
index
: 流的索引编号,根据它在AVFormatContext
的streams
数组中的位置来确定。 -
id
: 流的ID,用于在文件格式中识别流。 -
codecpar
: 指向AVCodecParameters
结构体的指针,包含了流的编解码器参数,如编解码器类型、视频的宽高、采样率等。 -
time_base
: 流的时间基准,表示时间戳与实际时间的转换关系。这是一个AVRational
类型的比率值,包含分子和分母。 -
start_time
: 流中第一个可解码的帧的演示时间戳(Presentation Timestamp,PTS),以time_base
为单位的。 -
duration
: 该流在时间基准time_base
单位下的总持续时间。 -
nb_frames
: 流中的帧数,如果可知的话。 -
codec
: (在新版FFmpeg中不建议使用,用codecpar
代替)指向与流关联的AVCodecContext
结构体,包含了所有与编解码器相关的详细信息和操作。 -
avg_frame_rate
: 平均帧率,用AVRational
类型表示。 -
r_frame_rate
: 实际帧率,通常用于视频流。 -
disposition
: 流的一些附加属性,例如是否有默认音频或是字幕。 -
metadata
: 指向一个AVDictionary
结构,包含了关于流的元数据键值对,例如语言或标题。 -
side_data
: 一个指向AVPacketSideData
数组的指针,存储了和流相关但不属于常规数据包的一些附加信息。
常见解复用操作
以下是使用FFmpeg进行解复用操作可能进行的一些步骤:
- 打开媒体文件:使用
avformat_open_input
函数打开输入文件,并创建AVFormatContext
。 - 读取流信息:调用
avformat_find_stream_info
函数获取媒体文件中的流信息。 - 查找音视频流:检查
AVFormatContext
中的AVStream
来识别各个音视频流。 - 读取数据包:通过
av_read_frame
循环读取媒体文件中的数据包。 - 关闭输入:使用
avformat_close_input
清理并关闭AVFormatContext
。
例:
下面是一个解复用过程的代码片段:
AVFormatContext *fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, "input.mp4", NULL, NULL) < 0) {
// 打开文件失败
}
// 读取媒体文件信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
// 读取流信息失败
}
// 查找第一个视频流
AVStream *video_stream = NULL;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream = fmt_ctx->streams[i];
break;
}
}
// 读取数据包
AVPacket pkt;
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
// 判断数据包是不是视频流的一部分
if (pkt.stream_index == video_stream->index) {
// 你可以在这里处理视频数据包
}
av_packet_unref(&pkt);
}
// 关闭输入
avformat_close_input(&fmt_ctx);
编解码相关 AVCodec XXX等
AVCodec
AVCodec
:代表一个编解码器,描述了编解码器的相关操作如打开、关闭、编解码等。
AVCodec
的一些关键成员:
name
:编解码器的名称,用以表征编解码器的内容(例如 “h264”、“aac” 等)。long_name
:编解码器的长名称,用于更详尽地描述编解码器(例如 “H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10”)。type
:编解码器的类型,表示它是视频编解码器、音频编解码器还是字幕编解码器,类型为AVMediaType
枚举中的一个值。id
:编解码器的特定ID,对应于AVCodecID
枚举中
AVCodecContext
AVCodecContext
:提供了编解码的上下文,包含了一系列进行编解码所需的参数和设置。
AVCodecContext
的一些关键成员:
codec_id
:代表了使用的编解码器的AVCodecID
。codec_type
:媒体类型(视频、音频、字幕等),对应AVMediaType
。codec
:指向实际的编解码器的指针。bit_rate
:码率,表示编解码的比特率。width
和height
:对于视频流,分别表示视频的宽和高。sample_aspect_ratio
:表示视频像素的宽高比。pix_fmt
:对于视频流,表示像素格式(AVPixelFormat
)。sample_fmt
:对于音频流,表示样本格式(AVSampleFormat
)。sample_rate
:对于音频流,表示采样率。channel_layout
:对于音频流,表示通道布局。channels
:对于音频流,表示通道数量。time_base
:时间基准,用于时间戳的转换。gop_size
:关键帧间的最大帧数。max_b_frames
:B帧的最大数量。profile
:编解码器使用的配置文件。level
:编解码器级别。delay
:编解码的延迟。priv_data
:指向编解码器私有数据的指针。flags
:各种标志,控制编解码过程中的选项(例如:是否进行帧间预测)。extradata
和extradata_size
:一些编解码器要求的额外数据和其大小。
AVCodecParameters
AVCodecParameters
:包含了流的编解码相关参数,如编解码器的ID、比特率、帧率、分辨率等。
AVCodecParameters
结构体的关键成员包括:
codec_type
:此流的媒体类型,例如视频、音频或字幕。codec_id
:编解码器的标识符,指定了使用哪种编解码器。codec_tag
:四字节码(fourcc),用来表示媒体的编码格式。bit_rate
:平均码率(比特/秒)。format
:对于视频流,表示像素格式;对于音频流,表示音频样本格式(sample format)。width
、height
:视频流的宽和高。field_order
:视频的字段排序(如逐行、交错等)。color_range
、color_primaries
、color_trc
、color_space
、chroma_location
:视频的色彩相关参数。video_delay
:视频流中的初始延迟帧数。
对于音频流,AVCodecParameters
还包括:
channel_layout
:指明了音频流通道的布局。channels
:通道数。sample_rate
:音频采样率。block_align
:音频流中单个样本的字节数。frame_size
:音频帧的大小,对于固定帧率音频编码是一致的。
压缩数据 AVPacket
AVPacket
是 FFmpeg 中用来存储压缩数据(例如一个视频帧或者一个音频帧)的数据结构。它包含了编解码器所需的压缩数据,并附带时间戳、持续时间以及其他编解码过程中必须的元数据。
AVPacket
结构体的关键成员包括:
pts
(Presentation Time Stamp):展示时间戳,表示这个数据包应该被展示的时间。它是基于流的time_base
表达的。dts
(Decoding Time Stamp):解码时间戳,用于指定数据包解码的顺序。对于没有B帧的简单流,pts
和dts
相同。duration
:数据包的持续时间,基于流的time_base
。data
:指向存储压缩数据的实际字节序列的指针。size
:data
指向的数据的大小,以字节为单位。stream_index
:这个AVPacket
从属的流的索引,用于从AVFormatContext
中识别出相应的流。flags
:包括一些标志位,比如是否是关键帧(AV_PKT_FLAG_KEY
)。side_data
:一个指向额外数据数组的指针,以及这个数组中的元素数量。额外数据用于传输不在主要数据流中的其他信息,例如颜色空间信息、HDR元数据等。pos
:这个AVPacket
在媒体文件中的字节位置,有助于调试或者定位。
通过 AVPacket
,FFmpeg 可以将压缩数据从编码器传输到多路复用器(muxer)或从解复用器(demuxer)传输到解码器,同样也用于过滤器之间的数据传递。
未压缩数据 AVFrame
AVFrame
结构体在 FFmpeg 库中用于存储未压缩的数据,比如解码后的视频帧或音频样本。它是媒体处理中一个非常重要的组成部分,尤其是在解码、转码和过滤等操作中。
下面是 AVFrame
结构体的一些关键成员:
data
:这是一个指针数组,用于存储实际的音视频帧数据。对于视频来说,data[0]
、data[1]
和data[2]
通常分别用于存储 Y、U、V 组件(对于 YUV 格式的视频)。对于音频来说,这个数组中的每个指针对应一个声道的样本数据。linesize
:这个整数数组存储了每个数据平面的步长(每行的字节数)。这对于视频帧是必须的,因为图像行不一定是紧凑排列的。extended_data
:这是data
数组的一个扩展版本,它对于包含更多声道数据的平面格式音频尤其有用。width
和height
:对于视频帧,这代表了图像的宽度和高度。nb_samples
:对于音频帧,这表示帧中含有的样本数量。format
:对视频来说,这是一个指明像素格式(AVPixelFormat
)的枚举值;对音频来说,这是一个指明样本格式(AVSampleFormat
)的枚举值。key_frame
:一个标志,指示这个帧是否是关键帧。pts
:表示这个帧的演示时间戳,基于编解码器上下文的time_base
。best_effort_timestamp
:解码器设置的最佳尝试时间戳,这有助于无pts
的情况下估算实际的时间戳。pkt_duration
:基于流的time_base
,这个帧的持续时间。pkt_pos
:帧原始数据在媒体文件中的位置,用于调试。channel_layout
:对于音频帧,指示声道布局。channels
和sample_rate
:音频帧的声道数和样本率。metadata
:帧相关的元数据。
AVFrame
通常用在视频/音频解码器的输出端或编码器的输入端。它提供了一个通用并且灵活的方式来处理未压缩的音视频数据。
FFmpeg 面向对象思想
封装(Encapsulation)
FFmpeg 中的数据结构像 AVCodecContext
, AVFrame
, AVPacket
等封装了与编解码器、数据帧和数据包相关的状态和行为。对这些结构体的操作通常通过 API 函数进行,而不是直接访问内部成员,这类似于面向对象中的封装概念。
继承(Inheritance)
虽然 C 不支持正式的继承,FFmpeg 使用了指针来模拟继承。例如,所有的解码器(AVCodec
)都有共同的函数和成员,但也可以定义额外的成员来扩展基本功能,类似于在面向对象编程中的基类和派生类概念。
多态(Polymorphism)
FFmpeg 通过函数指针在结构体中实现了多态。比如 AVCodec
结构体中包含多个函数指针,编解码器可以通过设置这些指针到特定函数来定义特定的行为。例如,不同的编解码器可以有不同的 encode2
或 decode
函数实现。这使得代码可以在运行时根据不同编解码器的具体实现选择正确的函数来调用。
抽象(Abstraction)
FFmpeg 抽象出了一套 API,隐藏了底层的复杂实现细节,提供了相对简单的接口供开发者使用。例如,不管背后具体是什么编解码器,开发者都可以通过统一的 avcodec_open2
、avcodec_decode_video2
、avcodec_encode_audio2
等函数来执行操作。
总而言之,即使 FFmpeg 是用 C 语言编写的,它也在设计上模拟了一些 OOP 的概念,提高了代码的模块性、可读性和可维护性。然而,需要注意的是,这种模拟并不等同于真正的面向对象编程语言中的实现,如 C++,其中有对继承、多态、封装等特性的原生支持。
Packet/Frame 数据零拷贝
“零拷贝”(Zero-Copy)是一种优化技术,旨在降低数据传输过程中的CPU消耗,减少内存使用,降低延迟。在处理 AVPacket
或 AVFrame
数据时,零拷贝的目的是尽可能减少数据从一个缓冲区到另一个缓冲区的复制操作。
如何实现零拷贝:
-
引用计数(Reference Counting): FFmpeg 为
AVFrame
和AVPacket
结构提供了引用计数机制。通过增加引用计数来“共享”数据而不实际复制数据,这可以通过使用av_frame_ref
或av_packet_ref
函数来实现。 -
直接缓冲区访问(Direct Buffer Access): 当解码器或编码器处理数据时,而不需要真正的数据所有权转移,它可以直接在原始缓冲区上操作,通过提供数据的指针而不是数据本身的副本。
-
自定义缓冲区分配(Custom Buffer Allocation): 通过接管缓冲区的分配和释放,用户可以控制数据的存储方式,例如,可以重用或池化 buffers 来减少频繁的内存分配。
-
内存映射(Memory Mapped I/O): 在适当的硬件和操作系统支持下,可以直接映射文件或设备内存到进程的地址空间,从而避免了数据在内核和用户空间之间的拷贝。
FFmpeg 中零拷贝的例子:
-
解码器零拷贝:某些解码器支持零拷贝解码,即直接在 GPU 的内存中解码视频帧,然后通过硬件加速接口如 VDPAU, DXVA2, 或 VideoToolbox 将帧传递给显示或者进一步处理。
-
过滤器零拷贝:FFmpeg 的过滤器图可以被配置为在过滤器之间传递
AVFrame
的引用而非数据副本。 -
硬件加速:使用 FFmpeg 的硬件加速API(如 AVHWDeviceContext 和 AVHWFramesContext)可以实现数据在硬件加速的解码器和编码器间的零拷贝传输。
参考资料:
音视频流媒体开发课程(从基础到高级,从理论到实践)学习计划、一对一答疑
音视频开发(FFmpeg/WebRTC/RTMP)
整理了一些音视频开发学习资料、面试题 如有需要自行添加群:739729163 领取