ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用FFmpeg在ARM小盒子上实现多路RTSP转RTMP直播推流

用FFmpeg在ARM小盒子上实现多路RTSP转RTMP直播推流

前阵子帮朋友做了一套多机位直播汇聚方案,刚需是现场五路画面要同时推到云端平台。找了一圈现成设备,要么贵得离谱,要么配置麻烦。后来我用一台 118g 的小盒子 MEDAI V2 装 FFmpeg,把五路 RTSP 拉流转成 RTMP 推流,稳定跑了两周没重启过。这篇文章就把我从原理到实操的完整过程捋一遍,包括 FFmpeg 的核心设计思路、推流参数怎么调、五路并发怎么安排、断流怎么自愈,以及我在这个过程中踩过的几个真坑。

如果你手里正好有一台类似的巴掌大 ARM 小主机,或者你想弄明白 FFmpeg 到底是怎么把视频文件变成直播流的,这篇内容应该能帮你省掉不少折腾时间。我默认你至少听过 FFmpeg 这个名字,但不需要你写过任何滤镜脚本,一切从命令行说起。

1. FFmpeg 的底层逻辑:为什么它什么都能干

1.1 一条命令背后的媒体管道:解封装、解码、滤镜、编码、再封装

FFmpeg 常被叫“瑞士军刀”,因为它把整个音视频处理拆成了几个非常清晰的阶段。当你运行一条推流命令时,数据流的走向是固定的管道链:输入协议/解封装器 → 解码器 → 滤镜链 → 编码器 → 封装器/输出协议。

我用打水的例子解释一下。解封装相当于从水井里把水提上来——输入文件可以是 MP4、FLV、TS,也可以是 RTSP 网络流,FFmpeg 先读容器格式,把里面的视频轨、音频轨分开。解码是把压缩过的 H.264/H.265 数据还原成一帧帧原始画面。这个阶段最吃 CPU,因为要处理像素级数据。滤镜是中间加工环节,比如缩放分辨率、加水印、裁切画面。编码是把原始画面重新压缩成 H.264,这一步同样吃性能。最后封装成 FLV 容器并通过 RTMP 协议推出去。

之所以说 FFmpeg 是瑞士军刀,是因为这四个阶段中的每一环你都能单独指定实现方式。源文件可能是 H.265 编码的 MP4,但你可以在解码后先缩放、再用 H.264 编码推送出去。不用写一行代码,命令行组合一下就行。

1.2 容器只是盒子,编码才是真正的压缩魔法

很多人分不清“格式”和“编码”。我反复跟朋友强调:.mp4 是容器,里面的视频可能是 H.264,也可能是 H.265,音频可能是 AAC,也可能是 MP3。容器负责把视频轨、音频轨、字幕轨按时间戳组织起来;编码格式决定画质和体积的平衡关系。

在做 RTMP 推流时,这个区分尤其关键。RTMP/FLV 对编码格式有硬约束:视频基本是 H.264,音频基本是 AAC。就算你本地有个 H.265 的 4K 视频,如果不转码直接塞进 FLV 容器推出去,很多播放器根本解不了。所以做直播汇聚,通常必须把 H.265 源先解码,再重编码成 H.264。这就解释了为什么看似简单的“格式转换”,实际跑起来 CPU 会突然飙高。

1.3 看懂命令行结构:FFmpeg 的参数顺序就是管道顺序

FFmpeg 命令行里最让人头大的是参数位置,但理解了管道模型之后完全有规律可循。基本结构是:

ffmpeg [全局参数] -i [输入] [输出参数] [输出地址]

其中,-i之前跟的是输入相关选项,比如-rtsp_transport tcp约束 RTSP 用 TCP 拉流;-i之后、输出地址之前,全部是输出相关参数,比如-c:v libx264指定视频编码器,-b:v 2000k指定码率,-f flv指定输出封装格式。

这个概念决定了你看到一条复杂推流命令时如何读懂它:从-i中间切开,左边管输入,右边管输出。如果你把-re放在输入侧,意思就是按源文件的原始帧率读取,模拟实时播放;如果放在输出侧,意思完全变了,甚至可能直接报错。

