本章放在系列末尾,专门练习“把前面的知识组合起来”。案例来自日常视频处理和面试中反复出现的任务:先用 ffprobe 确认输入事实,再决定 -map、复制/转码和目标容器,最后完成结构、解码和播放验收。

命令按 Windows PowerShell 编写。input.mp4part1.mp4logo.png 和 RTSP 地址都是占位符;请替换成自己的文件。编码器、滤镜和协议以执行机器的实际构建为准:

Terminal window
ffmpeg -hide_banner -encoders 2>&1 | Select-String "libx264|libx265|aac|libopus"
ffmpeg -hide_banner -filters 2>&1 | Select-String "scale|fps|overlay|concat"

相关基础可回看:媒体模型处理链路与转码流选择与映射参数与质量FFmpeg FiltersFFprobe

先建立输入事实

不要根据后缀猜编码、音轨或时长。每个案例开始时先查一次:

Terminal window
ffprobe -v error -show_entries "format=format_name,duration,size:stream=index,codec_type,codec_name,width,height,pix_fmt,avg_frame_rate,sample_rate,channels:stream_tags=language,title" -of json input.mp4

重点看:容器 format_name、流的 index/codec_type/codec_name、视频尺寸和帧率、音频采样率和声道,以及语言标签。ffprobe 输出的是输入事实,不会替你判断“哪个流符合业务规格”。

案例一:把上传文件处理成可播放的 MP4

场景

输入是 MKV,视频和音频编码已经被目标播放器支持,只需要换容器并让 MP4 的索引位于文件开头。

命令

Terminal window
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0? -c copy -movflags +faststart web-remux.mp4

-c copy 是 Stream Copy:只重新封装压缩 Packet,不经过解码和编码,因此快且没有二次编码损失。-movflags +faststart 会把 MP4 的索引(moov)移到文件前面,便于普通文件渐进播放;它不会把 H.265 自动变成 H.264,也不等于直播协议。

如果探测结果表明编码或像素格式不适合目标播放器,再转码:

Terminal window
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:0? -c:v libx264 -pix_fmt yuv420p -crf 23 -preset medium -c:a aac -b:a 128k -movflags +faststart web-transcoded.mp4

这里视频走 Decode → Encode,音频也重新编码;yuv420p 是常见的 8-bit 兼容选择,不适用于所有 HDR/10-bit 交付。libx264 不一定存在,先检查本机编码器列表。

验收

Terminal window
ffprobe -v error -show_entries "format=format_name:stream=index,codec_type,codec_name,pix_fmt" -of json web-transcoded.mp4
ffmpeg -v error -i web-transcoded.mp4 -map 0:v? -map 0:a? -f null -
ffplay web-transcoded.mp4

只改扩展名不是转封装;Rename-Item input.mkv output.mp4 不会改变媒体字节。

案例二:在体积、质量和带宽之间取平衡

场景

上传文件过大,需要一个可复现的离线压缩起点,而不是用“固定百分比画质”。

命令

Terminal window
ffmpeg -i input.mp4 -map 0:v:0 -map 0:a:0? -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k compressed.mp4

-crf 是编码器相关的恒定质量控制:对 libx264,较小数值通常意味着更高质量和更大文件;它不是百分比,也不能跨编码器直接比较。-preset 主要改变速度与压缩效率,不是一个独立的画质等级。应使用目标样片、文件大小和播放器观察共同调参。

如果有明确的平均码率和峰值约束,可以从下面的模板开始:

Terminal window
ffmpeg -i input.mp4 -map 0:v:0 -map 0:a:0? -c:v libx264 -b:v 2500k -maxrate 3000k -bufsize 6000k -c:a aac -b:a 128k constrained.mp4

-maxrate/-bufsize 描述 VBV 约束,不保证每一秒完全相同的字节数;最终体积仍受内容、音频和容器影响。需要限制输出文件大小时,不要把 -fs 当成画质控制,它可能在任意位置截断输出。

案例三:快速切片和精确切片

快速版本:复制流

Terminal window
ffmpeg -ss 00:01:00 -i input.mp4 -t 00:00:30 -c copy fast-clip.mp4

输入前的 -ss 通常先跳到目标前的最近 seek point,复制模式会保留关键帧之前那段数据,所以切点可能不精确,但速度快、不会重新编码。

精确版本:解码后编码

Terminal window
ffmpeg -i input.mp4 -ss 00:01:00 -t 00:00:30 -c:v libx264 -c:a aac -movflags +faststart accurate-clip.mp4

输出前的 -ss 会解码并丢弃到目标时间戳之前的内容,通常更适合要求精确首帧的任务,代价是处理更慢。-t 表示持续时长;-to 表示停止位置,两者互斥且同时出现时 -t 优先,实践中只选一个。

面试追问

“为什么 -c copy 的切片开头不准?”答案不是“加一个万能参数”:压缩视频通常只能从关键帧开始安全解码,输入时间戳和封装器也会影响边界。应先说明速度/精度取舍,再选择复制或重编码,并播放检查首尾。

案例四:生成封面和抽帧目录

