1. 项目缘起与整体设计思路1.1 为什么要在树莓派上折腾推流和录音手头有一块树莓派4B4GB内存版本平时跑着一些轻量服务大部分时间处于闲置状态。某天需要把一路摄像头画面推到局域网内多台设备上观看同时还要把音频录下来存档。市面上的成品方案要么依赖云端服务要么需要额外购买专用硬件成本和灵活性都不理想。树莓派4B的性能刚好够用加上它功耗低、可以长期开机很适合做这件事。核心需求拆解下来其实就三条第一把摄像头采集到的音视频数据推送到一个流媒体服务上第二局域网内的手机、电脑、平板都能拉流观看第三推流的同时把内容录制成本地文件方便后续回看。这三条需求指向的技术方案就是Nginx 搭配 RTMP 模块再配合FFmpeg做采集和推流。RTMP 这个协议虽然年头不短但在局域网推流场景下依然非常能打。它的延迟通常在1到3秒比 HLS 低不少而且 Nginx 的 RTMP 模块配置简单、资源占用小树莓派4B跑起来毫无压力。整个方案的架构可以这样理解FFmpeg 负责从摄像头和麦克风抓取数据编码后推给 Nginx 的 RTMP 服务Nginx 接收流之后一方面对外提供拉流地址另一方面通过配置自动把流录制到指定目录。整个过程不需要额外的转码服务器树莓派自己就能闭环完成。1.2 方案选型的几个关键考量选 Nginx-RTMP 而不是其他方案主要基于几点实际考虑。资源占用方面Nginx 本身非常轻量加上 RTMP 模块后内存占用增加有限树莓派4B的4GB内存完全撑得住。相比之下如果用某些基于 Java 的流媒体服务光 JVM 就要吃掉几百兆内存对树莓派来说负担偏重。配置灵活性方面Nginx 的配置文件结构清晰RTMP 模块支持多应用、多路流、权限控制、录制规则等几乎不需要写代码就能满足大部分需求。比如我想让某一路流自动录制只需要在配置里加几行record指令就行不用去改程序逻辑。生态成熟度方面Nginx-RTMP 这套组合在流媒体领域已经用了很多年遇到问题容易找到参考资料。FFmpeg 更是音视频处理领域的瑞士军刀推流、转码、录制样样都行命令行参数虽然多但常用的就那么几个掌握之后非常高效。还有一个容易被忽略的点是硬件加速。树莓派4B的 VideoCore VI GPU 支持 H.264 硬件编码如果摄像头输出的是原始格式可以用h264_omx编码器来减轻 CPU 负担。不过实际测试下来如果摄像头本身就能输出 H.264 编码的流直接透传是最省资源的做法连编码都省了。1.3 整体架构与数据流向整个系统的数据流向可以分成三段来看。第一段是采集端USB 摄像头或者树莓派官方的 CSI 摄像头负责采集画面麦克风负责采集声音。FFmpeg 从这些设备读取数据按照指定的编码参数进行处理。第二段是服务端Nginx 监听一个 RTMP 端口接收 FFmpeg 推上来的流同时根据配置决定是否录制、录制到什么路径、用什么格式。第三段是播放端局域网内的设备通过 RTMP 地址拉流播放或者通过 Nginx 提供的 HTTP 接口访问录制好的文件。这里有个细节值得说明Nginx-RTMP 模块本身不负责转协议它只处理 RTMP。如果播放端不支持 RTMP比如某些浏览器就需要额外的 HLS 或 DASH 转换。不过对于局域网内用 VLC、PotPlayer 或者手机上的流媒体播放器来说RTMP 直接播放完全没问题。如果确实需要浏览器播放可以在 Nginx 里再配一个 HLS 输出这个后面会提到。架构确定之后接下来的工作就是具体的环境搭建和配置。树莓派4B上跑的是 64 位系统这一点很重要因为某些编码器和库在 32 位系统上会有兼容性问题。系统版本建议用较新的内核和驱动支持更完善。2. 环境准备与核心组件安装2.1 系统基础环境确认在动手之前先把树莓派的基础环境确认一遍。打开终端执行几个命令看看系统信息。uname -a查看内核版本和架构确认是aarch64还是armv7l。如果是 64 位系统会显示aarch64后续安装的软件包也要对应 64 位版本。lsb_release -a查看发行版信息确认是哪个版本的系统不同版本的软件源和包名可能有差异。内存和存储也要看一眼。free -h查看内存使用情况4GB 版本实际可用大概在 3.7GB 左右跑 Nginx 和 FFmpeg 绰绰有余。df -h查看磁盘空间录制视频比较占空间建议至少留出 10GB 以上的空闲容量。如果用的是 SD 卡写入速度可能成为瓶颈录制高码率视频时容易出现丢帧有条件的话接一个 USB 3.0 的移动硬盘或者固态硬盘会稳很多。网络方面树莓派最好用有线连接WiFi 在推流时可能因为带宽波动导致卡顿。ifconfig或者ip addr查看 IP 地址记下来后面配置和访问都要用到。如果树莓派是动态获取 IP建议在路由器里给它绑定一个固定 IP免得重启后地址变了找不到。提示树莓派的电源要选质量好的官方推荐 5V 3A。供电不足会导致 USB 设备工作不稳定摄像头掉线、录制中断很多时候都是电源的锅。2.2 安装 Nginx 与 RTMP 模块树莓派上安装 Nginx 有几种方式最简单的是用包管理器直接装但默认的 Nginx 包不带 RTMP 模块需要额外编译。另一种方式是从源码编译把 RTMP 模块一起编进去。两种方式各有优劣包管理器安装省事但功能受限源码编译灵活但耗时较长。我选择的是源码编译方式因为需要精确控制模块和版本。先安装编译依赖sudo apt update sudo apt install -y build-essential libpcre3 libpcre3-dev libssl-dev zlib1g-dev git这些依赖分别是编译器、PCRE 库用于正则、SSL 库用于 HTTPS和压缩库。装完之后下载 Nginx 源码和 RTMP 模块源码。Nginx 选一个稳定版本比如 1.24.x 系列RTMP 模块用较新的版本。cd /tmp wget http://nginx.org/download/nginx-1.24.0.tar.gz tar -zxvf nginx-1.24.0.tar.gz git clone https://github.com/arut/nginx-rtmp-module.git下载完成后进入 Nginx 源码目录配置编译参数。这里的关键是--add-module指向 RTMP 模块的路径同时开启一些必要的模块cd nginx-1.24.0 ./configure --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_flv_module \ --with-http_mp4_module \ --add-module/tmp/nginx-rtmp-module--with-http_flv_module和--with-http_mp4_module这两个模块对后续播放录制的文件很有用建议加上。配置完成后执行make编译树莓派4B编译 Nginx 大概需要几分钟耐心等待。编译完成后sudo make install安装到/usr/local/nginx目录。安装完成后验证一下/usr/local/nginx/sbin/nginx -V输出里应该能看到--add-module/tmp/nginx-rtmp-module说明 RTMP 模块已经编进去了。2.3 安装 FFmpeg 与摄像头驱动FFmpeg 的安装相对简单直接用包管理器sudo apt install -y ffmpeg装完之后ffmpeg -version确认一下。树莓派官方源里的 FFmpeg 版本可能不是最新的但常用功能都有够用了。如果需要硬件编码支持要确认编译时是否开启了--enable-omx或--enable-v4l2-m2m可以用ffmpeg -encoders | grep h264查看可用的 H.264 编码器。摄像头方面USB 摄像头一般免驱插上就能用。用ls /dev/video*查看设备节点通常会出现/dev/video0。如果是树莓派官方的 CSI 摄像头需要在系统配置里启用摄像头接口然后加载对应的驱动模块。用v4l2-ctl --list-devices可以列出所有视频设备确认摄像头被正确识别。麦克风的话USB 麦克风或者带麦克风的摄像头都会在arecord -l里列出。记下声卡的编号和设备号FFmpeg 推流时要用到。如果声音有问题可以用alsamixer调整音量和输入源。注意树莓派上同时使用多个 USB 设备时带宽可能不够。如果摄像头和麦克风都是 USB 的尽量插在不同的 USB 控制器上避免相互干扰。3. Nginx-RTMP 核心配置详解3.1 RTMP 服务基础配置Nginx 的配置文件在/usr/local/nginx/conf/nginx.confRTMP 的配置需要写在rtmp {}块里这个块和http {}块是平级的。先来看一个最基础的 RTMP 配置rtmp { server { listen 1935; chunk_size 4096; application live { live on; record off; } } }这段配置的含义是Nginx 监听 1935 端口RTMP 默认端口chunk_size设置数据块大小为 4096 字节这个值影响传输效率一般 4096 或 8192 都可以。application live定义了一个应用名叫live推流地址就是rtmp://树莓派IP:1935/live/流名称。live on表示允许直播record off表示不录制。推流地址的格式是rtmp://IP:端口/应用名/流密钥。流密钥可以随便起比如camera1、test之类的但要注意不要用特殊字符。播放地址和推流地址一样只是角色不同推流端往这个地址发数据播放端从这个地址拉数据。配置改完之后用/usr/local/nginx/sbin/nginx -t测试配置文件语法没问题再nginx -s reload重载。如果 Nginx 还没启动直接nginx启动就行。3.2 录制功能配置与参数调优录制是这次项目的核心需求之一Nginx-RTMP 模块提供了丰富的录制选项。在application块里加上录制相关的指令application live { live on; record all; record_path /mnt/recordings; record_suffix -%Y%m%d-%H%M%S.flv; record_max_size 500M; record_interval 30m; }逐条解释一下。record all表示录制所有推上来的流也可以设成record audio或record video只录其中一种。record_path指定录制文件的存放目录这个目录要提前创建好并且给 Nginx 运行用户写权限。record_suffix定义文件名后缀格式用时间戳可以避免文件重名%Y%m%d-%H%M%S会替换成年月日时分秒。record_max_size限制单个文件的最大体积超过就自动切分新文件。record_interval按时间切分比如 30 分钟一个文件。这两个参数根据实际需求选一个用就行如果既要限制大小又要限制时长两个都写也可以谁先触发就按谁切。录制格式默认是 FLV这个格式适合流式写入即使录制过程中断电已经写入的部分也能播放。如果后续需要 MP4可以用 FFmpeg 转一道或者配置 Nginx 在录制完成后自动调用脚本转码。不过 FLV 转 MP4 需要重新封装不能直接改后缀名这一点要注意。实操心得录制目录最好放在外接存储上SD 卡频繁写入容易损坏。另外要定期清理旧文件不然磁盘很快会满。可以写个定时任务删除超过一定天数的录制文件。3.3 访问控制与多应用配置如果局域网里不想让所有人都能推流可以加访问控制。RTMP 模块支持基于 IP 的允许和拒绝application live { live on; allow publish 192.168.1.0/24; deny publish all; allow play all; }这段配置表示只允许192.168.1.0/24网段的设备推流其他一律拒绝但播放不受限制。这样即使有人知道了推流地址不在指定网段也推不上去。多应用配置也很实用。比如可以开一个live应用用于直播一个record应用专门用于录制互不干扰application live { live on; record off; } application record { live on; record all; record_path /mnt/recordings; record_suffix -%Y%m%d-%H%M%S.flv; }推流到live应用只直播不录制推流到record应用则自动录制。这样灵活性更高可以根据场景选择推流地址。3.4 HLS 输出配置可选虽然 RTMP 在局域网内播放没问题但有些场景下需要浏览器直接播放这时候可以加一个 HLS 输出。HLS 基于 HTTP兼容性更好但延迟会比 RTMP 高一些通常在 5 到 10 秒。application live { live on; hls on; hls_path /tmp/hls; hls_fragment 2s; hls_playlist_length 10s; }hls_path是切片文件和 m3u8 索引的存放目录hls_fragment是每个切片的时长hls_playlist_length是播放列表的总时长。配置好之后还需要在http {}块里加一个 location 来提供 HTTP 访问location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /tmp; add_header Cache-Control no-cache; }这样就能通过http://树莓派IP/hls/流名称.m3u8来播放了。HLS 的切片文件会不断生成和删除所以hls_path放在 tmpfs 或者内存盘里可以减少 SD 卡磨损。4. FFmpeg 推流实操与参数计算4.1 摄像头采集与推流命令FFmpeg 推流的核心命令结构是输入设备参数、编码参数、输出地址。先看一个 USB 摄像头的推流示例ffmpeg -f v4l2 -input_format h264 -video_size 1280x720 -framerate 30 -i /dev/video0 \ -c:v copy -f flv rtmp://192.168.1.100:1935/live/camera1这条命令的含义是从/dev/video0读取 H.264 格式的 720p 30帧视频-c:v copy表示直接复制视频流不重新编码输出为 FLV 格式推到 RTMP 地址。-c:v copy是最省 CPU 的方式前提是摄像头输出的就是 H.264。如果摄像头输出的是 MJPEG 或 YUYV 格式就需要重新编码ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video0 \ -c:v libx264 -preset ultrafast -tune zerolatency -b:v 2000k -maxrate 2500k -bufsize 4000k \ -f flv rtmp://192.168.1.100:1935/live/camera1这里用了libx264软件编码-preset ultrafast牺牲压缩率换速度-tune zerolatency降低编码延迟。-b:v 2000k设置目标码率 2000kbps-maxrate和-bufsize控制码率波动范围。树莓派4B用 ultrafast 预设编码 720p 30帧大概占用 30% 到 50% 的 CPU具体取决于画面复杂度。如果摄像头支持硬件编码可以用h264_omxffmpeg -f v4l2 -input_format yuyv422 -video_size 1280x720 -framerate 30 -i /dev/video0 \ -c:v h264_omx -b:v 2000k -f flv rtmp://192.168.1.100:1935/live/camera1h264_omx利用树莓派的 GPU 编码CPU 占用可以降到 10% 以下。不过这个编码器的参数支持不如 libx264 丰富画质调优空间有限。4.2 音频采集与音视频同步加上音频之后命令会复杂一些。假设麦克风是hw:1,0ffmpeg -f v4l2 -input_format h264 -video_size 1280x720 -framerate 30 -i /dev/video0 \ -f alsa -ac 2 -ar 44100 -i hw:1,0 \ -c:v copy -c:a aac -b:a 128k -ar 44100 \ -f flv rtmp://192.168.1.100:1935/live/camera1-f alsa指定用 ALSA 采集音频-ac 2双声道-ar 44100采样率 44.1kHz。音频编码用 AAC码率 128kbps。音视频同步方面FFmpeg 默认会尽量保持同步但如果采集设备的时间戳有偏差可能会出现音画不同步。可以加-async 1让音频向视频对齐或者-vsync 1让视频向音频对齐。如果音视频不同步比较严重可以尝试调整-thread_queue_size参数增大队列缓冲ffmpeg -thread_queue_size 512 -f v4l2 -input_format h264 -video_size 1280x720 -framerate 30 -i /dev/video0 \ -thread_queue_size 512 -f alsa -ac 2 -ar 44100 -i hw:1,0 \ -c:v copy -c:a aac -b:a 128k \ -f flv rtmp://192.168.1.100:1935/live/camera1这个参数在树莓派上特别有用因为 USB 带宽和 CPU 调度可能导致数据到达不及时增大队列可以减少丢帧和卡顿。4.3 码率与分辨率的选择计算码率的选择直接影响到画质和网络带宽。局域网内带宽充足可以适当提高码率。720p 30帧的视频码率建议在 1500k 到 3000k 之间。1080p 的话建议 3000k 到 6000k。可以用一个简单的公式估算码率kbps≈ 分辨率宽度 × 分辨率高度 × 帧率 × 运动系数 ÷ 1000。运动系数根据画面内容取 0.05 到 0.15静态画面取小值动态画面取大值。以 1280×720、30帧、中等运动为例1280 × 720 × 30 × 0.1 ÷ 1000 ≈ 2765kbps。所以 2500k 到 3000k 是比较合理的范围。如果画面变化不大可以降到 1500k 节省带宽和存储。录制文件的大小也可以估算文件大小MB≈ 码率kbps× 时长秒÷ 8 ÷ 1000。比如 2000kbps 录制 1 小时2000 × 3600 ÷ 8 ÷ 1000 900MB。所以如果录制频繁要确保存储空间足够。注意-c:v copy模式下码率由摄像头决定不能通过 FFmpeg 参数调整。只有重新编码时才能控制码率。4.4 开机自动推流的实现手动敲命令只能临时用实际部署肯定要开机自动运行。最简单的方式是写一个 systemd 服务[Unit] DescriptionFFmpeg RTMP Stream Afternetwork.target nginx.service [Service] Typesimple Userpi ExecStart/usr/bin/ffmpeg -thread_queue_size 512 -f v4l2 -input_format h264 -video_size 1280x720 -framerate 30 -i /dev/video0 -thread_queue_size 512 -f alsa -ac 2 -ar 44100 -i hw:1,0 -c:v copy -c:a aac -b:a 128k -f flv rtmp://192.168.1.100:1935/live/camera1 Restartalways RestartSec10 [Install] WantedBymulti-user.target把这段内容保存到/etc/systemd/system/ffmpeg-stream.service然后sudo systemctl enable ffmpeg-stream和sudo systemctl start ffmpeg-stream。Restartalways保证推流中断后自动重连RestartSec10是重试间隔。如果摄像头或麦克风初始化比较慢可以在ExecStart前面加一个sleep或者写一个启动脚本在脚本里等待设备就绪后再启动 FFmpeg。另外systemd 服务默认以指定用户运行要确保该用户有访问摄像头和麦克风的权限通常需要加入video和audio用户组。5. 常见问题排查与避坑经验5.1 推流失败与连接问题排查推流失败最常见的原因是连接不上 RTMP 服务。先用netstat -tlnp | grep 1935确认 Nginx 是否在监听 1935 端口。如果没有输出说明 Nginx 没启动或者配置有问题检查nginx -t的输出。如果有监听但 FFmpeg 还是连不上检查防火墙设置树莓派默认的 iptables 规则可能挡住了 1935 端口。另一个常见问题是推流地址写错。RTMP 地址的格式是rtmp://IP:端口/应用名/流密钥应用名要和 Nginx 配置里的application名称完全一致大小写敏感。流密钥可以随便起但不能包含空格和特殊字符。如果地址没问题但还是推不上去看看 Nginx 的错误日志通常在/usr/local/nginx/logs/error.log里面会有具体的拒绝原因。还有一种情况是推流成功但播放端拉不到流。这通常是application配置里live on没开或者播放地址写错了。播放地址和推流地址格式一样但要用播放器打开。可以先用ffplay测试ffplay rtmp://192.168.1.100:1935/live/camera1如果能播放说明服务正常问题在播放器端。5.2 录制文件异常与权限问题录制功能不工作首先检查record_path目录是否存在、是否有写权限。Nginx 默认以nobody用户运行如果录制目录属于pi用户且权限是 755nobody就写不进去。解决办法是把目录所有者改成nobody或者把 Nginx 运行用户改成pi。改运行用户可以在nginx.conf顶部加一行user pi;。录制文件损坏或者无法播放可能是录制过程中断导致的。FLV 格式对断点比较友好但 MP4 如果没写完索引就无法播放。所以建议录制用 FLV需要 MP4 再转。如果录制文件大小为 0检查record指令是否写在了正确的application块里以及推流是否真的推到了这个应用。磁盘满也是常见问题。录制目录所在分区写满后Nginx 会停止录制但推流不受影响。可以写一个监控脚本定期检查磁盘使用率超过阈值就删除最旧的录制文件。或者用logrotate类似的机制管理录制文件。5.3 音视频不同步与卡顿处理音视频不同步在树莓派上比较常见原因通常是 CPU 负载过高导致音频或视频处理不及时。先用top看看 FFmpeg 的 CPU 占用如果接近 100%说明编码压力太大。解决办法有几种降低分辨率或帧率、改用硬件编码、或者用-c:v copy透传视频流。如果 CPU 占用不高但还是卡顿可能是 USB 带宽不足。树莓派的 USB 控制器共享带宽多个设备同时工作可能互相影响。把摄像头和麦克风插到不同的 USB 控制器上或者换用 CSI 摄像头不占 USB 带宽可以缓解。另外-thread_queue_size参数调大一些也有帮助比如从默认的 8 调到 512。网络问题也会导致卡顿。用iftop或者nload看看推流时的网络流量如果接近网口上限百兆网口约 12MB/s就需要降低码率。千兆网口的话一般不会是瓶颈。WiFi 推流更容易受干扰有条件尽量用有线。5.4 常见问题速查表问题现象可能原因排查方法解决方案FFmpeg 连接被拒绝Nginx 未启动或端口未监听netstat -tlnp | grep 1935启动 Nginx检查配置推流成功但无法播放应用名或流密钥错误对比推流和播放地址确保地址完全一致录制文件为空目录权限不足检查目录所有者和权限修改目录权限或 Nginx 运行用户音视频不同步CPU 过载或时间戳偏差top查看 CPU 占用降低负载加-async 1画面卡顿USB 带宽不足或网络拥塞iftop查看流量换 USB 控制器降低码率录制文件无法播放录制中断导致索引缺失检查文件大小和格式用 FLV 格式录制转码成 MP4推流自动断开网络波动或设备掉线查看 systemd 日志配置Restartalways实操心得遇到问题先看日志。Nginx 的 error.log 和 FFmpeg 的 stderr 输出包含了绝大部分问题的线索。养成看日志的习惯比盲目搜索效率高得多。5.5 性能优化与长期运行建议树莓派长期运行推流服务有几个优化点值得注意。散热方面树莓派4B发热不小长时间编码会导致降频。加一个散热片或者小风扇把温度控制在 70 度以下性能更稳定。电源方面用质量好的电源适配器避免电压波动导致 USB 设备掉线。存储方面如果录制频繁建议用外接硬盘SD 卡只放系统和配置。外接硬盘最好有独立供电避免从树莓派取电导致供电不足。日志方面Nginx 的访问日志和错误日志会不断增长配置日志轮转或者定期清理避免占满磁盘。监控方面可以写一个简单的脚本定期检查推流状态和磁盘空间异常时发送通知。比如用curl检查 HLS 的 m3u8 文件是否可访问或者用pgrep ffmpeg检查推流进程是否存活。这些自动化手段能大大减少人工维护成本。最后再分享一个小技巧如果只需要录制而不需要实时观看可以把 Nginx 的live设为off只开record。这样 Nginx 不维护播放列表资源占用更低。等录制完成后直接播放 FLV 文件即可。这个模式适合做监控录像之类的场景不需要实时看但需要完整记录。