[FFmpeg] 输入输出访问 | 管道系统 | AVIOContext 与 URLProtocol | 门面模式
链接:https://trac.ffmpeg.org/
docs:FFmpeg
FFmpeg
是一个强大的多媒体框架,旨在处理媒体处理的各个阶段。
它就像一个数字媒体工厂,包含以下部门:打包/解包(容器处理)、
转译/压缩(编解码器处理)、管理原始视频帧和压缩数据包、
连接输入/输出源(输入输出访问)、执行编辑和特效(过滤与处理)、
保持所有内容的同步性(时间与速率管理),并提供灵活定制(配置选项)。
概览
章节列表
- 输入输出访问
- 容器处理
- 压缩媒体数据包
- 原始媒体帧
- 编解码器处理
- 过滤与处理
- 时间与速率管理
- 配置选项
第一章:输入输出访问
欢迎来到FFmpeg的第一章~
在FFmpeg能够执行诸如转换视频或提取音频等酷炫操作之前,它需要从某处获取多媒体数据并将其发送到另一处
。这个首要关键步骤由FFmpeg的输入输出访问层处理。
可将输入输出访问视为连接FFmpeg与外部世界的"管道系统"。
就像房屋需要管道来进出水一样,FFmpeg需要一种方式来输入输出视频、音频和其他数据。该层提供了处理数据流的标准化方式,无论数据是来自计算机本地文件、网络视频流,甚至是网络摄像头等实时捕获设备。
如果没有这种抽象层,FFmpeg将需要完全不同的代码来读取本地.mp4
文件(与从网站下载视频或从麦克风捕获相比)。输入输出访问层隐藏了这些差异,提供了一致的接口。
让我们从一个常见用例开始:将视频文件从一种格式转换为另一种格式。假设您有一个名为input.mp4
的视频文件,希望另存为output.avi
。
在命令行中使用ffmpeg
工具(底层使用FFmpeg库)时,通常执行如下操作:
ffmpeg -i input.mp4 output.avi
从输入输出角度分析此命令:
-i input.mp4
:告知FFmpeg使用input.mp4
作为输入源,-i
选项是标准输入文件或URL指定参数output.avi
:指定输出目标,FFmpeg将其识别为文件名并准备写入数据
在此简单命令中,FFmpeg的输入输出访问层负责:
- 打开
input.mp4
进行读取 - 分块读取
input.mp4
数据 - 打开
output.avi
进行写入 - 将处理后的数据写入
output.avi
-f
选项(-format
缩写)可显式指定输入输出格式,这对于没有明确头部的原始视频数据等输入尤为重要:
ffmpeg -f rawvideo -video_size 640x480 -pixel_format yuv420p -i input.yuv output.mp4
此处-f rawvideo
告知FFmpeg将input.yuv
视为原始视频数据,因为.yuv
文件本身不包含尺寸或像素格式等格式信息。输入输出层仍负责打开和读取文件,但需要这些额外参数(-video_size
, -pixel_format
)来理解读取数据的结构。
因此,输入输出访问层的核心职责是获取字节输入和发送字节输出。
内部机制:管道系统
当运行
ffmpeg -i input.mp4 output.avi
时,FFmpeg如何处理文件读取?
FFmpeg使用称为协议(或URLProtocol)的组件来了解如何与不同类型数据源和目的地交互。
当提供input.mp4
时,FFmpeg识别其为本地文件,内部通常视为file://input.mp4
URL。
随后FFmpeg查找能处理file://
URL的"协议处理器"——即file
协议。同理,output.avi
也由file
协议处理写入。
这些协议处理器抽象了底层细节。
FFmpeg主引擎只需要求输入输出层"打开此URL"、“读取数据”、“写入数据"或"关闭URL”,相应协议处理器执行实际工作,无论是调用文件系统函数、发起网络请求还是与设备驱动交互。
FFmpeg I/O处理流程:
- FFmpeg告知输入输出层打开目标,输入输出层使用正确协议(如本例的
file
协议)与外部资源交互。 - 随后FFmpeg循环请求输入数据并提供输出数据直至完成,最终关闭所有资源。
超越本地文件:多样化数据源与目的地
输入输出访问层的强大之处在于其通过统一方式处理多种不同类型数据源和目的地的能力。
除本地文件外,FFmpeg还能读写:
-
网络流媒体:HTTP、RTMP、RTSP、TCP、UDP等,只需提供不同URL:
ffmpeg -i http://example.com/video.mp4 output.avi ffmpeg -i input.mp4 rtmp://publish.example.com/app/streamkey
此类情况使用
http
或rtmp
等专用协议,详见FFmpeg文档协议选项。 -
捕获/播放设备:摄像头、麦克风、屏幕捕获、视频/音频卡,通过操作系统特定的设备名或URL访问:
ffmpeg -f video4linux2 -i /dev/video0 output.mp4 # Linux摄像头 ffmpeg -f dshow -i video="集成摄像头":audio="麦克风" output.mp4 # Windows摄像头+麦克风
使用设备特定的输入输出处理器(参见输入设备和输出设备)。
-
标准输入/输出(管道):通过stdin读取、stdout写入,实现命令行工具链式调用:
cat input.ts | ffmpeg -i pipe:0 output.mp4 # 从stdin读取 ffmpeg -i input.mp4 -f avi pipe:1 | ffplay pipe:0 # 写入stdout
通常使用
pipe
或fd
协议。
核心思想保持不变:FFmpeg通过输入输出层获取原始数据字节,无需关心字节获取方式(通过各种上层协议的制定,实现了很好的抽象
),只需确保可读写。
🎢协议解析结构体:AVIOContext与自定义数据源处理
AVIOContext 与 URLProtocol
-
AVIOContext
是 FFmpeg 中用于抽象 I/O 操作的核心结构体,负责数据的读写缓冲和协议处理。 -
它通过
URLProtocol
实现与具体协议(如文件、HTTP、RTMP等)的交互。
URLProtocol 是协议实现的接口,定义了一组标准函数(如打开、读取、写入、关闭等)。每种协议(如 file、http)需要实现自己的 URLProtocol 实例。
协作流程
AVIOContext 初始化时绑定一个 URLProtocol
当用户通过 AVIOContext 读写数据时,AVIOContext 会调用对应 URLProtocol 的函数。
例如,读取 HTTP 数据时:
- AVIOContext 处理
缓冲
和状态管理
。- 实际网络
请求由
HTTP 协议的URLProtocol 实现
完成。
联系
- AVIOContext 是上层抽象,提供统一的 I/O 接口。
- URLProtocol 是底层实现,处理具体协议的细节。
- 开发者通常只需操作 AVIOContext,无需直接调用 URLProtocol。
FFmpeg支持多种协议(HTTP、RTMP、RTSP、文件等)的多媒体数据获取。其协议解析模块通过三个核心结构体实现灵活的数据处理:
核心结构体解析
1. AVIOContext
头文件:libavformat/avio.h
https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/avio.h
关键字段:
buffer
:数据缓冲区buffer_size
:缓冲区容量pos
:当前读取偏移量
opaque
:指向用户数据或URLContextread_packet
/write_packet
:读写回调函数
缓冲区关系:
2. URLContext
典型实现(以RTP协议
为例):
typedef struct RTPContext
{URLContext *rtp_hd, *rtcp_hd;int ttl;int buffer_size;// ...其他协议特定字段
} RTPContext;const URLProtocol ff_rtp_protocol =
{.name = "rtp",.url_open = rtp_open,.url_read = rtp_read,.priv_data_size = sizeof(RTPContext),// ...其他回调函数
};
3. URLProtocol
作为协议实现的基类,定义标准操作接口:
url_open
:建立协议连接url_read
/url_write
:数据读写priv_data_size
:协议私有数据大小
URLProtocol是FFMPEG操作文件的结构(包括文件,网络数据流等等),包括open、close、read、write、seek等操作。
定义了协议相关的回调函数,类似于url协议的虚函数定义
。
也类似门面模式
,具体的回调函数的方法实现在ff_rtp_protocol
等具体的对象中。
- 门面模式通过一个统一的高层接口简化复杂子系统调用,像前台接待一样隐藏内部细节。
应用场景
标准协议处理
使用avformat_open_input
即可完成:
AVFormatContext *fmt_ctx = NULL;
avformat_open_input(&fmt_ctx, "rtsp://example.com/stream", NULL, NULL);
自定义数据源处理
当需要处理以下特殊场景时需直接操作AVIOContext:
- 内存数据源
struct buffer_data {uint8_t *ptr; size_t size;
};static int read_packet(void *opaque, uint8_t *buf, int buf_size) {struct buffer_data *bd = opaque;// 实现自定义读取逻辑
}
- 实时流处理(代码示例)
// 创建自定义AVIOContext
avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size,0, &bd, &read_packet, NULL, NULL);// 关联到FormatContext
fmt_ctx->pb = avio_ctx;
avformat_open_input(&fmt_ctx, NULL, NULL, NULL);
- 加密传输
static int decrypt_packet(void *opaque, uint8_t *buf, int buf_size) {// 解密逻辑实现return decrypt_data(buf, buf_size);
}
示例代码
#include <libavformat/avformat.h>struct buffer_data {uint8_t *ptr;size_t size;
};static int read_packet(void *opaque, uint8_t *buf, int buf_size) {struct buffer_data *bd = opaque;// 实现内存到缓冲区的拷贝逻辑
}int main() {// 初始化AVFormatContextAVFormatContext *fmt_ctx = avformat_alloc_context();// 创建自定义IO上下文AVIOContext *avio_ctx = avio_alloc_context(av_malloc(4096), 4096, 0, &bd, &read_packet, NULL, NULL);fmt_ctx->pb = avio_ctx;avformat_open_input(&fmt_ctx, NULL, NULL, NULL);// 后续处理流程...
}
运行测试前置条件:
sudo apt-get update
sudo apt-get install libavformat-dev libavutil-dev
运行:
gcc -o ffmpeg_buffer_test ffmpeg_buffer_test.c -lavformat -lavutil
架构设计思想
FFmpeg通过分层设计实现协议处理的灵活性:
- 高层接口:
avformat_open_input
封装常规操作 - 扩展层:
AVIOContext
提供自定义I/O接入点 - 协议实现层:
URLProtocol
定义标准协议接口
这种设计使得开发者既能快速处理标准协议,又能通过自定义AVIOContext实现特殊需求,在易用性和灵活性之间取得平衡。
C语言API:AVIOContext与URLProtocol
对于使用FFmpeg库(libavformat、libavcodec等)的开发人员,输入输出访问层主要通过AVIOContext
结构体和URLProtocol
相关函数管理。
AVIOContext
:表示I/O操作上下文,包含读写缓冲区及指向底层I/O函数的指针URLProtocol
:定义特定协议处理器,包含协议名("file"
、"http"
、"rtmp"
)及实现打开、读写、寻址、关闭等操作的函数指针
多数应用不直接操作
URLProtocol
,而是通过avio_open2
等函数为给定URL创建AVIOContext
。
// API函数使用示例(非完整代码)
AVFormatContext* fmt_ctx = avformat_alloc_context(); // 媒体文件格式上下文
AVIOContext* avio_ctx = NULL; // I/O操作上下文
char* input_url = "input.mp4";
int ret;// 使用avio_open2打开输入URL并创建AVIOContext
ret = avio_open2(&avio_ctx, input_url, AVIO_FLAG_READ, NULL, NULL);
if (ret < 0) {// 错误处理
}fmt_ctx->pb = avio_ctx; // 将AVIOContext关联到格式上下文// ...后续读取数据...
uint8_t buffer[4096];
int bytes_read = avio_read(avio_ctx, buffer, sizeof(buffer));// ...关闭资源...
avio_close(avio_ctx);
// 注意:avio_context_free在关闭后释放AVIOContext本身
该代码段展示了核心函数:
avio_open2
初始化avio_read
/avio_write
数据传输avio_close
终止操作。
doc/APIchanges
文件记录了库接口变更,包含诸多AVIOContext
相关更新,例如avio_open2
的引入、支持更多参数等。
示例代码如
avio_list_dir.c
展示了目录协议交互avio_read_callback.c
演示了自定义I/O上下文。
这些API细节主要面向使用FFmpeg库的开发人员,证实输入输出访问层(AVIOContext
、URLProtocol
)是FFmpeg架构的基础组成部分,专门负责原始数据字节的输入输出。
平时调api,最多用到avio,具体的数据结构和urlprotocol的实现我们并不关心,我们只需要调用,并且能输入和输出数据
总结
本章我们了解到,在FFmpeg解码、编码或处理多媒体数据前,必须首先读取数据,处理完成后写出数据。
这是输入输出访问层的职责,它通过特定协议处理器为本地文件、网络流和硬件设备等多样化数据源/目的地提供统一处理方式。
现在我们理解FFmpeg如何获取原始字节
,下一步是解析这些字节
。
多媒体数据通常封装在容器格式(如MP4
、MKV
、AVI
)中,这些容器提供结构和元数据。
下一章我们将探讨FFmpeg如何处理这些容器。
容器处理
(抽象和协议的重要性)
统一接口设计 --前文传送:
[xiaozhi-esp32] 应用层(9种state) | 音频编解码层 | 双循环架构
[xiaozhi-esp32] 音频处理 | Display | 纯虚锁的抽象思想
[Subtitle Edit] 字幕数据模型Paragraph
| 核心逻辑库(libse)
[shadPS4] 内存管理 | 权限管理 |文件系统 | 挂载和句柄