2. 硬件选型思路:118g 的小盒子为什么扛得住五路推流

2.1 带硬件编码器是分水岭:软编码和硬编码的差距

MEDAI V2 这类小盒子体积只有巴掌大,重量 118g,单看 CPU 算力肯定不如桌面级处理器。它能扛五路推流的底气,在于片上集成的硬件视频编码单元。我用一句话概括:软编码用CPU死算,硬编码用专用芯片上的DSP流水线处理。五路 720p@25fps 的视频如果全用libx264转码,SBC(单板电脑)级别的四核 CPU 会非常吃力,一路可能就要吃掉 30% 以上的 CPU。但改用硬件编码接口后,每路的 CPU 占用能压到 5% 左右,五路并发依然有富余算力给系统本身。

ARM 小盒子启用硬件编码通常有三种接口:h264_v4l2m2m(通用 Video4Linux2 Memory-to-Memory)、h264_rkmpp(瑞芯微芯片方案)、h264_omx(旧版 OpenMAX IL 接口)。具体用哪种取决于SoC型号,可以用ffmpeg -encoders | grep 264查看当前 FFmpeg 编译了哪些编码器。我个人更推荐h264_v4l2m2m,因为兼容性好,驱动稳定,出错信息更直白。

注意:硬编码虽然省 CPU,但也有代价。同等码率下画质通常不如libx264的medium/veryslow档位,运动复杂的画面容易出现轻微马赛克。不过在直播场景里,观众端看的是流畅度,加上码率给足,这个差距感知不明显。

2.2 功耗、散热与 7x24 小时运行的稳定性验证

小盒子天然适合长期挂机,但长期挂机最怕的是散热和 TF 卡/系统盘故障。很多人在台式机上跑 FFmpeg,风扇呼呼响也不在意;小盒子被动散热,环境温度超过 35℃ 就可能触发芯片降频。降频的直接后果不是死机,而是编码延迟突然从 30ms 跳到 300ms,推流间歇性卡顿。我当时做了一轮 72 小时压测,室内温度 28℃,盒子外壳微温,完全没触发降频,但为了保证余量,我在放置位置下加了一块小散热片,待机温度降了大概 5℃。

长期运行还要注意日志写入路径。FFmpeg 的日志默认打到标准输出,如果你用nohup直接重定向到一个文件,这个文件会无限增长,小则几百 MB,大则几个 GB。别等它写满存储才发现,建议配合logrotate轮转或日志里用-loglevel error只输出错误。

2.3 五路推流的带宽账本:先算再跑,别让上行链路成瓶颈

很多人忽略一个关键约束:五路推流真正卡死你的,往往不是 CPU,而是上行带宽。每路 720p 推流码率设 1.5~2 Mbps,五路就是 7.5~10 Mbps;如果五路都上 1080p 且码率 4 Mbps,总计 20 Mbps。家用宽带的典型上行是 30~50 Mbps,看起来够,但如果同一时间你还想被推流方回放观看,或者路由器 NAT 性能一般,丢包就会显现。

我的经验是先把码率定好,再反向决定分辨率档位。普通直播观看场景,720p@25fps 配 1.5 Mbps 是一个性价比很高的组合,画质足够清晰,移动端播放也不会频繁缓冲。1080p 需要至少 4 Mbps 码率才能体现“高清”的感觉,否则动态画面会糊成一片。

3. RTMP 推流的核心参数:延迟、码率与兼容性的博弈

3.1 RTMP 的江湖地位:老协议但依然是直播接入的事实标准

RTMP 是 Adobe 在 2002 年提出的流媒体协议,它基于 TCP,低延迟、穿透性好,几乎所有直播服务商和播放器都支持。虽然新一代协议(如 SRT、WebRTC)在特定场景优势明显,但你做多路直播推流时,目标平台接得最多的还是 RTMP 地址,形如rtmp://你的服务器地址:1935/live/流名。

