1. 先从一次排障说起:RTMP协议到底在忙什么
我接触RTMP协议不是在课堂上学到的,而是被一个线上事故逼着去查的。当时团队搭了一套直播点播系统,前端播放器要拉流,后端用的是一台CentOS 7服务器。别人推流推得好好的,轮到某一路信号源,播放器一直转圈,页面却不报错。抓了半天的包,最后才发现是RTMP握手阶段卡住了。从那次之后,我才认真把RTMP协议和Linux环境下的完整链路啃了一遍。
这篇文章就围绕RTMP协议在Linux下的实践展开。我会把协议原理、服务端搭建、客户端推拉流、常见坑点一次性讲透。适合刚接触流媒体、打算在Linux服务器上搭建直播系统、或者是运维和嵌入式方向想搞懂流媒体传输的朋友。读完你至少能独立搭出一套可用的RTMP服务,并且知道出问题时往哪个方向去查。
RTMP全称Real Time Messaging Protocol,最初由Macromedia设计,后来随着Flash盛行成为直播领域的事实标准。它基于TCP传输,默认端口1935。它的核心价值不是“传视频文件”,而是“边产生边传输”,让推流端和播放端保持实时性。在Linux下,我们常用的组合是Nginx配合nginx-rtmp-module,或者直接用SRS,推流工具则是FFmpeg。
很多人一听到RTMP就觉得老旧,觉得现在都HLS、WebRTC了,谁还碰RTMP?但实际情况是,大量直播系统从摄像机、编码器到云平台之间的上行链路,依然靠RTMP承担。它简单、稳定、生态成熟,尤其在Linux环境下,相关工具链非常完善。这篇文章就是要帮你把这套基于Linux的工具链实操起来。
1.1 推流和拉流:RTMP的两种基本姿势
理解RTMP,先分清两个方向。推流是数据源主动把音视频流推送到服务器,对应英文里的publish;拉流是播放端主动从服务器获取数据,对应play。RTMP服务端干的事情就是接收推流、转存储或转分发,并且允许播放端按需拉取。
在FFmpeg命令里,推流和拉流分别对应不同的参数写法。推流常见这种形式:
ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac -f flv rtmp://server/live/stream拉流则简单很多:
ffplay rtmp://server/live/stream这里有个容易混淆的点:RTMP协议传输的封装格式通常是FLV。所以推流命令里必须写-f flv,否则FFmpeg不知道以什么格式封装后扔到RTMP服务端。初次尝试的人经常会漏掉这个参数,结果是FFmpeg卡在输出格式自动探测上,或者直接报错说不认识rtmp://这种输出。
服务端那侧,要处理的是两个不同的请求:publish和play。在nginx-rtmp配置里,你可以针对不同的业务场景分别设置publish和play的权限、回调、鉴权逻辑。这一点后面实操部分会细说。
1.2 RTMP是基于TCP之上的精准拆包协议
RTMP虽然名字里有“协议”,但它其实由好几层组成。底层是TCP连接,在TCP之上,RTMP有一套自己的消息格式和分块(chunk)规则。握手阶段占用了前几次交互,接着就是AMF编码的命令交互,然后才是音视频数据。
把RTMP和HTTP做对比更容易理解。HTTP的每个请求是“一来一回”,响应完连接可能就关闭了,而RTMP则是一条长连接,推流端和服务器之间持续不断地交换数据。服务器收到的数据不是一条完整的“文件”,而是一段一段的流式字节,所以必须做分块读取、组装消息、然后交给对应的处理模块。
分块机制里有个关键点叫chunk size。默认是128字节,但发送端可以在传输过程中通过Set Chunk Size指令动态调整大小。实际推流中,为了减少TCP小包数量,FFmpeg会把chunk size调大,比如4096或者更大。你抓包时会看到RTMP的包明显比想象的大,就是因为chunk size被调整过。
如果你在Linux上抓包分析RTMP,建议先看握手是否成功。握手成功之后,如果看到连续发送connect、createStream、publish或play这几条AMF命令,说明协议交互正常。如果在connect之后就断开,多半是应用层权限或者流名称对不上,和网络无关。
1.3 2025年还在用RTMP,图什么
有人会问,RTMP延迟不算最低,默认还不支持加密,为什么直播推流还是普遍用RTMP?这里有几个很现实的原因。
第一,RTMP的推流端生态太成熟了。市面上几乎所有的硬件编码器、导播设备、手机直播App,都带RTMP推流能力。比如常见的OBS、各类无人机直播、专业摄像机内录推流,首选就是RTMP。换成SRT或者WebRTC,很多老旧设备根本没有对应模块。
第二,RTMP的链路简单,中间环节少。TCP长连接从推流端一直延伸到服务器,中间没有分段请求带来的额外开销。只要网络不丢包,延迟和稳定性都可控。HLS虽然切片容易分发,但切片会产生几秒到十几秒的延迟,不适合强交互场景。RTMP搭配FLV播放,可以在2到5秒内实现直播体验。
第三,Linux下的开源实现非常成熟。nginx-rtmp-module、SRS、MediaMTX等项目让部署和维护变得很容易。大部分Linux发行版都能直接编译安装,甚至有现成的包。这在WebRTC还没有完全简单到“一键部署”之前,是一个很大的工程优势。
2. Linux下搭建RTMP服务:两条主流路线对比
在Linux上搭建RTMP服务,绕不开两个选择:Nginx的nginx-rtmp-module和SRS。两者各有千秋,我实际体验下来,适用场景确实不太一样。
2.1 用nginx-rtmp-module快速起一个可用的服务
Nginx本身是Web服务器,nginx-rtmp-module是第三方模块,编译时以动态模块或静态模块方式加入。安装方式取决于你的Linux发行版。在Ubuntu/Debian上,可以用apt安装带扩展的nginx,但更推荐自己编译,因为这样能控制模块版本和nginx版本。
编译nginx和rtmp模块时有一个经验之谈:最好先安装好pcre、zlib、openssl开发库,否则编译过程中会报错。以Ubuntu 22.04为例,依赖包是:
apt install build-essential libpcre3-dev libssl-dev zlib1g-dev拿到nginx源码和nginx-rtmp-module源码后,执行配置:
./configure --add-module=../nginx-rtmp-module --with-http_ssl_module make -j$(nproc) make install编译完成后,在nginx.conf里加入rtmp段。这一段是核心:
rtmp { server { listen 1935; chunk_size 4096; application live { live on; record off; allow publish 127.0.0.1; allow publish 192.168.1.0/24; deny publish all; allow play all; } } }这段配置的意思是监听1935端口,chunk_size设为4096,定义一个叫live的应用。所有推流地址形如rtmp://服务器IP/live/流名。同时限制了推流来源IP,播放则全部放开。这种配置在内部测试、本机调试时非常方便,不必担心被别人乱推流把带宽占满。
nginx-rtmp的另外一个特点是它和nginx本身整合度高。可以很方便地在同一个端口上提供HTTP接口,配合hls_path和on_publish之类的回调,实现简单的控制台或回调鉴权。这对小团队、小项目来说,省去了单独搭一套流媒体服务的成本。
2.2 用SRS做更完整的流媒体服务
SRS(Simple Realtime Server)是一个专门做流媒体服务的开源项目,作者是国人,中文文档非常友好。它原生支持RTMP、SRT、WebRTC、HLS等多种协议,而且配置简单。SRS编译安装很直接:
git clone https://github.com/ossrs/srs.git cd srs/trunk ./configure make -j$(nproc)启动默认配置的话,SRS已经能跑起来,默认监听1935端口。它的配置文件是srs.conf,核心内容大概是:
listen 1935; max_connections 1000; daemon off; vhost __defaultVhost__ { rtmp { enabled on; } hls { enabled on; } }对比nginx-rtmp,SRS的配置更语义化,而且自带HLS切片能力。如果直播还要给不支持RTMP的播放器用,比如iOS上的Safari没法直接播放RTMP,就需要转成HLS,SRS一个配置就能搞定,nginx-rtmp则需要另外配HLS模块。
SRS还提供了HTTP API接口,可以查询实时连接数、流状态、踢掉指定推流端等。做运维和监控时会方便很多。我实际感受是,SRS在功能完整性上比nginx-rtmp模块高出一截,比较适合正式业务,而不只是临时测试。
2.3 到底选哪个,我给个判断标准
如果你只想在本机验证一下RTMP推拉流,或者只是给研发同学临时开一个调试服务,nginx-rtmp-module就够了。它轻量、依赖少、容易编译部署,而且所有配置集中在一个nginx.conf里,心智负担小。
如果是要上线一个正经直播系统,涉及多路流转码、回源、鉴权、监控、HLS/WebRTC多协议输出,那就直接用SRS。它这些问题都已经帮你处理好了,不需要在nginx上叠各种模块和脚本。
另外一个需要考虑的因素是团队的技术栈。如果你们团队本身就熟悉Nginx,那用nginx-rtmp扩展是最平滑的。但如果你是运维转行,没有太多Nginx配置经验,SRS的模板和控制台会更好上手。两个我都跑过,稳定性和性能在中小并发下没有本质区别,真正的分水岭是扩展性。
3. RTMP推流到拉流:完整链路实操
这一部分我会从零开始演示一条完整的链路,包括准备测试视频、用FFmpeg推流、然后用不同客户端拉流,最后解释几个影响播放体验的关键参数。所有操作都在Linux终端完成。
3.1 准备一份合适的测试视频
手边没有专业摄像机不要紧,FFmpeg可以直接生成一段合成视频。比如生成10秒的测试视频,带动态画面和正弦波音频:
ffmpeg -f lavfi -i testsrc=size=1280x720:rate=30 -f lavfi -i sine=frequency=440:sample_rate=44100 -t 10 -c:v libx264 -pix_fmt yuv420p -c:a aac test.mp4这里面testsrc是FFmpeg内置测试画面,动态变化,适合观察流畅度。音频则是一段440Hz正弦波,播放器上会有持续的提示音。生成这个文件后,推流时再配合-re参数以实时速率读取文件。
注意:生成视频时一定要指定-pix_fmt yuv420p。如果不指定,FFmpeg可能默认生成yuv444p,虽然文件能播放,但很多播放器和浏览器不兼容。这是推流过程中很常见的一个隐性错误。
如果要更真实的效果,也可以直接把电脑摄像头作为输入,但服务器上通常没有摄像头。用MP4文件模拟数据源是最稳的方式,还能反复测试断线重连、循环推流等场景。
3.2 FFmpeg推流:参数详解与实测结果
假设我们已经在服务器上启动了nginx-rtmp,应用名是live,现在把test.mp4推上去:
ffmpeg -re -stream_loop -1 -i test.mp4 -c:v libx264 -preset veryfast -tune zerolatency -c:a aac -f flv rtmp://127.0.0.1/live/test这里每个参数都有讲究:
-re:让FFmpeg按文件的原始时间戳来读取数据,而不是全速读完。没有这个参数,FFmpeg会瞬间把文件读完并快速推完,导致推流瞬间结束。-stream_loop -1:无限循环输入。配合-re可以让视频反复播放,稳定输出测试流。-preset veryfast:编码速度优先,降低编码延迟,但压缩率会差一点。直播场景一般不用slow或medium,因为直播追求低延迟,CPU开销也更大。-tune zerolatency:专门针对低延迟直播场景的x264调优项,能减少编码缓冲,但会牺牲一些画质和码率控制精度。-c:a aac:音频编码必须用AAC,RTMP默认支持AAC音频流。用MP3格式虽然FFmpeg也能封装进去,但是很多播放端已经不支持RTMP-MP3了。
推流开始后,终端会持续打印输出帧信息。看到类似time=00:00:01.23这样的输出,说明数据在实时推送。每隔一段时间还会显示码率、帧率、丢帧数等指标。如果丢帧数字一直上涨,就要检查网络带宽或编码参数是不是太狠了。
实测下来,在局域网里跑720p、30fps的流,FFmpeg CPU占用率在veryfast预设下大约10%到20%(取决于CPU型号和核心数)。如果CPU长期100%,说明编码已经跟不上实时,就会出现推流卡顿。这就是常见的“推流掉帧”问题。
3.3 拉流验证:用FFplay、VLC和网页播放器
推流成功之后,最直接的验证方式是在本机拉流:
ffplay rtmp://127.0.0.1/live/testFFplay会弹出窗口播放,同时显示音频波形等信息。如果看到测试画面和正弦波声音,链路就算通了。
如果服务器有公网IP,并且防火墙开放了1935端口,直接用VLC或者MPV也能从其他机器拉流。VLC里打开网络串流,输入rtmp://服务器IP/live/test即可。
对于浏览器播放,需要注意:现代浏览器基本都不支持RTMP协议。要让浏览器播放,必须在服务端转成HLS或者WebRTC。nginx-rtmp模块自带HLS切片功能,配置方式是在application里开启:
application live { live on; hls on; hls_path /tmp/hls; hls_fragment 2s; hls_playlist_length 6s; }这样FFmpeg推流后,服务器会生成/tmp/hls/test.m3u8和若干个.ts切片文件。浏览器端可以用hls.js或者video.js来播放。如果想让nginx提供HTTP访问切片文件,还需要在http块里配置一个静态别名:
location /hls/ { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } alias /tmp/hls/; }之后浏览器访问http://服务器IP/hls/test.m3u8就能看到流了。加这个配置时我心里一直提醒自己:nginx-rtmp模块生成的m3u8文件路径和location的alias一定要对得上,否则会404。这是新手常踩的坑。
3.4 影响播放体验的关键参数:GOP、关键帧间隔、音视频同步
很多人遇到RTMP卡顿,第一反应是调码率,但忽略了一个更基础的概念:GOP。GOP就是两个关键帧之间的间隔,通常用帧数或者秒数衡量。在H.264编码中,关键帧(I帧)携带完整的画面信息,后面的P帧和B帧都依赖前面的参考帧。如果GOP设置过大,播放器就必须等待下一个关键帧到来,才能开始解码。这会导致开播黑屏、拖动进度条后花屏、网络抖动时恢复时间太长。
在直播场景下,GOP时长一般设置为2秒到4秒。用FFmpeg推流时可以通过-g参数控制:
ffmpeg -re -stream_loop -1 -i test.mp4 -c:v libx264 -g 60 -keyint_min 60 -sc_threshold 0 ...-g 60表示每60帧一个GOP,在30fps下就是2秒一个关键帧。-keyint_min 60是最小间隔60帧,避免编码器在某些场景下主动插入过多关键帧。-sc_threshold 0是禁用场景检测,防止因为画面切换导致编码器额外生成关键帧。这样做的好处是,关键帧间隔完全可控,后续做切片、秒开和回放都很方便。
音视频同步也是一个容易忽略的问题。RTMP推流过程中,音频和视频各自有独立的时间戳。如果音频帧或视频帧的PTS(显示时间戳)不连续,播放器会猜测错误,出现音画不同步。排查方式是在FFmpeg输出信息里看A-V这一列。这个数字表示音频和视频的时间戳偏差,单位是秒。正常情况下应该稳定在0附近。如果这个值不断变大或抖动,说明编码器或时间基处理出了问题。
一个常见的音频时间戳错误是:视频源是25fps,音频采样率是44100Hz,两者之间的时间基换算不精确,导致偏差持续累积。解决办法是在推流前先用FFmpeg把输入文件的音视频时间戳归一化:
ffmpeg -re -i input.mp4 -af "aresample=async=1:first_pts=0" -vf "setpts=PTS-STARTPTS" ...这套参数的意思是把音频和视频的时间戳都从0开始对齐,并且异步重采样音频,保持连续。推流前先归一化,能省去很多音画不同步的麻烦。
4. 实操中的坑点与排查技巧
这一部分记录我在Linux下跑RTMP遇到过的典型问题。有些问题很隐蔽,搜索很久才找到答案。整理出来,希望能帮你少走弯路。
4.1 端口不通、防火墙和SELinux的连环坑
RTMP默认端口是1935,TCP协议。如果你的服务端启动后本机能连,但其他机器连不上,最先检查的就是防火墙。CentOS 7和Rocky Linux默认使用firewalld,Ubuntu使用ufw,需要分别开放端口。
如果是firewalld:
firewall-cmd --permanent --add-port=1935/tcp firewall-cmd --reload如果是ufw:
ufw allow 1935/tcp但这里还有一个坑:即使防火墙规则放行了,SELinux也可能拦截。很多腾讯云、阿里云默认开启SELinux,而它限制的不是端口,而是进程的网络行为。如果日志里没有任何拒绝信息,但外部连不上192.168.1.100:1935,检查SELinux的布尔值:
getsebool httpd_can_network_connect如果SELinux是Enforcing,nginx进程可能不被允许建立网络连接。临时设为宽松模式:
setenforce 0如果确定业务环境不需要SELinux,我建议长期关闭或者为对应二进制添加允许策略。这个坑的解释经常被忽略,因为很多教程都只说防火墙,不提SELinux。
4.2 推流卡顿、播放缓冲,先查这三个方向
推流卡顿的现场通常表现为播放器缓冲转圈,或者画面一帧一帧跳。首先确认推流端的CPU占用和日志中的丢帧情况:
- 如果FFmpeg日志里丢帧数字持续上升,说明编码速度跟不上实时,需要降低编码预设,比如从medium降到veryfast,或者降低分辨率。
- 如果丢帧为0,但播放端还是卡,检查GOP间隔。GOP过大会导致播放端开播后要等很久才能开始解码。设置
-g 60 -keyint_min 60通常能解决。 - 如果网络带宽充足但依旧卡,可能是TCP拥塞或MTU问题。简单做法是检查网卡是否启用了TCP分段卸载、GRO/LRO,这些在虚拟环境下偶尔会导致RTMP大包被拆分或丢弃。临时关闭GRO测试:
ethtool -K eth0 gro off另外,如果推流机和服务器的系统时间相差过大,会导致RTMP时间戳异常。保持NTP同步是一个好习惯,特别是在容器化环境里,容器时间漂移会造成流中断。
4.3 给RTMP加个简单的鉴权,防止被人盗流
公网RTMP服务如果不做保护,很快会被扫描到然后被人拿来推垃圾流。简单的做法是设置推流密钥,也就是在流名上做文章:rtmp://server/live/secretkey。只要密钥够复杂,暴力猜解成本就很高。但这种方式只适用于保密要求不高的场景。
更可控的是利用nginx-rtmp的on_publish回调,让服务器在收到推流请求时调用一个HTTP接口,由业务后端判断是否允许推流。配置如下:
application live { live on; on_publish http://127.0.0.1/auth/publish; }后端接口收到POST请求后,会包含name(流名)、addr(客户端IP)等参数。如果校验通过,返回HTTP 200;如果不通过,返回HTTP 403。这个回调在推流期间只会被调用一次,所以性能压力不大。拉流鉴权可以用on_play回调,类似。
这种方式还能做很多扩展,比如统计推流时长、限制单个IP推流数量、动态生成流密钥等。我在实际项目里就用这个回调做过每路流的有效期控制,到期后直接拒绝新建推流,对已有连接需要在服务端踢流的话,nginx-rtmp模块还提供了rtmp-stat模块和kill命令,SRS中也有对应的HTTP API。
4.4 常见问题速查表
整理一份我从以往的实验和团队交流中总结出来的速查表,方便你在排查时快速定位。
| 现象 | 可能原因 | 快速排查/解决 |
|---|---|---|
| 推流命令报connection refused | 服务没启动或端口没监听 | ss -lntp | grep 1935 |
| 本机能推,外部连不上 | 防火墙或SELinux | 放行1935/tcp,检查SELinux状态 |
| FFmpeg推流后秒断,服务端日志无记录 | 推流密钥错误或应用名不对 | 对比URL中live后面的流名和配置中的application |
| 播放器能打开但黑屏 | GOP过大或关键帧间隔太长 | 设置-g 60 -keyint_min 60 |
| 音画不同步 | 输入源时间戳不连续 | 推流前归一化时间戳,setpts和aresample |
| CPU占用100%导致掉帧 | 编码预设太慢 | 改用preset veryfast,降低分辨率或帧率 |
| HLS播放404 | m3u8路径与location不匹配 | 检查hls_path与alias是否对应 |
| 推流过程中随机断开 | TCP超时或防火墙会话状态 | 调整防火墙会话超时,检查网络稳定性 |
| 连接正常但无画面 | FLV封装缺失或编码不兼容 | 推流命令加-f flv,视频编码用libx264,音频用aac |
这张表里的问题我都实际遇到或复现过,不算全面,但能覆盖八成以上的RTMP新手现场。
4.5 几个小习惯,能让你少熬夜
第一,多抓包。在Linux服务器上抓RTMP包最简单的方式是用tcpdump:
tcpdump -i any port 1935 -w rtmp.pcap然后用Wireshark打开,如果不想装图形界面,也可以直接把pcap文件拉到本地再分析。看到握手、connect、createStream这个过程,基本就知道链路是否已经走通。协议层的排障比猜日志有效得多。
第二,多用短流名调试。一长串带时间戳、带签名的流名虽然安全,但在排查时不利于观察。调试阶段用stream1、test这样短名字,等验证通过再改签名逻辑,可以降低排障成本。
第三,别小看服务器时间。RTMP协议中的时间戳以推流端的时钟为基准,但如果服务器和推流端时钟不同步,后续做录像回放、截图、鉴权签名时都会出问题。我现在有习惯,凡是流媒体服务器一律加入NTP维护时间,这是少有的能提前避免一堆坑的准备工作。
最后再分享一个细节:如果在nginx-rtmp中使用record功能做直播录制,要小心磁盘空间。录制生成的flv文件会持续增长,单路高清流一天可能占几十GB。我在某次压测中就是因为忘了清理,直接把一块500G的磁盘写满了。后来加了日志轮转和定时清理脚本,才踏实下来。
目前这套基于Linux、RTMP、FFmpeg和Nginx的链路,已经是我们团队日常开发和排查流媒体问题的标配。希望这篇文章能帮你把RTMP从“听说过”变成“能上手”,下次遇到直播相关的问题时,心里有底,动手不慌。