1. 为什么要在树莓派上折腾RTMP这套东西
手里有块树莓派4B,大多数人第一反应是拿它做下载机、做家庭网关、或者跑个轻量级服务。但如果你手头恰好有摄像头、有麦克风,又想让多个设备同时看到画面,那推流这件事就绕不开了。我最初的需求很朴素:家里有台老摄像机,想让它采集的画面能同时被客厅电视、卧室平板和手机看到,而且最好能顺手把声音录下来存档。商业方案要么按月收费,要么画质被压得惨不忍睹,于是我把目光投向了树莓派加Nginx-RTMP这套组合。
RTMP的全称是Real-Time Messaging Protocol,直译过来就是实时消息传输协议。它最早是给Flash播放器传音视频用的,虽然Flash本身已经退出历史舞台,但RTMP作为推流协议却活得好好的,原因很简单:延迟低、兼容性好、服务端实现成熟。Nginx本身是个高性能的Web服务器,加上RTMP模块之后,它就变成了一个能接收推流、分发直播、同时把流写成文件的服务端。树莓派4B的硬件配置——四核Cortex-A72、最高8GB内存、千兆网口——跑这套东西绰绰有余,功耗还低,7x24小时开着也不心疼电费。
这套方案能解决什么问题?第一,把采集端和播放端解耦。摄像头只管往树莓派推流,至于谁在看、用什么看,树莓派不关心。第二,一份流可以同时做两件事:一边分发给多个观众,一边录成文件存到硬盘上。第三,成本极低,树莓派加一张SD卡加一个摄像头,总投入可能还不到某些商业方案一个月的订阅费。适合谁来参考?如果你懂一点Linux基础命令,能照着教程敲命令,那就能跟着做下来。完全没接触过命令行的朋友,建议先补一下cd、ls、nano这几个命令的用法,不然中途容易卡住。
我实际搭这套环境的时候,前前后后折腾了大概三个晚上,踩的坑主要集中在编译参数、权限配置和录制文件的格式选择上。下面我把整个流程拆开,从系统准备到最终跑通,每一步都说明白为什么这么做,以及我踩过哪些坑。
2. 树莓派4B上的系统准备与依赖梳理
2.1 系统版本的选择与基础配置
树莓派4B支持32位和64位系统,我强烈建议用64位的Raspberry Pi OS。原因很直接:64位系统能充分利用4B的ARM Cortex-A72架构,内存寻址不受4GB限制,编译Nginx的时候速度也快不少。我最初用的是32位系统,编译Nginx-RTMP模块花了将近四十分钟,换成64位之后缩短到二十分钟左右。系统烧录用官方的Raspberry Pi Imager就行,烧录时记得在高级选项里提前配置好WiFi和SSH,省得接显示器键盘。
系统起来之后第一件事是更新软件源并升级已有包。这一步不能省,因为Nginx-RTMP的编译依赖一些开发库,老版本的库文件可能导致编译失败。命令是sudo apt update && sudo apt full-upgrade -y,执行完重启一次。重启后检查一下内核版本和架构,用uname -a看一眼,确认是aarch64就对了。
接下来要调整几个系统参数。树莓派默认的交换空间只有100MB,编译过程中容易内存不足导致进程被杀死。我把它调到2GB,编辑/etc/dphys-swapfile,把CONF_SWAPSIZE改成2048,然后执行sudo dphys-swapfile setup && sudo dphys-swapfile swapon。另外,如果你打算长时间录制,建议把文件系统扩展到整张SD卡,用raspi-config里的Expand Filesystem选项操作即可。
2.2 编译Nginx-RTMP所需的依赖清单
Nginx-RTMP模块不是独立运行的,它需要作为Nginx的一个模块编译进去。这意味着我们不能直接用apt install nginx装现成的Nginx,必须自己下载源码编译。编译之前需要安装一堆开发库,我列一下完整的依赖清单:
build-essential:提供gcc、make等编译工具libpcre3-dev:Nginx处理正则表达式需要libssl-dev:支持HTTPS和加密推流zlib1g-dev:支持gzip压缩libxml2-dev和libxslt1-dev:如果你需要XSLT模块git:用来拉取RTMP模块源码
这些库用一条命令就能装完:sudo apt install build-essential libpcre3-dev libssl-dev zlib1g-dev libxml2-dev libxslt1-dev git -y。装完之后建议用dpkg -l | grep检查一下关键库的版本,确保没有遗漏。
注意:如果你之前用apt装过Nginx,建议先卸载干净,否则编译安装后可能出现版本冲突。卸载命令是
sudo apt purge nginx nginx-common nginx-full -y && sudo apt autoremove -y。
2.3 源码下载与目录规划
我习惯把源码放在/usr/local/src下面,方便管理。先创建目录并进入:sudo mkdir -p /usr/local/src && cd /usr/local/src。然后下载Nginx源码,我选的是1.24.0稳定版,这个版本和RTMP模块的兼容性经过验证,比较稳。下载命令是sudo wget https://nginx.org/download/nginx-1.24.0.tar.gz,解压用sudo tar -zxvf nginx-1.24.0.tar.gz。
RTMP模块从代码托管平台拉取,命令是sudo git clone https://github.com/arut/nginx-rtmp-module.git。这个模块的维护者一直在更新,但主干分支偶尔会有小问题,我建议切到一个稳定的标签版本。进入模块目录后用git tag看一下有哪些版本,我选的是v1.2.2,执行git checkout v1.2.2切换过去。
目录结构规划好之后,后面编译的时候指定路径就清晰了。我一般把Nginx安装到/usr/local/nginx,日志放在/var/log/nginx,录制文件放在/mnt/record。这些路径在编译配置和后续的配置文件中都要保持一致,不然会出现找不到文件或者权限拒绝的问题。
3. 编译安装Nginx与RTMP模块的完整过程
3.1 configure参数的选择逻辑
进入Nginx源码目录后,执行./configure来生成编译配置。这一步的参数决定了哪些模块会被编译进去,以及安装路径在哪里。我用的参数如下:
sudo ./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_flv_module \ --with-http_mp4_module \ --add-module=../nginx-rtmp-module \ --with-cc-opt="-O3" \ --with-ld-opt="-Wl,-rpath,/usr/local/lib"逐个解释一下为什么这么选。--prefix指定安装目录,方便后面统一管理。--with-http_ssl_module开启HTTPS支持,虽然RTMP本身不加密,但如果你后面想用HTTP-FLV或者HLS播放,HTTPS就派上用场了。--with-http_flv_module和--with-http_mp4_module是为了支持FLV和MP4文件的伪流播放,录制完的文件可以直接通过HTTP拖进度条播放。--add-module指向RTMP模块的源码路径,注意路径是相对于当前目录的,我这里是../nginx-rtmp-module。
--with-cc-opt="-O3"是让编译器用最高级别的优化,树莓派的CPU性能有限,优化一下能提升推流时的处理效率。--with-ld-opt指定运行时库的搜索路径,避免编译出来的二进制找不到动态库。
configure执行完之后,它会输出一个摘要,告诉你哪些模块会被编译。仔细检查一下RTMP模块是否在列表里,如果没有,说明路径写错了,需要重新执行。
3.2 编译与安装中的性能调优
编译用make -j4,利用树莓派4B的四核并行编译。这个过程大概需要十五到二十分钟,取决于SD卡的速度和交换空间的使用情况。如果中途出现virtual memory exhausted之类的错误,说明内存不够,要么加大交换空间,要么用make -j2降低并行度。
编译完成后执行sudo make install,Nginx会被安装到/usr/local/nginx。安装完之后用/usr/local/nginx/sbin/nginx -V查看编译参数,确认RTMP模块已经包含在内。如果输出里有--add-module=../nginx-rtmp-module,就说明成功了。
接下来配置系统服务,让Nginx能开机自启。创建/etc/systemd/system/nginx.service文件,内容如下:
[Unit] Description=nginx with rtmp After=network.target [Service] Type=forking ExecStart=/usr/local/nginx/sbin/nginx ExecReload=/usr/local/nginx/sbin/nginx -s reload ExecStop=/usr/local/nginx/sbin/nginx -s quit PrivateTmp=true [Install] WantedBy=multi-user.target保存后执行sudo systemctl daemon-reload && sudo systemctl enable nginx。这样每次树莓派启动,Nginx就会自动运行。
3.3 验证RTMP模块是否生效
在正式配置推流之前,先验证一下RTMP模块是否真的加载了。执行/usr/local/nginx/sbin/nginx -V 2>&1 | grep rtmp,如果输出里有nginx-rtmp-module,就说明模块已经编译进去了。然后启动Nginx:sudo systemctl start nginx,用sudo systemctl status nginx查看状态,确认是active (running)。
这时候用浏览器访问树莓派的IP地址,应该能看到Nginx的默认欢迎页面。如果看不到,检查一下防火墙设置,树莓派默认没有开启ufw,但如果你之前装过,需要放行80端口:sudo ufw allow 80/tcp。
提示:RTMP默认使用1935端口,推流之前记得在防火墙里放行这个端口。如果只在局域网内使用,可以跳过防火墙配置,但如果你打算从外网推流,端口映射和安全策略需要另外考虑。
4. RTMP服务端配置:从推流接收到录制落盘
4.1 nginx.conf中RTMP块的完整写法
Nginx的主配置文件在/usr/local/nginx/conf/nginx.conf。RTMP的配置需要写在http块之外,和http块平级。我先把完整的RTMP配置块贴出来,然后逐段解释:
rtmp { server { listen 1935; chunk_size 4096; max_message 1M; application live { live on; record off; allow play all; allow publish all; } application record { live on; record all; record_path /mnt/record; record_suffix -%Y%m%d-%H%M%S.flv; record_unique on; allow play all; allow publish all; } } }listen 1935是RTMP的标准端口,chunk_size 4096表示数据块大小为4KB,这个值在局域网内比较合适,太大增加延迟,太小增加开销。max_message 1M限制单条消息最大1MB,防止异常数据撑爆内存。
application live定义了一个名为live的应用,live on表示开启直播模式,record off表示不录制。allow play all和allow publish all分别允许任何人播放和推流,局域网内用没问题,但如果暴露在公网,建议改成指定IP段。
application record是专门用来录制的应用,record all表示录制所有流,record_path指定录制文件的存放目录,record_suffix定义文件名后缀格式,这里用了时间戳,record_unique on确保每次推流生成独立的文件,不会覆盖。
4.2 录制路径的权限与存储规划
录制目录/mnt/record需要提前创建并赋予Nginx进程写入权限。Nginx默认以nobody用户运行,所以执行sudo mkdir -p /mnt/record && sudo chown nobody:nogroup /mnt/record && sudo chmod 755 /mnt/record。如果你把录制目录放在外接硬盘上,记得先挂载硬盘并设置开机自动挂载,否则重启后录制会失败。
存储规划方面,FLV格式的录制文件大小大约每小时1GB到2GB,取决于码率。树莓派4B的USB接口是USB 3.0,接一块移动硬盘能轻松应对长时间录制。我建议至少准备64GB的存储空间,并且定期清理旧文件。可以写一个简单的定时任务,每天凌晨删除七天前的录制文件:
0 3 * * * find /mnt/record -name "*.flv" -mtime +7 -delete把这个加到crontab -e里就行。注意-mtime +7表示修改时间超过7天,-delete直接删除,不经过回收站。
4.3 推流地址的构成与鉴权思路
推流地址的格式是rtmp://树莓派IP:1935/应用名/流名。比如rtmp://192.168.1.100:1935/live/camera1,其中live是应用名,camera1是流名,可以随便取,但推流端和播放端必须一致。
如果你不想让任何人都能推流,可以在application块里加推流鉴权。Nginx-RTMP模块支持on_publish回调,当有推流请求时,Nginx会向一个HTTP接口发送请求,接口返回2xx状态码才允许推流。配置写法是:
on_publish http://127.0.0.1:8080/auth;然后在树莓派上跑一个简单的HTTP服务来验证密钥。这个方案适合对安全有要求的场景,但会增加一点延迟。局域网内使用的话,allow publish all就够了。
播放地址和推流地址一样,用VLC或者ffplay就能直接播放。VLC的操作是打开网络串流,输入rtmp://192.168.1.100:1935/live/camera1,点播放就能看到画面。ffplay的命令是ffplay rtmp://192.168.1.100:1935/live/camera1,延迟大概在1到3秒之间。
5. 推流端的配置与实测效果
5.1 用ffmpeg从摄像头采集并推流
树莓派上接USB摄像头的话,用ffmpeg推流是最直接的方式。先确认摄像头被识别:ls /dev/video*,通常会出现/dev/video0。然后用v4l2-ctl查看支持的格式:v4l2-ctl --device=/dev/video0 --list-formats-ext,记下支持的分辨率和帧率。
推流命令如下:
ffmpeg -f v4l2 -input_format h264 -video_size 1280x720 -framerate 30 -i /dev/video0 \ -f alsa -i hw:1,0 \ -c:v copy -c:a aac -b:a 128k \ -f flv rtmp://192.168.1.100:1935/live/camera1-input_format h264表示摄像头直接输出H264编码,这样-c:v copy就能直接复制视频流,不用重新编码,极大降低CPU占用。音频用ALSA采集,hw:1,0是声卡设备号,用arecord -l可以查看。如果摄像头不支持H264输出,那就只能软编码,把-c:v copy换成-c:v libx264 -preset ultrafast -tune zerolatency,但树莓派4B软编码720p@30fps大概会占用60%以上的CPU。
5.2 用OBS从另一台电脑推流
如果你手头有电脑,用OBS推流更方便,可以灵活切换场景、加字幕、混音。OBS的设置里,服务选“自定义”,服务器填rtmp://192.168.1.100:1935/live,串流密钥填camera1。输出模式选高级,编码器选硬件编码(比如NVENC或者QuickSync),码率设2000到4000 Kbps,关键帧间隔设2秒。
OBS推流的好处是可以在推流端做画面合成,比如画中画、文字叠加、多摄像头切换。但缺点是电脑必须一直开着,功耗比树莓派高得多。我的做法是平时用树莓派直接推流,需要做复杂制作的时候才开电脑用OBS。
5.3 实测延迟、CPU占用与画质表现
我在局域网内实测的结果:ffmpeg直接复制H264流,720p@30fps,码率2Mbps,树莓派4B的CPU占用在15%到20%之间,内存占用不到200MB。播放端用VLC,延迟大约1.5秒;用ffplay,延迟大约1秒。录制文件用record应用生成,每个文件大约每分钟30MB,画质和推流端一致,没有二次压缩。
如果开启软编码,CPU占用会飙升到70%以上,而且画面偶尔会卡顿。所以强烈建议用支持H264硬编码的摄像头,或者用树莓派的硬件编码器。树莓派4B的VideoCore VI支持H264硬件编码,但需要通过raspivid或者libcamera来调用,ffmpeg直接调用硬件编码器需要额外配置,稍微麻烦一些。
注意:录制FLV文件的时候,如果推流中断,最后一个文件可能没有正确写入索引,导致无法播放。解决办法是用
ffmpeg -i input.flv -c copy output.mp4重新封装一下,或者直接用MP4格式录制。Nginx-RTMP模块支持record_suffix .mp4,但MP4录制在中断时更容易损坏,FLV反而更健壮。我的做法是录FLV,需要的时候再转MP4。
6. 录制文件的后续处理与回放方案
6.1 FLV转MP4的批量处理脚本
FLV文件虽然健壮,但兼容性不如MP4,很多播放器不支持直接拖进度条。我写了一个简单的脚本,每天凌晨把前一天的FLV文件转成MP4:
#!/bin/bash RECORD_DIR="/mnt/record" for file in "$RECORD_DIR"/*.flv; do if [ -f "$file" ]; then output="${file%.flv}.mp4" ffmpeg -i "$file" -c copy -movflags +faststart "$output" -y if [ $? -eq 0 ]; then rm "$file" fi fi done-c copy表示不重新编码,直接复制流,速度极快。-movflags +faststart把MP4的索引移到文件开头,方便网络播放。转换成功后删除原FLV文件,节省空间。把这个脚本保存为/usr/local/bin/convert_flv.sh,加执行权限,然后加到crontab里每天凌晨4点执行。
6.2 通过HTTP回放录制内容
Nginx本身就能提供HTTP文件服务,把录制目录映射到HTTP路径下,就能用浏览器直接播放MP4文件。在http块里加一个location:
location /record/ { alias /mnt/record/; autoindex on; autoindex_exact_size off; autoindex_localtime on; }重启Nginx后,访问http://192.168.1.100/record/就能看到文件列表,点MP4文件就能在线播放。autoindex on开启目录列表,autoindex_localtime on让文件时间显示为本地时间,方便查找。
如果你想让播放更流畅,可以加mp4模块的伪流支持。在http块里确保有mp4;指令,然后访问MP4文件时加?start=0参数就能拖进度条。不过现代浏览器对MP4的HTTP Range请求支持很好,不加参数也能拖。
6.3 存储空间监控与自动清理策略
长时间录制最怕硬盘写满,导致Nginx崩溃。我加了一个监控脚本,当磁盘使用率超过85%时,自动删除最旧的录制文件:
#!/bin/bash THRESHOLD=85 RECORD_DIR="/mnt/record" USAGE=$(df "$RECORD_DIR" | tail -1 | awk '{print $5}' | sed 's/%//') if [ "$USAGE" -gt "$THRESHOLD" ]; then find "$RECORD_DIR" -name "*.mp4" -type f -printf '%T+ %p\n' | sort | head -n 10 | awk '{print $2}' | xargs rm -f fi这个脚本每小时跑一次,每次删除最旧的10个MP4文件。-printf '%T+ %p\n'输出文件的修改时间和路径,sort按时间排序,head -n 10取最旧的10个,xargs rm -f批量删除。加到crontab里:0 * * * * /usr/local/bin/cleanup_record.sh。
7. 踩过的坑与排查思路复盘
7.1 编译时报错“SSL modules require the OpenSSL library”
这个错误出现的原因是libssl-dev没有安装,或者安装的版本和Nginx要求的版本不匹配。解决办法是先确认libssl-dev已经安装:dpkg -l | grep libssl-dev。如果没有,执行sudo apt install libssl-dev -y。如果已经安装但还是报错,可能是pkg-config找不到OpenSSL的路径,执行export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig:$PKG_CONFIG_PATH,然后重新configure。
我遇到过一次比较诡异的情况:系统里同时存在OpenSSL 1.1和3.0两个版本,Nginx默认找了1.1,但RTMP模块需要3.0的符号。解决办法是在configure时显式指定OpenSSL路径:--with-openssl=/usr/include/openssl。不过这种情况比较少见,大多数时候装好libssl-dev就能解决。
7.2 推流成功但播放端黑屏
推流端显示推流成功,但播放端一直黑屏,这种问题最让人头疼。排查思路是从推流端到服务端再到播放端,逐段确认。首先在树莓派上用ffplay rtmp://127.0.0.1:1935/live/camera1本地播放,如果本地能播,说明服务端没问题,问题出在网络或者播放端。如果本地也黑屏,说明推流数据有问题。
常见原因有几个:推流端的编码格式和RTMP不兼容,比如用了H265编码,RTMP默认只支持H264。解决办法是在ffmpeg里强制指定-c:v libx264。另一个原因是时间戳问题,ffmpeg推流时如果时间戳不连续,播放端会卡住。加-fflags +genpts让ffmpeg重新生成时间戳。还有一个原因是防火墙拦截了1935端口,用sudo netstat -tlnp | grep 1935确认Nginx在监听。
7.3 录制文件无法播放或时长异常
录制出来的FLV文件用播放器打开显示时长是0,或者直接报错无法播放。这种情况通常是推流中断导致的,FLV的元数据没有正确写入。解决办法是用ffmpeg修复:ffmpeg -i broken.flv -c copy fixed.flv。如果修复不了,试试ffmpeg -i broken.flv -c copy -flvflags no_duration_filesize fixed.flv,这个参数强制ffmpeg忽略文件里的时长信息,重新生成。
预防措施是在Nginx配置里加record_interval 30m,每30分钟自动切分文件,这样即使某个文件损坏,也只丢失30分钟的内容。另外record_unique on确保每次推流生成新文件,不会追加到旧文件上导致结构混乱。
7.4 树莓派发热降频导致推流卡顿
树莓派4B在高负载下发热明显,如果没有散热片或者风扇,CPU温度超过80度就会降频,推流画面开始卡顿。用vcgencmd measure_temp查看温度,如果经常超过75度,建议加一个散热风扇。我用的是一块铝制散热片加一个5V小风扇,温度稳定在55度左右。
软件层面也可以优化:把Nginx的worker进程数设为2而不是4,减少CPU竞争;关闭不必要的Nginx模块,减小内存占用;推流端用硬件编码而不是软编码。这些措施综合下来,树莓派4B可以稳定推流720p@30fps,连续运行一周不重启。
8. 几个提升稳定性的小配置
8.1 开启Nginx的访问日志与错误日志
默认情况下,Nginx的RTMP模块不记录推流和播放的详细日志。在rtmp块里加access_log和error_log可以方便排查问题:
rtmp { access_log /var/log/nginx/rtmp_access.log; error_log /var/log/nginx/rtmp_error.log info; server { ... } }access_log记录每次推流和播放的IP、时间、流名,error_log记录错误信息。日志文件会逐渐增大,建议加logrotate规则,每天切割一次,保留7天。
8.2 限制同时推流的客户端数量
如果不加限制,多个客户端同时往同一个流名推流会导致画面混乱。在application块里加max_connections可以限制并发连接数:
application live { live on; max_connections 10; ... }max_connections 10表示最多10个客户端同时连接,超过的会被拒绝。这个值根据你的实际需求调整,局域网内一般5到10就够了。
8.3 用systemd管理Nginx的自动重启
Nginx偶尔会因为内存不足或者其他原因崩溃,用systemd的Restart选项可以让它自动重启:
[Service] Restart=on-failure RestartSec=5sRestart=on-failure表示非正常退出时自动重启,RestartSec=5s表示重启前等待5秒。这样即使Nginx崩溃,5秒后就能恢复服务,不用手动干预。
我在实际使用中发现,树莓派4B跑这套方案最关键的其实是散热和存储。散热不好,CPU降频,推流必卡;存储满了,Nginx写不进去,直接崩溃。把这两个问题解决好,剩下的就是配置细节了。另外,如果你打算长期录制,建议用外接硬盘而不是SD卡,SD卡的写入寿命有限,频繁录制容易坏卡。我第一张SD卡就是这么报废的,换了移动硬盘之后稳定运行了半年多。