本章放在系列末尾,专门练习“把前面的知识组合起来”。案例来自日常视频处理和面试中反复出现的任务:先用 ffprobe 确认输入事实,再决定 -map、复制/转码和目标容器,最后完成结构、解码和播放验收。
命令按 Windows PowerShell 编写。input.mp4、part1.mp4、logo.png 和 RTSP 地址都是占位符;请替换成自己的文件。编码器、滤镜和协议以执行机器的实际构建为准:
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 Filters 和 FFprobe。
先建立输入事实
不要根据后缀猜编码、音轨或时长。每个案例开始时先查一次:
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 的索引位于文件开头。
命令
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,也不等于直播协议。
如果探测结果表明编码或像素格式不适合目标播放器,再转码:
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 不一定存在,先检查本机编码器列表。
验收
ffprobe -v error -show_entries "format=format_name:stream=index,codec_type,codec_name,pix_fmt" -of json web-transcoded.mp4ffmpeg -v error -i web-transcoded.mp4 -map 0:v? -map 0:a? -f null -ffplay web-transcoded.mp4只改扩展名不是转封装;Rename-Item input.mkv output.mp4 不会改变媒体字节。
案例二:在体积、质量和带宽之间取平衡
场景
上传文件过大,需要一个可复现的离线压缩起点,而不是用“固定百分比画质”。
命令
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 主要改变速度与压缩效率,不是一个独立的画质等级。应使用目标样片、文件大小和播放器观察共同调参。
如果有明确的平均码率和峰值约束,可以从下面的模板开始:
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 当成画质控制,它可能在任意位置截断输出。
案例三:快速切片和精确切片
快速版本:复制流
ffmpeg -ss 00:01:00 -i input.mp4 -t 00:00:30 -c copy fast-clip.mp4输入前的 -ss 通常先跳到目标前的最近 seek point,复制模式会保留关键帧之前那段数据,所以切点可能不精确,但速度快、不会重新编码。
精确版本:解码后编码
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 的切片开头不准?”答案不是“加一个万能参数”:压缩视频通常只能从关键帧开始安全解码,输入时间戳和封装器也会影响边界。应先说明速度/精度取舍,再选择复制或重编码,并播放检查首尾。
案例四:生成封面和抽帧目录
取单张封面
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",例如:
ffmpeg -ss 00:00:10 -i input.mp4 -frames:v 1 -vf "scale=640:-2" -q:v 2 -update 1 thumbnail-640.jpg每 10 秒抽一帧
New-Item -ItemType Directory -Force .\frames | Out-Nullffmpeg -i input.mp4 -vf "fps=1/10,scale=640:-2" .\frames\frame-%05d.jpgfps=1/10 是滤镜阶段的时间采样;%05d 只是从 00001 开始的文件编号,不是原始时间戳。长视频要先用 -t 或业务上限控制输出数量。
案例五:多轨选择和替换音频
先选择需要的轨道
ffprobe -v error -show_entries "stream=index,codec_type,codec_name:stream_tags=language,title" -of json input.mkvffmpeg -i input.mkv -map 0:v:0 -map 0:a:1? -c copy selected.mkv-map 决定把哪些输入流放进当前输出;写出 -map 后,该输出不再使用默认自动选流。0:a:1? 的 ? 表示第二条音频不存在时忽略它,适合结构确实允许缺失的批处理;如果业务要求必须有音频,就不要静默使用 ?。
使用另一个文件的旁白
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
场景
Logo 是第二个输入,需要缩放后放到右下角。只要使用了 scale 或 overlay,视频就必须经过解码、滤镜和重新编码。
命令
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 会在映射的最短输出流结束时停止,音频长度不确定时要先确认这个取舍。
如果只需要缩放而没有第二个输入,简单滤镜就够了:
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
片段必须具有相同的流数量、编码、时间基等兼容参数。先创建清单:
$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.mp4concat Demuxer 通过清单依次读取文件并调整时间戳;它适合避免重编码的同规格片段。这里用 UTF-8 无 BOM 写清单,兼容 Windows PowerShell 5.1(其 Set-Content 默认可能写成 UTF-16)。清单中的文件名是受信任的相对路径,-safe 1 保持安全默认值;不要把不受信任的清单直接交给自动化任务。
规格不同:concat Filter
下面假设两个输入都各有一条视频和一条音频:
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.mp4Concat Filter 要求每段从时间戳 0 开始,并要求对应流的数量和参数可被滤镜统一;必要时先用 scale、aresample 等滤镜规范化。只有视频时使用 concat=n=2:v=1:a=0,并只映射 [outv]。不同规格不能为了“能运行”而强行 -c copy。
案例八:生成 HLS 点播文件
New-Item -ItemType Directory -Force .\hls | Out-Nullffmpeg -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,不能只复制这一条单码率命令。
检查清单和分片是否齐全:
Get-Content .\hls\index.m3u8Get-ChildItem .\hls | Select-Object Name,Lengthffprobe -v error -show_entries "format=format_name,duration" -of json .\hls\index.m3u8m3u8 和分片还需要通过 HTTP 服务及目标播放器验证;直接双击清单不能证明浏览器链路、缓存头或跨域配置正确。
案例九:RTSP 摄像头打不开或录制异常
最小验证顺序
Test-NetConnection camera.example.com -Port 554ffprobe -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 等待标准输入,并把进度和日志分开处理:
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/HLS 和 RTSP 协议文档。
评论