4
0
0

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 推流链路图:采集设备 gdigrab dshow x11grab v4l2 采集画面,经滤镜缩放裁剪加台标,编码成 libx264 和 aac,封装成 flv mpegts hls dash,交给 rtmp srt whip 协议,送到 SRS nginx mediamtx 流媒体服务器,最后观众通过 HLS 用浏览器看或 RTMP 用客户端看;下方四张卡片分别说明 RTMP 老牌推流、HLS 切片分发、SRT 弱网抗丢、DASH 与 WHIP 新一代协议的特点,代码块给出三条推流命令,底部提示 -re 不能忘和码率要对上服务器设置

推流这条链路,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 千万别忘,否则直播变录播,码率必须对得上服务器上限。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!