
先说一个实际场景。上个月我接到一个临时需求机房里有台跑服务的机器没有任何桌面环境但要把它旁边摄像头的画面连同声音推到局域网里让另一间办公室的同事能直接打开看。现场没有显示器也不方便装一套带界面的推流工具我第一反应就是 ffmpeg 命令行。平时大家拿 ffmpeg 都是转格式、剪切片段但采集摄像头和麦克风再推流这件事它同样能做得非常干净而且完全可以在无界面环境里长期运行。下面直接进正题。这篇文章围绕 ffmpeg 采集摄像头和麦克风并推流这条主线覆盖设备识别、推流参数、多路输入、画质与延迟平衡、自动化调度以及我实际踩过的几个高频排错点。不管你是第一次碰命令行还是准备把摄像头直播做成无人值守服务里面应该都有可以直接抄作业的内容。1. 先让 ffmpeg 认出你的设备平台差异是第一个坑很多人第一次用 ffmpeg 采集摄像头直接照网上的命令敲大概率得到一句device not found。这不是 ffmpeg 找不到摄像头而是它默认不知道你的设备叫什么、走哪个后端。ffmpeg 在桌面系统上并没有“自动扫描所有摄像头”的能力设备名、索引、采集后端都得在命令行里写清楚。这一节的本质是解决“输入侧”的问题让 ffmpeg 知道从哪里拿视频、从哪里拿音频。按平台分开处理是最省时间的方式因为三大平台的设备命名规则完全不同。1.1 Windowsdshow 后端和设备全名的获取方式在 Windows 上ffmpeg 访问摄像头和麦克风默认通过 dshow 后端。想拿到设备列表先执行这条命令ffmpeg -f dshow -list_devices true -i dummy输出会很长但值得逐行看。你会在 “DirectShow video devices” 下面看到类似这样的字段Integrated Camera USB Video Device然后在 “DirectShow audio devices” 下面看到Microphone Array USB Audio Device这些全名是用来区分设备的。同一型号的摄像头可能被识别成很长的名字比如USB Video Device (4K)所以别自己脑补设备名一定要以 list 输出为准。后面的推流命令里要把全名连同引号一起拼进去ffmpeg -f dshow -i videoIntegrated Camera:audioMicrophone Array ...注意 Windows 下设备名里的空格和引号很容易被 PowerShell 吃掉。如果直接在 PowerShell 里跑引号转义会让你头疼很久我自己的习惯是先开一个 cmd 窗口验证命令能跑通再写脚本免得在转义上浪费几小时。另外如果有多路摄像头也可以用-f dshow -list_options true -i video设备名查看这个摄像头具体支持哪些分辨率和帧率这对后面设置采集参数很有用。1.2 Linuxv4l2 设备节点音频走 ALSA 还是 PulseAudioLinux 上视频采集没有“设备名”这一说一切归结到/dev/video0、/dev/video1这类字符设备。基础命令很直接ffmpeg -f v4l2 -i /dev/video0 ...但/dev/video0未必是摄像头有些机器装着多个采集卡设备节点顺序还经常变。想确认哪个节点对应哪个硬件优先用v4l2-ctl由 v4l2-utils 提供v4l2-ctl --list-devices输出会按硬件分组比如USB Camera (04f2:b6a8): /dev/video0 /dev/video1音频方面桌面 Linux 一般走 PulseAudio直接-f pulse -i default就能拿到系统默认麦克风。但无桌面服务器上经常没有 Pulse 服务这时用 ALSA 更直白。先用arecord -l看声卡编号然后写进命令ffmpeg -f v4l2 -framerate 25 -video_size 1280x720 -i /dev/video0 -f alsa -i hw:0,0 ...还有一个容易忽略的点如果 ffmpeg 提示打不开/dev/video0先检查当前用户是否在 video 组里。普通用户很可能没有/dev/video*的读写权限要么把用户加进 video 组要么临时用 sudo 验证。1.3 macOSavfoundation 按索引号取设备macOS 上 ffmpeg 主要用 avfoundation 后端设备是按索引编号的。列设备和 Windows、Linux 都不一样ffmpeg -f avfoundation -list_devices true -i 输出里视频设备和音频设备都带一个数字索引例如[0] FaceTime HD Camera [1] Capture screen 0 [0] Built-in Microphone使用的时候视频索引 0 写0音频索引 0 写:0冒号是音频设备的约定前缀ffmpeg -f avfoundation -framerate 30 -video_size 1280x720 -pixel_format nv12 -i 0 -f avfoundation -i :0 ...macOS 下另一个常见坑是像素格式。默认采集到的原始格式可能不是 YUV如果不显式指定-pixel_formatffmpeg 要做额外转换CPU 占用会明显抬高。1.4 获取设备列表不等于设备可用列设备列表只是一个探测过程并不代表真正打开了设备。摄像头如果被其他程序占用列表照常显示但真正采集时会报device busy。排查这类问题先把可能占用摄像头的软件全部退出再测一次。另外部分消费级摄像头只支持单路采集同一设备被两个进程同时打开也会报 busy别用两个 ffmpeg 进程去抢同一个摄像头。2. 第一路推流摄像头加麦克风一条命令推到 RTMP设备认出来之后就可以写正式的推流命令了。我以 RTMP 为例来讲因为 RTMP 的兼容性足够好无论后面接视频平台还是自建流媒体服务器基本都是一套写法。2.1 Windows 下的典型推流命令逐段拆解我实际用的是这样一条命令ffmpeg -rtbufsize 512M -f dshow -framerate 25 -video_size 1280x720 -i videoIntegrated Camera:audioMicrophone Array -c:v libx264 -preset veryfast -tune zerolatency -b:v 2500k -maxrate 2500k -bufsize 5000k -g 50 -pix_fmt yuv420p -c:a aac -b:a 128k -ar 44100 -f flv rtmp://10.0.0.8:1935/live/cam1从左到右拆开看-rtbufsize 512M是实时采集缓冲建议放在最前面缓冲越大越不容易因为瞬时编码卡顿丢数据-f dshow -framerate 25 -video_size 1280x720是输入侧的采集参数告诉 dshow 摄像头按 25 帧、720p 输出-i video...:audio...把视频设备全名和音频设备全名用冒号拼在一起然后-c:v libx264指定 H.264 软件编码-preset veryfast是速度优先的 preset比 medium 快很多适合直播-tune zerolatency告诉 x264 少缓存帧这是直播低延迟的关键参数-b:v 2500k -maxrate 2500k -bufsize 5000k把视频码率控制在 2.5Mbps 左右-g 50表示每 50 帧一个关键帧正好对应 2 秒-pix_fmt yuv420p保证播放端兼容音频走 AAC 128k最后-f flv配合 RTMP 地址输出。Linux 下的等价命令基本一样只是输入侧换成 v4l2 和 pulseffmpeg -f v4l2 -framerate 25 -video_size 1280x720 -i /dev/video0 -f pulse -i default -c:v libx264 -preset veryfast -tune zerolatency -b:v 2500k -maxrate 2500k -bufsize 5000k -g 50 -pix_fmt yuv420p -c:a aac -b:a 128k -ar 44100 -f flv rtmp://10.0.0.8:1935/live/cam12.2 为什么 RTMP 输出要用 flv 封装有没有人试过直接-f mp4推到 RTMP会报错。原因是 RTMP 协议内部的封装格式就是 FLVffmpeg 要通过-f flv告诉打包器按 FLV 规则组织数据RTMP 服务端才认。反过来如果只录本地文件用-f flv存出来的.flv文件也能直接用播放器打开。这里有一个容易混淆的点rtmp://是目标地址-f flv是封装格式两个都要写对。地址写对了但你用 mp4 封装去打 RTMP服务器照样不接。2.3 推流之后怎么验证跑起来后不要只看终端没有报错就认为成功。最稳妥的做法是在另一台机器上拉流看ffmpeg 自带 ffplay 可以快速验证ffplay -fflags nobuffer -probesize 32 -analyzeduration 0 rtmp://10.0.0.8:1935/live/cam1加那些参数是为了降低播放器启动时的缓冲延迟否则你可能等好几秒才看到画面还以为推流失败了。也可以用 ffprobe 检查流信息确认音视频轨都在ffprobe -v error -show_streams -print_format json rtmp://10.0.0.8:1935/live/cam12.4 先录本地文件再推流能省一半排查时间我给自己定的规矩是凡是新环境、新摄像头第一次接触时先录 10 秒本地文件而不是直接推流。这样可以把问题快速分成两类——本地文件都录不出来那是采集设备或者命令参数的问题本地文件正常但推流不行那才需要查网络和服务器。这条习惯帮我绕开过很多无头绪的排查。3. 多路采集双摄像头、桌面叠加、音轨混音的组织思路单路推流跑通后再往上走一步就是“不止一路”的场景。远程演示时想同时呈现两块屏幕或者两个不同角度的摄像头再比如把摄像头小窗叠到桌面上又或者几个麦克风要合成一路声音。这些都绕不开 ffmpeg 的滤镜机制准确说是filter_complex。3.1 filter_complex 的图模型输入输出怎么接ffmpeg 的filter_complex本质上是一个有向图每个输入流带编号[0:v]是第一个输入的视频[0:a]是第一个输入的音频[1:v]是第二个输入的视频以此类推。滤镜可以有多个输入和多个输出你用方括号给中间结果起名字最后用-map告诉编码器拿哪条流去编码。用大白话说就像水管系统每个水龙头有一个编号标签你决定哪些管子接进哪个混合器混合之后给新的出口贴个标签最终 ffmpeg 从指定出口取水送进编码器。这个图模型是 ffmpeg 最强大的地方也是新手最容易乱的地方。我的建议是每一步滤镜都明确命名宁可多写几个中间变量也不要一行套到底。3.2 双摄像头并排先统一分辨率再做 hstack一个实际可用的双摄并排命令如下ffmpeg -f dshow -i videoCam A:audioMic A -f dshow -i videoCam B:audioMic B -filter_complex [0:v]scale960:540,setsar1[v0];[1:v]scale960:540,setsar1[v1];[v0][v1]hstackinputs2[vout];[0:a][1:a]amixinputs2:durationlongest[aout] -map [vout] -map [aout] -c:v libx264 -preset veryfast -tune zerolatency -b:v 3500k -c:a aac -b:a 128k -f flv rtmp://10.0.0.8:1935/live/dualcam这里有两个关键点。第一两个摄像头必须缩放到相同分辨率hstack才不会因为尺寸不一致而报错第二加setsar1是修正像素宽高比不同摄像头采集出来的 SAR 可能不同不强制成 1 会出现拼接后画面变形的问题。音频上我用了amix把两路麦克风混合durationlongest表示以较长的那路为准不会因为某一路提前结束就把整个输出截断。3.3 摄像头小窗叠加到桌面overlay 的位置计算另一个常见需求是把摄像头画面叠到桌面左上角或右下角像视频会议的自视窗一样。Windows 下桌面走 gdigrab摄像头走 dshow命令大概长这样ffmpeg -f gdigrab -framerate 25 -video_size 1920x1080 -i desktop -f dshow -framerate 25 -video_size 640x480 -i videoUSB Camera:audioUSB Mic -filter_complex [1:v]scale320:240[cam];[0:v][cam]overlay20:20[vout] -map [vout] -map 1:a -c:v libx264 -preset veryfast -tune zerolatency -b:v 4000k -c:a aac -b:a 128k -f flv rtmp://10.0.0.8:1935/live/pipoverlay20:20的意思是小窗左上角位于桌面画面左上角向右 20 像素、向下 20 像素。如果想放在右下角位置就是overlayW-w-20:H-h-20其中W和H是主画面宽高w和h是叠加层宽高ffmpeg 会自动替换成实际数值。这个参数写一次就能理解。Linux 桌面场景把-f gdigrab -i desktop换成-f x11grab -i :0.0即可其余滤镜逻辑一样。3.4 音轨混音不要无脑用 amix两路麦克风直接amix很容易得到一团模糊的声音尤其是音量差异大或两路都开着扬声器时更容易混进回声。我建议先分别调节音量再混合-filter_complex [0:a]volume1.0[a0];[1:a]volume0.5[a1];[a0][a1]amixinputs2:durationlongest[aout]如果你的 ffmpeg 版本比较新amix支持normalize0参数关闭自动归一化后音量不会被偷偷压低旧版本没有这个参数就用前置volume补偿。另外amix和amerge的区别要拎清amix是多个输入混合成一条单声道或立体声amerge是把两路单声道拼成立体声的左、右声道且要求输入通道数一致不适用的场合强行用会报参数不匹配。4. 画质与延迟码率、GOP、编码器、缓冲区怎么配合推流参数不是越复杂越好。见过不少新手把码率拉得很高结果画面照样糊、播放端照样卡。画质是分辨率、码率、帧率三者权衡的结果延迟则由编码方式、GOP 大小和播放端缓冲共同决定。这一节把关键参数之间的配合关系讲明白。4.1 码率预算720p 和 1080p 大概给多少先看一个参考表具体数值不是绝对的但方向是对的场景分辨率/帧率参考码率桌面静止画面切换1280x720 251.5 ~ 2 Mbps人物访谈/轻微运动1280x720 252.5 ~ 4 Mbps摄像头运动较明显1920x1080 304 ~ 8 Mbps高动态内容1920x1080 608 ~ 12 Mbps同样的分辨率下运动量越大需要的码率越高。静止的 PPT 页面可以用很低的码率保持清晰而有人在镜头前大幅晃动时码率不足就会出现马赛克和糊块。设置-b:v的同时加上-maxrate和-bufsize可以让编码器在场景复杂度上升时不要突然飙升码率避免超过网络带宽导致丢包。4.2 GOP 与关键帧间隔-g 50的作用是每 50 帧插入一个关键帧。从播放端看关键帧是“从哪里开始解码都能恢复画面”的锚点。直播场景里观众随时可能加入、切换、网络抖动恢复如果关键帧间隔过长观众要等好几秒才能看到画面体验非常差。行业里默认的底线是 2 到 4 秒一个关键帧所以 25fps 的视频用-g 5030fps 用-g 60很常见。但关键帧也不是越短越好。关键帧本身的编码数据量远大于普通帧间隔太短会造成码率峰值波动大弱网环境反而容易卡。2 秒左右是直播比较均衡的取值。4.3 软编与硬编x264、QSV、NVENC、AMF编码器选型直接影响 CPU 占用和画质。先看你机器支持什么ffmpeg -encoders | grep 264常见选择如下编码器适用平台特点libx264所有平台软编画质好兼容性最好吃 CPUh264_qsvIntel 核显硬编省 CPU低码率下画质略逊于 x264h264_nvencNVIDIA 显卡硬编延迟低适合直播推流h264_amfAMD 显卡硬编参数写法与 nvenc 不同软编在极低码率下的画质通常比硬编好但会把 CPU 吃满。如果是无界面服务器CPU 还要跑业务服务就尽量用硬编。要注意硬编的参数不能照抄 x264 那套比如-tune zerolatency是 x264 专属nvenc 对应的是-tune ll或-preset llhqAMF 对应的是-usage lowlatency。实际选型前务必看本机ffmpeg -h encoderh264_nvenc之类的帮助输出别照搬网上旧命令。还有一个兼容性提醒很多 RTMP 服务器只接受 H.264 AAC。硬编方便不代表可以随便调成 HEVCHEVC 的流在不少老协议和播放器里根本无法解出画面。4.4 低延迟参数是组合拳推流端延迟低的完整组合大概是输入缓冲合理、编码不去攒帧、GOP 短、播放端关闭多余探测。用 x264 时-tune zerolatency已经包含了“不缓存帧”的语义如果用硬编记得用其对应的低延迟模式。播放端拉流时前面写过的-fflags nobuffer -probesize 32 -analyzeduration 0能明显缩短启动时间。延迟和抗丢包能力是一对矛盾。低延迟意味着缓冲小网络抖动时容易出现画面卡顿或断开缓冲大一点画面更稳但延迟也上去了。无人值守直播我通常会牺牲一部分延迟换取稳定性因为观众更讨厌画面反复转圈。5. 无人值守定时录像、循环推流、断线自愈很多人用 ffmpeg 推流是一次性操作但真正把它纳入生产流程时更关心能不能无人值守。我的经验是命令本身不难难在让它“断了自动重连、到点自动开始”。5.1 录像用 mkv别直接用 mp4本地录像时我推荐先存成 mkvffmpeg -f dshow -i videoIntegrated Camera:audioMicrophone Array -c:v libx264 -preset medium -crf 20 -c:a aac -t 02:00:00 -s 1280x720 record.mkv为什么不用 mp4因为 mp4 封装对中断非常敏感录制过程中 ffmpeg 被 kill 或者机器断电文件很可能完全打不开。mkv 容错性好得多即使录制中断也能恢复出大部分内容。录完之后如果非要 mp4再无损转一次封装ffmpeg -i record.mkv -c copy record.mp4录像用-crf 20而不是固定码率是因为录像看重质量一致性CRF 模式会按画面复杂度动态分配码率比 CBR 更适合存档。5.2 循环推流视频文件频道不断流维护时段或放假期间经常会用一段视频循环推流保证直播频道不掉线。命令很经典ffmpeg -re -stream_loop -1 -i demo.mp4 -c copy -f flv rtmp://10.0.0.8:1935/live/cam1两个要点-stream_loop -1必须放在-i前面表示无限循环读取输入文件-re让 ffmpeg 按原文件的时间戳速度读取否则文件会被极速推完。如果 demo.mp4 里没有音轨还需要补一路静音轨再组合否则很多服务器会抱怨“无音频流”基本做法是加一个-f lavfi -t 1 -i anullsrcr44100:clstereo输入再用-shortest让视频文件长度控制整体输出。5.3 Linux 下用 systemd 做自愈推流服务真正稳定运行的推流服务我会把它交给 systemd。先写一个启动脚本/usr/local/bin/camstream.sh内容就是推流命令然后写服务单元[Unit] DescriptionStream camera to RTMP Afternetwork-online.target [Service] ExecStart/usr/local/bin/camstream.sh Restarton-failure RestartSec10 Uservideoservice [Install] WantedBymulti-user.target这里的关键是Restarton-failure。当目标服务器暂时不可达或网络抖动导致 ffmpeg 退出时systemd 会等 10 秒后自动拉起进程等效于无限重连。用RestartSec10而不是 1是为了避免目标服务器还没恢复时疯狂重启把日志刷爆。5.4 Windows 计划任务bat 脚本加 schtasksWindows 上做不到 systemd但用任务计划也很成熟。写一个camstream.bat里面放推流命令记得把日志重定向到文件ffmpeg -rtbufsize 512M -f dshow -framerate 25 -video_size 1280x720 -i videoIntegrated Camera:audioMicrophone Array -c:v libx264 -preset veryfast -tune zerolatency -b:v 2500k -g 50 -c:a aac -b:a 128k -f flv rtmp://10.0.0.8:1935/live/cam1 C:\logs\stream.log 21然后用schtasks创建每天定时任务schtasks /create /tn CameraStream /tr C:\scripts\camstream.bat /sc daily /st 09:00 /ru SYSTEM日志重定向这个习惯很重要。没有日志半夜推流挂了第二天你只能靠猜有了日志一眼就能看出是编码器报错还是网络断开。6. 我遇到的几个高频异常排查思路比答案更重要最后把一些常见的异常直接整理成“症状—可能原因—排查链路”的格式。因为同一个错误码在不同环境里表现可能不完全一样知道怎么查比记住答案更有用。6.1 症状video device not foundWindows 上最常见。首先执行ffmpeg -f dshow -list_devices true -i dummy看看设备真实名称不要凭记忆敲。然后确认摄像头没有被其他进程占用。还有一个小坑PowerShell 的引号解析和 cmd 不一样在 PowerShell 里跑同样的命令可能报错切到 cmd 再试一次往往就好了。排查链路设备名是否精确一致是否被其他程序占用终端类型是否影响了引号传递按这个顺序大概率能定位。6.2 症状cannot set video size原因基本是摄像头不支持你指定的采集分辨率。很多低成本摄像头只支持 640x480 或 1280x720却被人一上来就指定 1920x1080。先看-f dshow -list_options true -i video设备名输出的能力列表或者先用摄像头默认参数跑一个本地录像再逐步往上加参数。如果想输出 640x480 但设备只支持 1280x720那就让设备按 1280x720 采集再用-vf scale640:480缩放到目标尺寸采集分辨率可以高于最终编码分辨率。6.3 症状real-time buffer too high 或 near high这条很常见。意思是采集端实时数据进来太快编码和写入来不及消化缓冲快爆了。根本原因通常是 CPU 不够软编 1080p 在低端机器上压力很大。缓解方式按优先级排降低分辨率或帧率改用硬编适当调大-rtbufsize。注意-rtbufsize只是增加了缓冲容量不是加快处理速度如果 CPU 已经打满调再大也只是把“快速丢帧”变成“延迟丢帧”。6.4 症状write failed: broken pipe / server closed connection推流到一半被服务器断开常见的直接原因有三种一是 RTMP 串流名冲突同一个流名同时被两个推流端连接时服务器通常会踢掉先来的那个二是编码格式不兼容服务器只接受 H.264 AAC你却推了 HEVC 或别的音频格式三是认证 token 过期或推流地址被服务端主动关闭。排查顺序建议先看服务器日志再看串流名是否唯一然后用 ffprobe 确认推上去的流编码信息是否符合目标服务器要求。6.5 把问题分层设备、输入、编码、网络四层定位我自己在排错时会把问题分成四层设备层、输入参数层、编码层、网络层。设备层问题是摄像头根本打不开输入参数层问题是分辨率、帧率、格式不匹配编码层问题是 CPU 不够或编码器参数不生效网络层问题则是推流目标不可达、防火墙挡端口、串流冲突。每次出问题先问自己现在卡在哪一层。本地录像能通基本排除前两层ffprobe 拉到流且编码正确基本排除第三层剩下就是网络和服务端的活。这套顺序看着慢实际上比在命令行里瞎试参数高效得多。我自己的习惯一直是这样无论出什么问题先录 10 秒本地文件把输入侧和输出侧隔离开再推流把网络侧盯住最后再调画质参数。希望这份笔记能让你少走几个弯路。