FFmpeg 推 RTMP 的标准做法是:把编码后的音视频封装成 FLV 标签流,再通过 RTMP 的publish命令发送到服务器端。因为 RTMP 和 FLV 是一对老搭档,你在 FFmpeg 命令里常常看到-f flv与rtmp://地址连着出现。这个组合其实就是直播行业最常见的接入方式。

3.2 延迟与流畅度的平衡:preset、tune、GOP、B 帧的取舍

推流命令里最值得抠细节的是编码参数。我常用的基线命令是:

ffmpeg -i input -c:v libx264 -preset veryfast -tune zerolatency -profile:v main -level 4.1 \ -b:v 2000k -maxrate 2200k -bufsize 4000k -g 50 -keyint_min 50 -sc_threshold 0 \ -c:a aac -b:a 128k -ar 44100 -ac 2 -f flv rtmp://server/app/stream

下面逐个说为什么。

-preset veryfast是编码速度和压缩率的平衡点。越快的预设 CPU 占用越低,但同码率下画质略差。直播场景选veryfast或faster最合适,medium在某些 ARM 小盒子上会把 CPU 吃满。

-tune zerolatency让编码器尽量少做前瞻缓存,说白了就是别为了画质攒一堆帧再输出,否则延迟直线上升。

-g 50是关键帧间隔,也叫 GOP 大小。对于 25fps 的视频,50 表示每 2 秒出一个关键帧。关键帧是播放器能“随时切入”的帧,间隔越大码率越省,但观众切到直播间时黑屏等待时间更长。2 秒一个关键帧是直播行业常见的折中,既照顾秒开体验,又不会太浪费码率。

-sc_threshold 0关闭场景切换检测。如果不关,编码器遇到画面剧烈变化时会强制插入关键帧,导致码率抖动,直播中可能出现瞬时卡顿。

B 帧的坑要单独说。FLV/RTMP 容器对 B 帧是兼容的,但某些老旧播放器在遇到 B 帧时解码器会错乱。稳妥起见我会加-bf 0把 B 帧关掉,牺牲一点压缩率换取最大兼容性。

3.3 音频的隐藏坑:采样率、声道数和 A 档编码

音频在推流场景里经常被忽略,但出了问题非常恶心。先说采样率:FFmpeg 的音频重采样器默认可能输出 48000Hz,而你要推的流可能是 44100Hz 的源。虽然播放器通常能处理,但兼容性差的老设备可能出现声音变调。统一指定-ar 44100或-ar 48000,由你的源文件决定,别让 FFmpeg 猜。

声道数建议-ac 2,哪怕源是单声道也输出立体声。原因很简单:很多直播平台的音频链路对双声道兼容性最好,你做五路汇聚时,某个声道数据被平台静音的概率更低。

AAC 编码时,码率给 96k~128k 就够直播语音和一般音乐场景了。如果源文件没有音轨,务必加-an丢掉音频处理,否则 FFmpeg 会因为找不到音频流直接报错退出。多路监控摄像头常见的坑就在这,很多摄像头默认只输出视频轨。

4. 五路 RTMP 推流实战:从单路验证到并发落地

4.1 先跑通单路:写命令前先确认源流本身没问题

拿到 MEDAI V2 之后,我没有直接上五路,而是先从一路开始验证。原因是如果源头拉流就有问题,后面所有优化都是徒劳。

测试用的源头是网络摄像头 RTSP,命令是:

ffmpeg -rtsp_transport tcp -i "rtsp://192.168.1.100:554/stream1" \ -c:v copy -c:a aac -f flv "rtmp://192.168.1.5/live/cam01"

这一步我先尝试-c:v copy,意思是不重新编码,直接把源视频码流拷贝进 FLV 容器。这样做的好处是 CPU 几乎为零,而且能快速验证网络链路和 RTMP 服务器配置是否正确。如果这种模式都推不出去,说明问题不在编码,而在地址、防火墙或协议握手。

验证通过后,我再把命令升级为带转码的完整版,加-c:v libx264和码率控制参数,确认转码过程中小盒子不升温过快、延迟稳定在 1 秒以内。到这里,单路链路才算真的跑通。