取单张封面

Terminal window
ffmpeg -ss 00:00:10 -i input.mp4 -frames:v 1 -q:v 2 -update 1 thumbnail.jpg

-frames:v 1 让视频只输出一帧,-update 1 明确告诉 image2 Muxer 写入单个文件;-q:v 的具体含义由 JPEG 等图像编码器决定,不是跨格式通用的“百分比质量”。若要限制封面宽度,可加 -vf "scale=640:-2",例如:

Terminal window
ffmpeg -ss 00:00:10 -i input.mp4 -frames:v 1 -vf "scale=640:-2" -q:v 2 -update 1 thumbnail-640.jpg

每 10 秒抽一帧

Terminal window
New-Item -ItemType Directory -Force .\frames | Out-Null
ffmpeg -i input.mp4 -vf "fps=1/10,scale=640:-2" .\frames\frame-%05d.jpg

fps=1/10 是滤镜阶段的时间采样;%05d 只是从 00001 开始的文件编号,不是原始时间戳。长视频要先用 -t 或业务上限控制输出数量。

案例五:多轨选择和替换音频

先选择需要的轨道

Terminal window
ffprobe -v error -show_entries "stream=index,codec_type,codec_name:stream_tags=language,title" -of json input.mkv
ffmpeg -i input.mkv -map 0:v:0 -map 0:a:1? -c copy selected.mkv

-map 决定把哪些输入流放进当前输出;写出 -map 后,该输出不再使用默认自动选流。0:a:1?? 表示第二条音频不存在时忽略它,适合结构确实允许缺失的批处理;如果业务要求必须有音频,就不要静默使用 ?

使用另一个文件的旁白

Terminal window
ffmpeg -i video.mp4 -i narration.wav -map 0:v:0 -map 1:a:0 -c:v copy -c:a aac -b:a 192k -shortest narrated.mp4

输入编号按 -i 出现顺序从 0 开始。视频没有改变,可以复制;旁白需要编码成目标容器支持的音频。-shortest 会在最短的输出流结束时停止,但不会自动修复两个输入的起点延迟、时钟或音画同步。

场景

Logo 是第二个输入,需要缩放后放到右下角。只要使用了 scaleoverlay,视频就必须经过解码、滤镜和重新编码。

命令

Terminal window
ffmpeg -i input.mp4 -loop 1 -i logo.png -filter_complex "[1:v]scale=160:-1[logo];[0:v][logo]overlay=W-w-24:H-h-24[outv]" -map "[outv]" -map 0:a:0? -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k -shortest watermarked.mp4

-loop 1 是图片输入选项,让静态 Logo 持续提供帧;[0:v][1:v] 是输入流,[logo][outv] 是滤镜图内部的接线标签。-map "[outv]" 把滤镜结果放进输出,不能写成 -c:v copy-shortest 会在映射的最短输出流结束时停止,音频长度不确定时要先确认这个取舍。

如果只需要缩放而没有第二个输入,简单滤镜就够了:

Terminal window
ffmpeg -i input.mp4 -vf "scale=w=1280:h=-2:force_original_aspect_ratio=decrease" -map 0:v:0 -map 0:a:0? -c:v libx264 -crf 23 -c:a copy resized.mkv

-vf 适合单输入单输出的 Filterchain;多输入、分支或合并才使用 -filter_complex。滤镜语法中的 , 连接同一条链,; 分隔不同链,[label] 负责接线。

案例七:拼接同规格片段

不重新编码:concat Demuxer

片段必须具有相同的流数量、编码、时间基等兼容参数。先创建清单:

Terminal window
$concatLines = @(
"ffconcat version 1.0"
"file 'part1.mp4'"
"file 'part2.mp4'"
)
[System.IO.File]::WriteAllLines((Join-Path (Get-Location) 'concat.txt'), $concatLines, [System.Text.UTF8Encoding]::new($false))
ffmpeg -f concat -safe 1 -i .\concat.txt -c copy joined.mp4

concat Demuxer 通过清单依次读取文件并调整时间戳;它适合避免重编码的同规格片段。这里用 UTF-8 无 BOM 写清单,兼容 Windows PowerShell 5.1(其 Set-Content 默认可能写成 UTF-16)。清单中的文件名是受信任的相对路径,-safe 1 保持安全默认值;不要把不受信任的清单直接交给自动化任务。

规格不同:concat Filter

下面假设两个输入都各有一条视频和一条音频:

Terminal window
ffmpeg -i part1.mp4 -i part2.mp4 -filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0]concat=n=2:v=1:a=1[outv][outa]" -map "[outv]" -map "[outa]" -c:v libx264 -crf 23 -c:a aac -b:a 128k joined-reencoded.mp4

Concat Filter 要求每段从时间戳 0 开始,并要求对应流的数量和参数可被滤镜统一;必要时先用 scalearesample 等滤镜规范化。只有视频时使用 concat=n=2:v=1:a=0,并只映射 [outv]。不同规格不能为了“能运行”而强行 -c copy

案例八:生成 HLS 点播文件

