03-ffmpeg 录制与直播:录屏、摄像头与推流
📝 笔记摘要:ffmpeg 录制与直播,录屏、摄像头与推流
ffmpeg 的采集能力常被低估:录屏、录摄像头、录系统声音、推 RTMP、推 HLS、推 SRT,全是它一条命令的事。难处不在命令本身,而在每个平台的采集方式不一样,设备名也不一样——Windows 上设备名是「麦克风 (Realtek High Definition Audio)」,Linux 上是 /dev/video0,macOS 上是设备索引。
这篇按平台把采集命令拆开,再把直播协议逐个讲清楚:每种协议适合什么场景、延迟大概多少、推流命令怎么写、出了问题怎么顺着现象反查。
ffmpeg 系列 共 3 篇,这是第 3 篇
01 ffmpeg 基础 安装与工作原理
02 ffmpeg 转码与剪辑实战 容器、编码、滤镜与画质
03-ffmpeg 录制与直播 录屏、摄像头与推流 ← 本篇
1. 录屏
1.1 Windows
Windows 上有两个采集器:gdigrab 是设备,走 GDI,兼容性最好;ddagrab 是滤镜,走 DXGI,GPU 加速,更快。
# 整块屏幕
ffmpeg -f gdigrab -framerate 30 -i desktop -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
# 只录某个区域
ffmpeg -f gdigrab -framerate 30 -offset_x 100 -offset_y 100 -video_size 1280x720 -i desktop -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
# 只录某个窗口
ffmpeg -f gdigrab -i title=Calculator -c:v libx264 -pix_fmt yuv420p out.mp4
-offset_x、-offset_y、-video_size必须在-i前面
它们是 gdigrab 这个输入设备的选项
写到-i后面会被当成输出选项忽略掉,你就录到了整块屏幕
-pix_fmt yuv420p一定要加
不加的话 libx264 编出来是 yuv444p,本机能播,发到手机上黑屏
这是录屏文件「自己看没问题、别人看不了」的头号原因
窗口标题有空格要加引号
title="计算器 - 记事本"
标题对不上就录不到,窗口标题会变,录之前先看一眼标题栏
隐藏鼠标加-draw_mouse 0
-f gdigrab -draw_mouse 0 -i desktop ...
录教程的时候鼠标乱晃很影响观感
要带声音,得再加一路 dshow 输入:
# 先查设备名
ffmpeg -list_devices true -f dshow -i dummy
# 整屏 + 麦克风
ffmpeg -f gdigrab -framerate 30 -i desktop \
-f dshow -i audio="麦克风 (Realtek High Definition Audio)" \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac -b:a 128k out.mp4
设备名必须一字不差
audio="麦克风 (Realtek High Definition Audio)"括号、中文、空格都得对上
对不上就报 Cannot find audio device,复制粘贴最保险
-list_devices true -f dshow -i dummy是标准查法
那个dummy只是个占位文件名,它的作用是让 ffmpeg 打印设备列表然后退出
不要去猜设备名,机器上插了几块声卡就有几个「麦克风」,编号不一定是你想要的那个
录系统内部声音需要虚拟声卡
Windows 上 dshow 抓不到「电脑正在播放的声音」,那是系统输出环,不是输入设备
要录得装虚拟声卡(VB-CABLE 之类),让系统输出走虚拟设备,再用 dshow 录它
-b:a 128k是音频码率
128k 够人声了,音乐场景给 192k
ddagrab 是 FFmpeg 6.0 之后才有的,走 Windows 的 Desktop Duplication API,帧在显存里,抓屏几乎不占 CPU。
# 硬件编码(NVIDIA)
ffmpeg -f lavfi -i ddagrab=output_idx=0:framerate=60 -c:v h264_nvenc -preset p5 -cq 20 out.mp4
# 软件编码,要先把帧从显存拷回内存
ffmpeg -f lavfi -i ddagrab=output_idx=0:framerate=60 -vf "hwdownload,format=bgra,format=yuv420p" -c:v libx264 -preset ultrafast -crf 20 out.mp4
ddagrab 是滤镜,不是设备
ffmpeg -devices里永远看不到它,要去ffmpeg -filters找
所以要用-f lavfi -i ddagrab=...把滤镜当源用
参数名字和 gdigrab 不一样
output_idx=0第几块显示器,0 是主屏
framerate=60帧率
video_size=800x600:offset_x=100:offset_y=100区域
draw_mouse=0隐藏鼠标
它产出的是 D3D11 硬件帧
软件编码器不能直接吃,必须先hwdownload
用了 nvenc 就能跳过这一步,这也是 ddagrab 最有价值的用法
ddagrab 需要 Windows 8 以上
老系统只能退回 gdigrab
1.2 Linux
# 整块屏幕
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
# 从某个坐标开始抓
ffmpeg -f x11grab -framerate 30 -video_size 1280x720 -i :0.0+10,20 -c:v libx264 -pix_fmt yuv420p out.mp4
# 采集窗口(用 xwininfo 查窗口 id)
ffmpeg -f x11grab -framerate 30 -video_size 1280x720 -i :0.0 -window_id 0x3400007 out.mp4
:0.0是显示号.屏幕号
第一个 0 是显示器编号,第二个 0 是屏幕编号
多屏的机器上:0.1是第二块屏
没有 X11 就没有 x11grab
Wayland 下 x11grab 抓不到东西——Wayland 不允许应用随便读别的窗口内容
这是新版 Linux 发行版上最常见的「ffmpeg 录屏录出来是黑屏」原因
Wayland 上要走 PipeWire 的 portal 接口,或者干脆用 OBS 这类专门工具
无头服务器上会报DISPLAY未设置
那是正常的,x11grab 需要图形会话
远程机器想录屏,要么用 X11 转发,要么直接用采集卡的v4l2设备
音频用 PulseAudio 或 ALSA:
# 屏幕 + 系统声音
ffmpeg -f x11grab -framerate 30 -video_size 1920x1080 -i :0.0 \
-f pulse -i default -c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac out.mp4
# 直接走 ALSA
ffmpeg -f alsa -i hw:0,0 out.wav
-f pulse -i default抓的是默认输出设备
「系统声音」在 PulseAudio 里就是 default sink 的输入
pactl list sources short列所有音源,default通常指向系统回放
ALSA 的设备名是三段式
hw:<声卡号>,<设备号>,<子设备号>比如hw:0,0
cat /proc/asound/cards查声卡号,arecord -l查设备
PulseAudio 的default更省事
ALSA 绕过了混音层,只抓某一张声卡,桌面上别的声音就录不到了
1.3 macOS
# 查设备
ffmpeg -f avfoundation -list_devices true -i ""
# 录屏幕,不录声音
ffmpeg -f avfoundation -framerate 30 -i "1:none" -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
# 录屏幕 + 默认麦克风
ffmpeg -f avfoundation -framerate 30 -i "1:0" -c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac out.mp4
输入串的格式是「视频设备索引:音频设备索引」
"1:none"第 1 个视频设备,不录音频
"1:0"第 1 个视频设备 + 第 0 个音频设备
"default:none"用系统默认视频设备
none不是「静音」,是「不录这条轨」
必须先在系统设置里给终端开屏幕录制权限
系统设置 → 隐私与安全性 → 屏幕录制 → 把 Terminal / iTerm 打开
没开权限的话 ffmpeg 直接报权限错误,不是黑屏,这点比 Linux 友好
用的是系统 ScreenCaptureKit,效率比模拟抓屏高
1.4 摄像头和麦克风
# Linux 摄像头
ffmpeg -f v4l2 -video_size 1280x720 -framerate 30 -i /dev/video0 -c:v libx264 -preset ultrafast -pix_fmt yuv420p out.mp4
# Windows 摄像头
ffmpeg -f dshow -i video="HD Webcam" -c:v libx264 -pix_fmt yuv420p out.mp4
# macOS 摄像头
ffmpeg -f avfoundation -i "0:0" -c:v libx264 -pix_fmt yuv420p out.mp4
# 只要麦克风
ffmpeg -f pulse -i default -c:a aac voice.m4a
Linux 摄像头设备名就是
/dev/video0
ls /dev/video*有几个就是几个摄像头
插拔 USB 摄像头之后编号会变,别把/dev/video0写死在脚本里
摄像头原生分辨率和帧率可能不被 ffmpeg 支持
ffmpeg -f v4l2 -list_formats all -i /dev/video0先查一遍它支持哪些组合
摄像头输出常常是 yuyv422
不指定-pix_fmt yuv420p编出来的东西部分播放器不认
Windows 摄像头的设备名也要先查
ffmpeg -list_devices true -f dshow -i dummy
摄像头名字里的品牌型号必须完全对上
2. 直播
推流这条链路,ffmpeg 负责前半段:采集、编码、封装、发送。服务器只做接收和转发,它不认识你的素材,也不替你编码。
2.1 RTMP
ffmpeg -re -i in.mp4 \
-c:v libx264 -preset veryfast -tune zerolatency -b:v 2500k \
-c:a aac -b:a 128k \
-f flv rtmp://服务器:1935/live/streamkey
-re必须在-i前面
它让 ffmpeg 按原速读文件
不加它,ffmpeg 会以 CPU 能跑多快就推多快——10 分钟的文件 30 秒就推完了,直播变录播
推流地址里的路径就是「流名」,末尾那串是密钥
rtmp://服务器:1935/live/streamkey
服务器上要有live这个 app,否则推不上去
RTMP 默认不带鉴权
公网上裸推等于把流地址公开,别人拿到就能推、也能看
RTMP 端口是 1935,云服务器的安全组和防火墙都得放行
2.2 HLS
ffmpeg -re -i in.mp4 \
-c:v libx264 -preset veryfast -b:v 2500k -c:a aac -b:a 128k \
-f hls -hls_time 2 -hls_list_size 6 -hls_flags delete_segments \
-hls_segment_filename "seg_%03d.ts" out.m3u8
HLS 是把视频切成小文件,观众按顺序拉
out.m3u8是播放列表,里面列着切片的名字和时长
seg_000.ts是真正的视频切片
-hls_time 2决定切片时长,也基本决定了延迟
2 秒切片 → 延迟 4~6 秒;1 秒切片 → 延迟 2~3 秒
切片越短延迟越低,但请求次数暴增,对 CDN 不友好
-hls_list_size 6保留最近 6 个切片
-hls_flags delete_segments播完就删旧切片
不加这个参数,切片会一直堆在磁盘上直到写满
-hls_segment_filename指定切片命名规则
不写的话 ffmpeg 自己起名,但文件名不好预测,清理脚本就不好写
HLS 的最大优势是浏览器原生就能播
不需要装播放器、不需要装插件,这是它被广泛使用的原因
2.3 SRT
# 推流端:主动连出去
ffmpeg -re -i in.mp4 -c copy -f mpegts "srt://服务器:9000?mode=caller&streamid=xxx"
# 接收端:等着别人连
ffmpeg -f mpegts -i "srt://:9000?mode=listener&streamid=xxx" -c copy out.ts
SRT 走 UDP,自带重传和 FEC
丢掉的包能补回来,这是它和 RTMP 最大的区别
跨公网、跨运营商的网络抖动大,RTMP 一卡全卡,SRT 能扛住
mode=caller和mode=listener必须成对
一边主动连、一边等着,两边都写 caller 会互相等死
streamid是通道标识,一条链路上可以跑多路流,靠它区分
延迟可以做到亚秒级
?latency=120单位是毫秒,默认 120,小了容易丢包,大了延迟高
SRT 默认不加密
要加密就设 passphrase,两端必须一致
?passphrase=xxx密码至少 10 个字符
2.4 DASH 与 WHIP
# DASH:分片自适应
ffmpeg -re -i in.mp4 -c:v libx264 -b:v 2500k -f dash -seg_duration 2 -use_timeline 1 -window_size 6 out.mpd
# WHIP:WebRTC 推流(服务端要支持)
ffmpeg -re -i in.mp4 -c:v libx264 -tune zerolatency -g 60 -b:v 2500k -c:a opus -f whip http://服务器:8889/live/whip
DASH 和 HLS 是一类东西
同样是切片分发,区别是 DASH 用 mpd 描述、可以按带宽自适应码率
WHIP 是 WebRTC 的推流协议
ffmpeg 8.0 才加的 whip muxer,而且标记为实验性
它只吃 H.264(不能带 B 帧)和 Opus,别的编码得先重编
延迟能压到几百毫秒,这是 RTMP 和 HLS 都做不到的
但前提是服务端支持 WHIP 接入,不是随便一个 nginx 就能接
ffmpeg 只有推流端,没有拉流端,想收 WebRTC 得用别的工具
WHIP 是 IETF 标准(RFC 9725)
它带 Bearer token 鉴权,ffmpeg 发的就是带前缀的Bearer xxx
有些服务端只收裸 token,会拒掉
2.5 边录边推
ffmpeg -f lavfi -i ddagrab=output_idx=0:framerate=30 \
-f dshow -i audio="麦克风 (Realtek High Definition Audio)" \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p -c:a aac -b:a 128k \
-f tee "[f=flv]rtmp://服务器:1935/live/key|[f=mp4]local.mp4"
tee muxer 把同一路流分给多个出口
[f=flv]...|[f=mp4]...每个出口用方括号包起来,管道符分隔
一份编码,多路输出——推流的同时留个本地存档
它是并行的,不是串行
所有出口共享同一个编码过程,不会把 CPU 翻倍
给每个出口单独设参数
[f=flv]rtmp://...|[f=mp4]local.mp4两边都用同一份编码
想给不同出口不同画质,得靠 filter_complex 分流,写法复杂不少
2.6 推流常见问题
连不上服务器
先查端口:RTMP 是 1935,HLS 走 HTTP 是 80/443,SRT 自己定
云服务器的安全组和系统防火墙是两道关,两道都得开
报错里带Connection refused是端口没通,Connection timed out多半是被防火墙拦了
推上去了但观众看不到
服务器上没建对应的 app,SRS 里要先建http_emu或者对应的 live app
推流地址里的路径和服务器配置对不上,流被收到一个没人看的通道里
用 ffmpeg 自己验一遍:ffprobe -i rtmp://服务器:1935/live/streamkey,能读出流信息就说明推流本身没问题
延迟太高
HLS 的延迟主要由切片时长决定,-hls_time 2就是 4~6 秒延迟
GOP 也要短:-g 60(30 帧时是 2 秒一个关键帧)
加-tune zerolatency,它会砍掉 B 帧、减小缓存
画面卡顿或者画质糊
码率超过了服务器设置的上限,服务端会丢包或者直接断流
-preset太慢,编码跟不上采集速度,画面会一顿一顿
CPU 不够就换硬编,h264_nvenc或h264_qsv
💡 补充几个容易忽略的:
推流和录像是两条链路,别混参数
录像关心的是画质和体积,推流关心的是延迟和抗抖动
推流用固定码率-b:v,录像用-crf,两者目的不同
-c copy推流是最省事的选择
源文件已经是 H.264 + AAC 的时候,直接 copy 就行
省掉编码,延迟和 CPU 都最优
只有源格式不适合目标协议时才重编
ffmpeg 也能拉流
ffmpeg -i rtmp://服务器:1935/live/streamkey -c copy out.mp4
转推的活也能干:-i rtmp://源 -c copy -f flv rtmp://目标
长时间推流要盯着日志
用-progress pipe:1输出机器可读的进度,脚本里能判断是不是卡住了
磁盘满、带宽跑满、连接被重置,这三种都会让直播悄悄停掉
服务器端怎么搭
用 Docker 搭 SRS 推流服务器 —— RTMP、HLS、WebRTC 一套都齐
VSR 视频字幕 remover 部署 —— 另一条 ffmpeg 实战线
白板和系统声音在 Windows 上是老大难
需要虚拟声卡把系统输出重定向到 dshow 能抓的输入
macOS 上还要额外开屏幕录制权限
🔥 一句话总结:录屏按平台分——Windows 用
-f gdigrab(-offset_x/-offset_y/-video_size必须写在-i前),想更快用-f lavfi -i ddagrab=...(它是滤镜不是设备,产出显存帧,软编要先hwdownload);Linux 用-f x11grab -i :0.0,Wayland 下抓不到;macOS 用-f avfoundation -i "1:none",权限要在系统设置里开;设备名一律用ffmpeg -list_devices true -f dshow -i dummy查,别猜。直播四种协议:RTMP 生态最全、HLS 浏览器原生能播、SRT 跨公网抗丢包、DASH 自适应、WHIP 延迟最低但 ffmpeg 8.0 才有且是实验性;-re千万别忘,否则直播变录播,码率必须对得上服务器上限。