4.2 多路并发脚本:进程隔离比单进程内拼 filter 靠谱

五路同时推流,直觉上可能觉得用一个 FFmpeg 进程、通过filter_complex把五路输入拼接起来效率更高。但实际踩坑后我的结论是:直接开五个独立的 FFmpeg 进程,各自处理各的流。

理由有三。第一,五路独立进程互不干扰,某一路源摄像头断流,杀掉那个进程重启就行,不影响其他四路。第二,每个进程可以用不同的输入参数和编码参数,比如某一路画面动态大,码率单独调高;另一路静态监控,可以降低码率省带宽。第三,FFmpeg 的多输入 filter 图拼接配置复杂,出错了排查成本高,而多进程方案的逻辑非常简单直观。

我当时写了一个部署脚本,大致逻辑如下:

#!/bin/bash INPUTS=( "rtsp://192.168.1.101/stream|rtmp://server/live/cam01" "rtsp://192.168.1.102/stream|rtmp://server/live/cam02" "rtsp://192.168.1.103/stream|rtmp://server/live/cam03" "rtsp://192.168.1.104/stream|rtmp://server/live/cam04" "rtsp://192.168.1.105/stream|rtmp://server/live/cam05" ) for item in "${INPUTS[@]}"; do source="${item%%|*}" target="${item##*|}" nohup ffmpeg -rtsp_transport tcp -i "$source" \ -c:v libx264 -preset veryfast -tune zerolatency -b:v 1800k \ -c:a aac -b:a 96k -ar 44100 -ac 2 \ -f flv "$target" > /tmp/ffmpeg_$(basename "$target").log 2>&1 & done

如果源本身已经是 H.264 编码,而且码率合适,你甚至可以-c:v copy做纯转发,五路全部 CPU 占用极低。但一定提前确认每一路源的编码、分辨率、码率都一致,否则推流平台上会显示差异很大的画面质量。我这个场景里各路源画质有差,所以统一走了一次转码。

4.3 开机自启与守护:systemd 把推流变成可靠服务

跑起来了还不够,最怕盒子意外重启后五路推流不恢复。把推流命令做成 systemd 服务是最干净的做法。

我为每一路推流写一个 service 文件太啰嗦,改用一条服务管理一个启动脚本,脚本内部负责拉起全部 FFmpeg 进程。systemd 单元配置如下:

[Unit] Description=RTMP Push Service After=network-online.target Wants=network-online.target [Service] Type=forking ExecStart=/usr/local/bin/rtmp_pusher.sh start ExecStop=/usr/local/bin/rtmp_pusher.sh stop Restart=on-failure RestartSec=15 User=root [Install] WantedBy=multi-user.target

Type=forking配合脚本里的 start/stop 逻辑,systemd 才能正确跟踪主进程状态。脚本的start分支负责逐路拉起 FFmpeg 并记录 PID,stop分支用pkill -f按特征参数杀掉全部相关进程。

4.4 断流自动恢复:从 FFmpeg 退出到立即重拉一场接力

摄像头偶尔断电重启,RTSP 流会瞬间断开,FFmpeg 进程也会直接退出。怎么自动恢复?我在 systemd 的Restart=on-failure之外,还给整个启动脚本加了一个逻辑:先无限 for 循环包装 ffmpeg 命令,退出后等 3 秒再重新拉流。

while true; do ffmpeg -rtsp_transport tcp -i "$source" ... -f flv "$target" echo "FFmpeg exited, restarting in 3s..." >> "$log" sleep 3 done

这样做的好处是,就算某一路摄像头暂时恢复不了,也只有这一路的日志不断刷重启记录,不会影响其他四路。坏处是日志会长,所以我的日志文件都加了轮转,并定期清理超过一定大小的日志。

另外,RTSP 拉流建议使用-rtsp_transport tcp,虽然 TCP 握手开销比 UDP 大,但稳定性和穿透性都好很多,特别是跨路由器的场景不容易丢包。

5. 常见问题与排查实录:从 CPU 飙红到音画不同步