Terminal window
New-Item -ItemType Directory -Force .\hls | Out-Null
ffmpeg -i input.mp4 -map 0:v:0 -map 0:a:0? -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k -f hls -hls_time 6 -hls_playlist_type vod -hls_segment_filename ".\hls\segment-%03d.ts" .\hls\index.m3u8

-hls_time 6 是目标分片时长,不承诺每段恰好 6 秒;关键帧位置和编码设置会影响实际切点。hls_playlist_type vod 表明这是完成后不再变化的点播清单。多码率自适应时,需要为每个版本编码并用 -var_stream_map 生成多个媒体清单,文件名模式还必须包含 %v,不能只复制这一条单码率命令。

检查清单和分片是否齐全:

Terminal window
Get-Content .\hls\index.m3u8
Get-ChildItem .\hls | Select-Object Name,Length
ffprobe -v error -show_entries "format=format_name,duration" -of json .\hls\index.m3u8

m3u8 和分片还需要通过 HTTP 服务及目标播放器验证;直接双击清单不能证明浏览器链路、缓存头或跨域配置正确。

案例九:RTSP 摄像头打不开或录制异常

最小验证顺序

Terminal window
Test-NetConnection camera.example.com -Port 554
ffprobe -hide_banner -rtsp_transport tcp -i "rtsp://user:password@camera.example.com/stream"
ffmpeg -hide_banner -rtsp_transport tcp -i "rtsp://user:password@camera.example.com/stream" -t 15 -map 0:v:0 -map 0:a:0? -c copy .\rtsp-sample.mkv

第一步只验证 TCP 端口可达;第二步验证 RTSP URL、鉴权和媒体描述;第三步让 FFmpeg 实际读取并封装 15 秒。-rtsp_transport tcp 使用 RTSP 控制连接内交织的 TCP 传输,常用于排除 UDP 丢包,但不能修复错误地址、凭据、权限、摄像头拒绝或无效媒体。

若看到 Invalid data found when processing input,不要直接归因于“编码坏了”:依次检查路径/URL、DNS 和端口、鉴权、主辅码流路径、协议和服务端返回内容。若没有干净的首帧,先不要把问题归给滤镜、模型或浏览器。真实凭据不要写入文档、日志或命令历史;生产程序应使用受控配置并脱敏。

面试高频问法速答

问法关键回答命令线索
转封装和转码有什么区别?转封装只换容器、复制压缩 Packet;转码要解码再编码,能改变编码/画面但耗时并可能有损。-c copy vs -c:v/-c:a
-map-c 分别做什么?-map 选择流,-c 决定选中流复制还是编码;二者互不替代。-map 0:v:0 -map 0:a:1 -c:v ...
-ss 放哪里?输入前通常更快但复制切点受关键帧影响;输出前会解码丢弃,通常更精确。-ss ... -i vs -i ... -ss
为什么滤镜不能和 -c:v copy 一起用?滤镜处理解码后的 Frame,而 Stream Copy 不解码;要么去掉滤镜,要么重新编码该流。-vf/-filter_complex + -c:v libx264
如何证明任务成功?退出码只是进程层证据,还要检查文件、ffprobe 结构、完整解码和目标播放器。ffprobe + -f null - + ffplay
HLS 分片为何不总是 6 秒?-hls_time 是目标时长,分片通常在关键帧处切开;GOP/关键帧策略会影响边界。-hls_time 6
线上服务如何拿进度?不要解析人类日志中的 frame=;使用机器可读的 -progress,并自行管理超时、取消和子进程。-nostdin -progress pipe:1 -nostats
Non-monotonous DTS 怎么办?先保留最小复现并检查输入/拼接/切片的时间戳和容器约束,不要盲目套“修复时间戳”参数。ffprobe 的时间字段、Packet/Frame 查询

服务侧执行模板

批处理或后端调用应禁止 FFmpeg 等待标准输入,并把进度和日志分开处理:

Terminal window
ffmpeg -nostdin -n -i input.mp4 -map 0:v:0 -map 0:a:0? -c:v libx264 -crf 23 -c:a aac -b:a 128k -progress pipe:1 -nostats service-output.mp4

-progress pipe:1 输出机器可读的 key=value 进度,-nostats 关闭人类进度行,-n 避免无意覆盖已有文件。调用方仍需读取退出码和 stderr,设置超时,在取消时终止并清理子进程,最后按统一清单验收输出。

统一交付检查清单

  • 执行机器已确认 FFmpeg 版本、编码器、滤镜和协议能力;
  • 输入的容器、流、语言标签、时长和时间轴已记录;
  • -map、复制/转码、滤镜和目标容器与任务规格一致;
  • 输出文件存在且大小合理,ffprobe 结构符合预期;
  • 视频和音频完成完整解码,并在目标播放器实际播放;
  • HLS/RTSP 等网络链路已在真实客户端和足够时长下验证;
  • 自动化任务设置 -nostdin、超时、取消、覆盖策略、资源限制和凭据保护。

语义依据:FFmpeg 命令文档Stream selection 与 -map滤镜文档格式与 concat/HLSRTSP 协议文档