先交代写法:六条路各按同一套写------赢在哪、真实限制、一句判词,限制那栏不会因为哪条路更新更热就手软。另外先消个歧:文中说的 Rust crate ez-ffmpeg,和 JS 圈那个上过 HN 首页的 "Ez FFmpeg" 同名不同物,两者无关。
常见的选型场景是这样的:你要在 Rust 服务里抽几帧喂模型,或者把音轨转成 16 kHz 采样喂语音识别。打开 crates.io,先看到月下载 128 万的 ffmpeg-next,点进仓库,维护者原话是 "maintenance-only mode for the most part... Any PR to improve existing API is unlikely to be merged";转头问社区,得到 Rust 老兵 kornel 的建议------"avoid ffmpeg... all incomplete and poorly maintained";最后在 rustcc 的帖子里看到被认同的退路:直接 Command::new("ffmpeg"),理由是「最靠谱,没那么多坑」。
一圈下来,很多人就停在了最后一步。本文把 2026 年 7 月实际可走的六条路摆开:每条路赢在哪、真实限制是什么、一句判词。口径说明:下载数均按月统计,数据截至 2026-07;笔者没核实过的数字一律不进正文,表中以「---」标注。
路 1:std::process::Command + ffmpeg------被低估的默认答案
赢在哪。 互联网三十年积累的 FFmpeg 知识都以命令行形式存在,拿来即用:HN 上命令行教程类内容动辄几百分(FFmpeg by Example 920 分),你遇到的每个任务几乎都有现成命令。没有 FFI、没有链接步骤、没有版本适配。rustcc 那句「最靠谱,没那么多坑」不是玩笑,是大量踩坑之后的理性收敛。
真实限制。 部署时要带上 ffmpeg 可执行文件;更要命的是,数据一旦要回到你的进程里------帧、PCM 采样------就得走 stdout 解析或临时文件。jaredly 早在 2015 年提出的 issue 里就是这个模式:先把画面渲染成图片,再用命令行 ffmpeg 拼成视频(原话 "using ffmpeg on the cli")。十年过去,很多人仍在这么干。不是因为它好,是因为别的路一直没铺平。
判词:产物是文件、不需要数据回内存,它今天仍是对的;数据要进内存,代价就来了。
路 2:ffmpeg-sidecar------还是子进程,但封装得省心
赢在哪。 月下载约 13 万,自述能把任意视频当作裸 RGB 帧数组迭代(原话 "as if it were an array of raw RGB frames")。它把「启动子进程」封装成了结构化 API:你写的是迭代器,不是 stdout 解析器。因为通过子进程的 stdin/stdout,它完全不链接 libav------编译、链接、平台适配这些麻烦,与你无关。想要帧数据、又不想碰 FFI 的人,这是体验最好的一条路。
真实限制。 本质仍是子进程:运行环境里最终要有一个 ffmpeg 可执行文件,帧数据要穿越进程边界;进程内的滤镜图、精细控制不在这个模型里。进程边界在你的负载下代价有多大,量了才算数------本文不引用数字。
判词:接受子进程模型的前提下,拿帧最省心的路。
路 3:ffmpeg-next / rsmpeg------底层绑定库,整个生态的地基
赢在哪。 要完整的 libav 控制面------自定义 IO、冷门封装格式、逐 packet 的精细操作------这一层没有替代品。ffmpeg-next 月下载 128 万,ez-ffmpeg 自己也是通过它链接 libav 的;这个数字本身就说明有多少东西压在它上面。
真实限制。 先说维护状态:ffmpeg-next 是有意冻结的("maintenance-only mode for the most part"),不是废弃,但改进型 PR 基本不会被合并;而它所在的 fork 链已有三代------ffmpeg → ffmpeg-next → ffmpeg-the-third。再说使用成本:解一帧要手写 send_packet/receive_frame,40 行起步的样板代码,连「音视频一起转码」这种任务都可能翻不到现成示例------有人看完示例后留下的原话是 "Examples... do not include transcoding of audio+video"。kornel 那句 "avoid ffmpeg... all incomplete and poorly maintained",骂的正是这一类库的整体状态。
还有一个反直觉的事实:这一层以 safe 封装为卖点的库,也有公开 API 被标注 "might trigger undefined behavior" 的先例(zmwangx/rust-ffmpeg#225)。safe 的皮,FFI 的骨------绑定层的 safe 是工程承诺,不是数学证明。
单看 rsmpeg:877 star,挂在 larksuite 组织下,支持 FFmpeg 6/7/8(0.18.0 跟进了 FFmpeg 8.0)。节奏确实慢------约 11 个月没有大动作------但说它「休眠」言过其实。要贴着 FFmpeg 大版本走的底层封装,目前它是跟得更紧的那个。
判词:当地基,极称职;当日常 API,心智负担全额自付。
路 4:video-rs------高层视频,自己说「还没做完」
赢在哪。 高层方案中,长期只有它一个像样的选项(月下载约 3.2 万)。它 2023 年就提供了 PyAV 风格的 decode/encode:十行代码内迭代出 RGB ndarray;README 示例本身就是用 ndarray 逐帧编码出一个 rainbow.mp4;还有 seek_to_frame(是否帧精确,文档没说)。今天想在 Rust 里获得 PyAV 式的视频解码体验,它是最顺手的现成答案。
真实限制。 限制也写在它自己的 README 里:"still a work-in-progress... some parts not flushed out","Use with caution"。没有音频 API。官方后继项目 rave 的目标是脱离 FFmpeg,但目前 "not ready for use yet"。
判词:视频单科很能打;音频缺席加自述未完成,决定了它是单科选手。
路 5:symphonia + rubato------纯 Rust 音频,直到撞上编解码墙
赢在哪。 纯 Rust 能从 MP4/MKV 里解出音轨并重采样到 16 kHz(symphonia + rubato,symphonium 一次调用搞定;mutter 五行就能喂给 whisper-rs)。解码和重采样整条链路都是 Rust,是本文六条路里唯一从头到尾不碰任何 C 产物的------不链 libav,也不需要 ffmpeg 二进制。供应链干净这件事,这条路独享。顺带修正一个流传的误会:whisper-rs 的 GitHub 仓库虽已 archive,但在 Codeberg 继续维护,月下载约 17 万------别当它已经死了。
真实限制。 要「任意视频、任意编码、一行到位」就撞上编解码墙:symphonia 的 HE-AAC、Opus 仍标注未完成,AC-3 根本不在支持表里------这正是 FFmpeg 后端仍不可替代的地方。另外,视频不在它的射程内。
判词:音频任务且编码在支持表内,它是最干净的方案;出了表,就得回 FFmpeg 系。
路 6:ez-ffmpeg------进程内 FFmpeg 运行时
官方定位原文如下(英文照抄):"the actively-developed high-level in-process FFmpeg runtime for Rust."
这个定位的核心不是哪一件功能,而是「把 FFmpeg CLI 原样搬进 Rust」的人体工程学。 一条链式调用读起来就是一条命令:
rust
FfmpegContext::builder()
.input("input.mkv")
.output(
Output::from("output.mp4")
.set_video_codec("libx264") // -c:v libx264
.set_video_codec_opt("crf", "23") // -crf 23
.set_video_codec_opt("preset", "fast") // -preset fast
.set_audio_codec("aac"), // -c:a aac
)
.build()?
.start()?
.wait()?;
迁移是机械平移,没有要重学的概念:秒数换成微秒(-ss 10 → set_start_time_us(10_000_000));参数名去掉 - 原样可用(crf、preset、movflags 这类走 set_*_opt,直通 FFmpeg 的 AVOption 系统------CLI 认什么名字,这里就认什么名字);-vf 后面那串滤镜一字不改填进 filter_desc;选流也是 CLI 的 stream specifier 语法(0:a:0)。你攒了多年的 ffmpeg 命令行直觉,在这里一条都不作废。
0.15 起,连「平移」这一步都可以先省掉------命令直接粘进去跑。 开启 cli 特性后,from_cli 接受一条完整的 ffmpeg 命令字符串,在你自己的进程里解析、建流水线、跑完,不起子进程:
rust
use ez_ffmpeg::cli::from_cli;
fn main() -> Result<(), Box<dyn std::error::Error>> {
from_cli("ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset veryfast -c:a aac -y output.mp4")?
.start()?
.wait()?;
Ok(())
}
值得单说的是它的态度:命令被分成三类------验证过的形态 才允许执行(目前 6 个,每个都有语义金样与真正的 ffmpeg CLI 逐项对拍过);没验证的 只翻译、不执行;不认识的 当场拒绝,并锚定到出问题的那个 token。姊妹函数 emit_rust_code 则把同一条命令翻译成一段可直接编译的 builder 代码,你可以接着改、接着扩展。它宁可清清楚楚地拒绝你,也不做那种「跑起来了,但结果和 ffmpeg 微妙地不一样」的事。
同构不止停在 API 面,一直通到源码。 内部调度按 ffmpeg CLI 自身的架构建模:fftools 里的 ffmpeg_demux.c / ffmpeg_dec.c / ffmpeg_filter.c / ffmpeg_enc.c / ffmpeg_mux.c / ffmpeg_sched.c / sync_queue.c,在这里对应 demux_task / dec_task / filter_task / enc_task / mux_task / ffmpeg_scheduler / sync_queue------文件对文件。这带来两个实际好处:一,行为要向 CLI 对齐时,有逐行可对照的参照物,出了差异能定位到 fftools 的对应位置;二,想搞懂 FFmpeg 内部机制的人,可以把这份代码当成带类型、带所有权语义的 fftools 对照读本------比直接啃 C 源码的坡度缓得多。
功能面,先交代历史,再说新东西。 ez-ffmpeg 0.13 时代就能用 FrameFilter 在流水线里拦截、改写每一帧(仓库自带自定义 tile/volume 滤镜示例);0.14 新增了专用的 FrameExtractor / SampleExtractor / VideoWriter------把「能做到」变成「一行做到」,并默认依据色彩标签正确完成 YUV→RGB 转换。具体来说:FrameExtractor 抽帧支持采样策略(EveryNth / UniformN / Keyframes);SampleExtractor 直接给出 16 kHz mono f32,可以喂 whisper-rs;VideoWriter 将你自己渲染的帧送入编码流水线(write / write_owned),带滤镜图校验,输出端支持文件与 RTMP 直播目标(直播目标有 API 层与模块文档支持,本文未实测推流)。色彩这件小事值得单说:HD 视频默认按 BT.709 转 RGB,而老版本 PyAV 的 to_ndarray('rgb24') 是一刀切 BT.601,饱和色会偏。
真实限制。 第一,编译和链接的痛,ez-ffmpeg 一点没消除------它仍通过 ffmpeg-next 链接 libav,而这恰恰是上文刚批评过的那层地基:那一层的冻结状态与 fork 链风险,它原样继承。那位在 GitHub issue 里写下 "I've spent the last 12 hours... willing to spend another week" 的开发者,换成 ez-ffmpeg 还是要过同一道链接关。它能承诺的只有后半句:完成链接后,那 100 行手写流水线无需再写。第二,上面这三个新 API 在 README 里明确标注为 experimental;刚说的 cli 门面也有自己的边界------它是可选特性(要显式开),执行路径目前只认 FFmpeg 7.1 运行时(别的版本在任何 I/O 之前就明确报错),而且是个刻意窄的子集:只有那 6 个验证过的形态能跑,两遍编码、复杂滤镜图、硬件加速都不在里面。第三,项目约有 340 star,仍处于早期阶段,且目前主要由一人维护------bus factor 是真实风险;「safe 的 Rust API」不等于「没有 C 的 CVE」,底层仍是 libav,封装层自身也有 unsafe 胶水------「safe 的皮,FFI 的骨」这句话,对它自己同样成立。第四,decord 的 GPU 批量解码、torchaudio 的多后端 DSP,ez-ffmpeg 并不宣称与之对等。
判词:会写 ffmpeg 命令、想把它搬进 Rust 进程,这是迁移成本最低的路;帧和采样要进内存、又要 FFmpeg 级格式覆盖,这也是最短的路。要纯 Rust 供应链,或者过不了链接关,请选别条。
一张总对照表
| 路线 | 运行模型 | 覆盖亮点(有据) | 关键限制(有据) | 数据点(2026-07) |
|---|---|---|---|---|
Command + ffmpeg CLI |
子进程 | 全部命令行能力;全网现成命令直接可用 | 部署带二进制;数据回内存要过 stdout/临时文件 | FFmpeg 8.0 已发布,其 Whisper 支持上过 HN 首页(1033 分) |
| ffmpeg-sidecar | 子进程 | 把视频变成 RGB 帧迭代器("as if it were an array of raw RGB frames");不链 libav | 仍是子进程;运行环境需有 ffmpeg 可执行文件 | 约 13 万下载/月 |
| ffmpeg-next | 进程内(底层绑定) | 完整 libav 控制面;生态地基 | maintenance-only;40 行起步的手写流水线;三代 fork 史 | 128 万下载/月 |
| rsmpeg | 进程内(safe 封装) | 支持 FFmpeg 6/7/8(0.18.0 含 8.0) | 节奏慢,约 11 个月无大动作 | 877 star |
| video-rs | 进程内(高层) | 十行 RGB ndarray;ndarray 编出 rainbow.mp4;seek_to_frame | 无音频 API;自述 WIP、"Use with caution";继任 rave 未就绪 | 约 3.2 万下载/月 |
| symphonia + rubato | 进程内(纯 Rust) | 解音轨 + 重采样 16 kHz;symphonium 一次调用;mutter 五行喂 whisper-rs | HE-AAC/Opus 标注未完成;AC-3 不在支持表;不做视频 | symphonia 约 107 万下载/月、0.6.0(2026-05);whisper-rs 约 17 万/月(Codeberg 维护) |
| ac-ffmpeg | 进程内 | 视频/音频/编码三域都覆盖 | 停在 packet 循环层;近 14 个月未发版 | --- |
| ez-ffmpeg | 进程内(运行时) | API 与内部调度按 FFmpeg CLI 同构建模,命令行经验一对一平移;0.15 起可把 ffmpeg 命令直接粘进去在进程内跑(cli 门面,验证子集);FrameExtractor / SampleExtractor / VideoWriter 三件一等公民 API;按色彩标签正确转 RGB |
仍链 libav,编译痛不消失;新 API 标 experimental;早期阶段 | 约 340 star |
「---」为笔者未核实的数据,本文不依赖它们成立。
地图边界说明:gstreamer-rs 也是一个真实存在的选项------月下载约 56 万、维护活跃的成熟流水线生态------但它既非 FFmpeg 系,也不是「几行代码」风格的 API,属于另一张地图,本文不展开。
差集:Python 三个库的工作,由一个进程内运行时接手
先把话说全,免得被断章取义:Rust 并非拿不到解码帧------video-rs 十行内就能迭代 RGB ndarray,ffmpeg-sidecar 更是把任意视频变成 RGB 帧迭代器(子进程方式)。真正缺的是:在一个进程内、FFmpeg 级格式覆盖的运行时里,把这件事做成调用只需一行、支持采样策略和色彩管理的 API。
所以差集长这样:Python 那边是 PyAV + decord + torchaudio 三个库;Rust 这边 video-rs 不管音频、symphonia 不管视频、sidecar 需要启动子进程------ez-ffmpeg 0.14 第一次把这三类工作放进同一个进程内 FFmpeg 运行时。
先把范围划清楚:按笔者截至 2026-07 的检索,Rust 生态中,同时把「解码 RGB 帧导出、16 kHz f32 音频导出、自产帧推入编码」做成一等公民 API、且仍在积极维护的进程内 FFmpeg 运行时,只有 ez-ffmpeg 一个:video-rs 连音频 API 都没有,自述 WIP(官方后继项目 rave 尚 not ready);ac-ffmpeg 覆盖三域,但停留在 packet 循环层、近 14 个月未发版。gstreamer-rs 的 appsink/appsrc 确实也能在进程内做到这三类事,但它是另一套流水线模型、非 FFmpeg 系------这正是前文把它划出本图的原因,此处不重复计入。
再补两句实话。其一,PyAV 本身也是 libav 的绑定库------ez-ffmpeg 用的是同一个引擎,所以这不是「Rust 重写了 PyAV」,而是同一个引擎、只是在外面套了一层 Rust 原生的高层 API,不用掉进裸 FFI,也不用起子进程。其二,为什么现在值得填补这个差集:candle、burn、ort、tract、whisper-rs 都在快速成长,这些库要的是「帧和音频变成数据」,而今天喂给它们的第一步,常常是一行 shell。生态缺口有时荒诞到什么程度?有人用 Rust 写了 decord 式的视频读取器 video_reader-rs------却只发布到 PyPI,cargo add 不到。
什么时候,答案就该是 CLI
选型文最容易犯的毛病,是把所有场景都往最新的那条路上引。下面这些场景,直接用命令行才是对的:
- 产物是文件,命令现成。 转码、切片、一次性批处理------机器学习音频圈至今仍把
ffmpeg -i in.mp4 -ar 16000 -ac 1 -c:a pcm_s16le out.wav作为第一步。如果这一行就是你的全部需求,别引入任何 crate。 - 运行环境本来就带 ffmpeg 二进制。 部署的代价已经付过一次,不必再付一遍链接的代价。
- 要的正是进程隔离。 转码任务崩溃不连累服务主进程------这是子进程模型的特性,不是缺陷(这是工程判断,非引文)。
- 团队熟悉命令行、不熟悉 libav API,而任务不在关键路径上。 迁移成本大于收益。
反过来,一旦帧或采样必须落进你的进程内存------喂模型、实时处理、和业务逻辑交织------CLI 的代价就显出来了:解析 stdout、写临时 WAV、参数容易对不上。jaredly 在 2015 年提出的变通办法沿用至今,正是这些代价积累十年的证据。
写在最后:按场景选,不按信仰选
- 产物是文件、命令现成 → CLI
- 要帧数据、不想碰 FFI 和链接 → ffmpeg-sidecar
- 要完整 libav 控制面 → ffmpeg-next (要跟 FFmpeg 大版本跟得更紧,看 rsmpeg)
- 只做视频帧、能接受 WIP → video-rs
- 只做音频、要纯 Rust、编码在支持表内 → symphonia + rubato
- 会写 ffmpeg 命令,想以最低迁移成本搬进 Rust 进程 → ez-ffmpeg(参数名、滤镜串、流选择符全是 CLI 语法)
- 帧、采样、自产帧都要进程内,且要 FFmpeg 级覆盖 → ez-ffmpeg(记住两条:链接的痛不消失;新 API 标 experimental)
「语言只是工具,还是要根据场景选用更合适的」------crate 更是如此。六条路各有赢面,谁也替代不了谁;取长补短,比站队有用。
Rust crate ez-ffmpeg 的项目地址:github.com/YeautyYE/ez-ffmpeg。