5.1 五路硬编码一起跑,CPU 反而比单路软编还低

刚把五路推流跑起来时,我用top一看,CPU 占用居然只有 12%左右,比之前单路libx264软编还低。原因是我换了硬件编码器接口。ARM 小盒子不支持libx264高效硬编,但支持h264_v4l2m2m。命令里把-c:v libx264改成-c:v h264_v4l2m2m,CPU占用立刻降下来。

这里有个重要提醒:先确认你的 FFmpeg 是否编译了对应的硬件编码器。如果默认系统包里没有,需要重新编译 FFmpeg,加上--enable-omx --enable-v4l2-m2m或具体平台的编译参数。我当时用的就是自行编译版本,全程大概花了半小时。

5.2 画面每隔几秒卡一下:GOP 与码率控制背锅

压测时发现五路画面每隔大概 4~5 秒会集体卡顿一次,排查到编码日志后发现是码率忽高忽低。原因是-g间隔内场景快速变化触发了编码器插入关键帧,关键帧的瞬时码率远超-b:v设定值,网络发送队列瞬间堆积,导致画面卡顿。

解决方式就是我前面说的-sc_threshold 0关闭场景检测,同时把-maxrate收紧到-b:v的 1.1 倍左右。改完之后五路码率曲线平直了很多,几乎看不出周期性卡顿。

5.3 音画不同步:时间戳和缓冲区的恩怨

音频视频不同步在推流场景里常见的诱因是:音频采用默认的 AAC 编码但输入源音频采样率不统一,或者编码缓冲设置太大。我遇到的情况是一个摄像头的音频时间戳基准和视频不同,独此一路音画错位接近 500ms。

建议在命令里加上-vsync cfr强制视频恒定帧率输出,再用-af aresample=async=1:first_pts=0让音频在输入不同步时做平滑重采样,而不是简单丢帧/补帧。这套组合处理了绝大多数音画对齐问题。

5.4 快速参考:五路推流常见症状与解决对策

症状可能原因优先排查点
推流进程频繁退出RTSP 源断流 / 认证失败拉流地址能否在 VLC 正常打开
播放端一直转圈上行带宽不足检查码率总和是否逼近带宽上限
画面花屏GOP 过大或关键帧丢失降低-g数值或开启 TCP 拉流
音画不同步时间戳基准不一致单路测试判断是哪一路偏移
CPU 峰值过高软编码未启用硬编确认 ffmpeg 编码器列表有无硬编型号
长时间运行后画质劣化内存泄漏或温度降频监控内存占用和 SoC 温度

5.5 一个容易被忽略的细节:时间基准与-re的应用场景

本地文件推流时,很多人忘记加-re导致推流速度远超实时,码率瞬间冲爆主机。-re的作用是让 FFmpeg 按源文件的真实帧率读取输入,模拟实时摄像头源。但如果是实时 RTSP 拉流,不能加-re,否则拉流速度被人为限速,画面延迟越来越大。这也是我在实际项目中不止一次遇到的反向坑:有人把-re加到了 RTSP 命令里,结果运行一小时后实测延迟从 1 秒涨到了 3 分钟。

写在最后:一点个人的实操复盘

五路推流在 MEDAI V2 上稳定跑了小半个月之后,我最大的感受是:这类小型设备真正吃性能的不是 FFmpeg 本身,而是“所有环节都正确选择”的综合能力。硬件编码给你省了 CPU,但参数没调对、带宽没算清、日志没规划、开机自启没做,任何一个短板都能让整套推流像纸糊的一样散架。

我最开始也迷信过“一条命令走天下”,总想靠filter_complex把五路并到一个进程里统一管理,后来发现复杂模型的调试成本远大于收益。反而是五路独立进程、统一脚本、systemd 兜底这套“笨”方案,落地最快、故障恢复最直观。如果你也想复刻这套玩法,我建议你也从单路开始,逐步加数量,每一步都盯着 CPU、内存、延迟这三个指标看变化。这样到了五路并发时,你会很清楚自己的资源边界在哪里,而不是等盒子真的卡死了才回头找原因。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进