FFmpeg是一个模块化架构 的项目,各组件在功能上相互独立,又通过标准的数据结构(如AVPacket、AVFrame)协同工作,共同完成多媒体任务。
下面的关系图和表格概括了其核心架构:
(HTTP, FILE, RTMP)"] Format["封装/解封装层
(MP4, FLV, MKV)"] Codec["编解码层
(H.264, AAC)"] Filter["滤镜处理层
(scale, overlay)"] Device["设备层
(摄像头, 扬声器)"] Util["基础工具库
(公共函数, 数据结构)"] end subgraph B [数据处理流程] Input[输入源] --> Format Format -- AVPacket
压缩数据 --> Codec Codec -- AVFrame
原始数据 --> Filter Filter -- AVFrame
处理后数据 --> Codec Codec -- AVPacket
压缩数据 --> Format Format --> Output[输出目标] end subgraph C [支持与工具库] Sws["视频处理
(swscale)"] Swr["音频处理
(swresample)"] end subgraph D [用户接口层] Ffmpeg[命令行工具 ffmpeg] Ffplay[播放器 ffplay] Ffprobe[分析器 ffprobe] end C -- 支撑 --> B Util -- 基础支撑 --> A B -- 实现核心功能 --> D
🔧 核心库与职责
FFmpeg的核心功能主要由以下库实现,其职责联系清晰:
| 核心库 | 主要职责与说明 | 依赖关系与协作 |
|---|---|---|
| libavformat | 封装/解封装层 :处理媒体容器格式 (如MP4、FLV)和网络协议(如RTMP、HLS),负责读取/写入音视频流。 | 所有I/O的起点与终点。它依赖libavcodec进行编解码,为libavfilter提供数据源。 |
| libavcodec | 编解码层 :提供各种音视频编解码器(如H.264、AAC),实现数据压缩与解压缩。 | 核心处理枢纽。接收来自libavformat的AVPacket进行解码,或接收来自libavfilter的AVFrame进行编码。 |
| libavfilter | 滤镜处理层 :提供强大的音视频滤镜框架,可进行裁剪、叠加、缩放、降噪等复杂处理。 | 处理链中间层。接收libavcodec解码后的AVFrame,处理后再送回libavcodec编码。 |
| libavutil | 基础工具库:提供公共辅助函数,如数学计算、数据结构、内存管理等,是其他所有库的基础。 | 基石,被所有其他核心库依赖。 |
| libswscale | 视频处理库 :专门负责图像缩放、像素格式转换(如YUV转RGB)。 | 通常被libavfilter的滤镜或播放器ffplay调用,处理视频帧。 |
| libswresample | 音频处理库 :专门负责音频重采样、格式转换、声道布局调整。 | 通常被libavfilter的滤镜或播放器ffplay调用,处理音频帧。 |
| libavdevice | 设备交互库 :用于采集或输出到硬件设备,如摄像头、屏幕。 | 可视为特殊的libavformat,将硬件设备抽象为I/O设备。 |
💡 如何理解他们之间的联系
理解FFmpeg的秘诀是追踪数据。核心数据结构定义了组件间的"对话语言":
AVPacket:存放压缩后的编码数据 (例如一个H.264帧)。它主要在libavformat(解封装/封装)和libavcodec(解码/编码)之间传递。AVFrame:存放解码后的原始数据 (例如一帧YUV图像或PCM音频)。它主要在libavcodec、libavfilter以及处理库(libswscale/libswresample)之间传递。
🛠️ 联系实例:以转码流程为例
假设你用 ffmpeg -i input.mp4 -vf scale=1280:720 output.mp4 转换视频,其内部组件协作如下:
- 读取 :
libavformat打开input.mp4,解封装得到视频流(压缩的AVPacket)。 - 解码 :
libavcodec(H.264解码器)将AVPacket解码成原始的AVFrame(YUV图像)。 - 处理 :
libavfilter的scale滤镜调用libswscale,将AVFrame缩放到1280x720。 - 编码 :
libavcodec(H.264编码器)将处理后的AVFrame重新编码成AVPacket。 - 写入 :
libavformat将新的AVPacket封装进output.mp4。
希望以上解释能帮助你清晰地理解FFmpeg的架构。为了能更好地解答你的疑问,可以告诉我你是对某个具体组件的内部实现细节感兴趣,还是想了解在特定开发场景(如播放器开发)中如何组织这些组件呢?### 第1步:读取与解封装 (libavformat)
| 类别 | API/数据结构 | 说明 |
|---|---|---|
| 核心结构 | AVFormatContext |
统领整个媒体文件上下文,包含所有流信息、封装格式等,是后续所有操作的根。 |
| 流信息 | AVStream |
代表文件中一路独立的媒体流(如视频流、音频流)。 |
| 压缩数据包 | AVPacket |
本步骤的产出 。存放从文件中读取出来的一帧压缩的音/视频数据。 |
| 关键函数 | avformat_open_input() |
打开输入文件,并填充AVFormatContext。 |
avformat_find_stream_info() |
读取文件头,获取流的详细信息(如编码格式、时长等)。 | |
av_read_frame() |
核心读取函数 。从AVFormatContext中读取下一个AVPacket。 |
|
avcodec_find_decoder() / avcodec_parameters_to_context() |
根据AVStream中的编码信息找到对应的解码器,并将参数复制到解码器上下文。 |
流程简述 :打开上下文 -> 探测信息 -> 找到解码器 -> 循环调用 av_read_frame() 获取 AVPacket。
第2步:解码 (libavcodec)
| 类别 | API/数据结构 | 说明 |
|---|---|---|
| 核心结构 | AVCodecContext |
编解码器上下文,包含了特定编解码器(如H.264)的所有参数和状态。 |
| 原始数据帧 | AVFrame |
本步骤的产出 。存放解码后的原始音视频数据(如YUV像素、PCM音频)。 |
| 关键函数 | avcodec_alloc_context3() |
为指定的编解码器分配AVCodecContext。 |
avcodec_open2() |
使用特定编解码器(如AVCodec *decoder)初始化AVCodecContext。 |
|
avcodec_send_packet() |
发送 一个AVPacket给解码器。这是新的"发送-接收"API的一部分。 |
|
avcodec_receive_frame() |
接收 解码器输出的一帧AVFrame。需循环调用,直到返回AVERROR(EAGAIN)或AVERROR_EOF。 |
流程简述 :初始化解码器上下文 -> 循环将上一步的AVPacket通过 send_packet() 送入 -> 循环通过 receive_frame() 取出 AVFrame。
第3步:处理 (libavfilter 与 libswscale)
这步是可选的,当需要进行缩放、裁剪等处理时才需要。libavfilter是一套复杂的滤镜图框架,这里以简单的 scale 滤镜(内部会调用libswscale)为例。
| 类别 | API/数据结构 | 说明 |
|---|---|---|
| 核心结构 | AVFilterGraph |
管理整个滤镜链(滤镜图)的容器。 |
AVFilterContext |
代表滤镜图中的一个具体滤镜实例(如 scale 滤镜)。 |
|
AVFilterInOut |
用于帮助构建滤镜图的输入/输出端链表。 | |
| 关键函数 | avfilter_get_by_name() |
根据名称(如"scale")获取滤镜。 |
avfilter_graph_create_filter() |
用指定的滤镜创建一个AVFilterContext并加入滤镜图。 |
|
avfilter_graph_parse_ptr() |
更高级的API,可以用字符串描述(如"scale=1280:720")直接解析并构建滤镜图。 | |
avfilter_graph_config() |
配置并验证构建好的滤镜图。 | |
av_buffersrc_add_frame_flags() |
将一个AVFrame送入滤镜图的输入端。 |
|
av_buffersink_get_frame() |
从滤镜图的输出端获取 处理后的AVFrame。 |
|
libswscale |
SwsContext |
图像缩放和格式转换的上下文。 |
sws_getContext() |
创建或获取一个SwsContext,指定源和目标尺寸、格式等参数。 |
|
sws_scale() |
执行实际的缩放/转换操作。 |
流程简述(滤镜方式):
- 创建滤镜图,添加
buffer(输入)、scale(处理)、buffersink(输出)滤镜并链接。 - 配置滤镜图。
- 将第2步的
AVFrame用av_buffersrc_add_frame_flags()送入滤镜图。 - 用
av_buffersink_get_frame()取出处理后的新AVFrame。
第4步:编码 (libavcodec)
此步骤与第2步镜像,方向相反。
| 类别 | API/数据结构 | 说明 |
|---|---|---|
| 核心结构 | AVCodecContext |
编码器上下文,包含了目标编码格式(如H.264)的所有参数(码率、GOP等)。 |
| 关键函数 | avcodec_find_encoder() |
根据编码器ID(如AV_CODEC_ID_H264)或名称找到编码器。 |
avcodec_open2() |
使用编码器初始化AVCodecContext。 |
|
avcodec_send_frame() |
发送 一个AVFrame给编码器。 |
|
avcodec_receive_packet() |
接收 编码器输出的一帧AVPacket。需循环调用。 |
|
av_frame_get_buffer() |
为新创建的AVFrame分配数据缓冲区(在处理完准备送入编码器时可能需要)。 |
流程简述 :初始化编码器上下文 -> 将第3步处理后的AVFrame通过 send_frame() 送入 -> 循环通过 receive_packet() 取出压缩后的 AVPacket。
第5步:写入与封装 (libavformat)
此步骤与第1步镜像,方向相反。
| 类别 | API/数据结构 | 说明 |
|---|---|---|
| 核心结构 | AVFormatContext |
输出文件的上下文。 |
| 流信息 | AVStream |
需要在输出文件中创建对应的流,并从编码器上下文复制参数。 |
| 关键函数 | avformat_alloc_output_context2() |
根据格式或文件名,为输出文件分配AVFormatContext。 |
avio_open() |
打开输出文件的I/O上下文。 | |
avformat_new_stream() |
在输出上下文中创建一个新的AVStream。 |
|
avcodec_parameters_from_context() |
关键 :将编码器AVCodecContext中的参数复制到输出AVStream的codecpar中。 |
|
avformat_write_header() |
写入文件头。 | |
av_interleaved_write_frame() / av_write_frame() |
核心写入函数 。将编码得到的AVPacket写入文件,前者能保证交错写入的正确性。 |
|
av_write_trailer() |
写入文件尾。 | |
| 时间处理 | av_packet_rescale_ts() |
至关重要 :将AVPacket的时间戳从编码器的时基转换为输出流的时基。 |
流程简述 :创建输出上下文 -> 创建流并复制参数 -> 写文件头 -> 循环将第4步的AVPacket 进行时间戳转换后 写入 -> 写文件尾。
总结与联系
整个过程的核心就是数据结构 AVPacket 和 AVFrame 在几个核心上下文(AVFormatContext, AVCodecContext, AVFilterGraph)之间,通过 send/receive、read/write 系列函数进行流转。