学习目标:掌握 FFmpeg 从单源视频一次转码同时生成 HLS(TS+fMP4)和 DASH(fMP4)的完整命令结构、清单参数以及常见的陷阱排查方法。
实现步骤按以下顺序构建:
1. **分析源文件** – 确认编码格式、帧率、关键帧间隔(GOP)是否满足分片对齐要求。
2. **输出映射规划** – 为每个码率变体建立独立的视频流与音频流输出映射,并指定标签(`-map`)。
3. **muxer参数配置** – 对 HLS 设置 `hls_time`、`hls_list_size`、`hls_playlist_type`;对 DASH 设置 `seg_duration`、`window_size`、`adaptation_sets`。
4. **同时启动两个 muxer** – FFmpeg 支持多输出(`-f hls` 与 `-f dash`),但需要确保时间戳同步,通常使用 `-g` 强制 GOP 相同。
5. **验证输出** – 检查 M3U8、MPD 及其引用的分片文件是否完整,ts 与 fMP4 分片时长是否一致。
### 3.3 FFmpeg 命令与参数详解
以下是一条生产级转码命令的完整模板(同时生成 HLS 与 DASH 各两个变体):
ffmpeg -i input.mp4 \
-c:v libx264 -b:v:0 2500k -b:v:1 1000k \
-s:v:0 1920x1080 -s:v:1 1280x720 \
-c:a aac -b:a 128k -ar 48000 \
-ac 2 -g 48 -keyint_min 48 -sc_threshold 0 \
-map 0:v -map 0:a -map 0:v -map 0:a \
-var_stream_map "v:0,a:0 v:1,a:1" \
-f hls \
-hls_time 6 -hls_list_size 10 -hls_playlist_type vod \
-hls_segment_filename "output/%v_%03d.ts" \
-master_pl_name "master.m3u8" \
"output/stream_%v.m3u8" \
-f dash \
-seg_duration 6 -window_size 10 \
-adaptation_sets "id=0,streams=v id=1,streams=a" \
-use_template 1 -use_timeline 1 \
"output/manifest.mpd"
**关键参数说明:**
| 参数 | 作用 | 注意事项 |
|------|------|---------|
| `-var_stream_map` | 定义变体流组合,支持多音频轨 | 索引顺序需与 `-map` 一一对应 |
| `-g 48` | 强制关键帧间隔 48 帧(2秒@24fps) | 必须与分片时长匹配,否则片段头部不闭合 |
| `-hls_playlist_type vod` | HLS 清单类型(点播模式) | 直播时改为 `event` 或 `live` |
| `-adaptation_sets` | DASH 适应集定义 | 支持逗号分隔多集,每集可指定角色 `role` |
| `-use_template 1 -use_timeline 1` | DASH 使用模板化清单与时间线 | 减少 MPD 体积,更易于 CDN 缓存 |
### 3.4 清单生成配置对比
| 清单属性 | HLS(M3U8) | DASH(MPD) |
|----------|------------|-------------|
| 分片封装 | TS 或 fMP4 | 仅 fMP4 |
| 主清单 | `master.m3u8`(含变体带宽) | `manifest.mpd`(含 adaption set) |
| 分段时长控制 | `hls_time`(秒) | `seg_duration`(秒) |
| 回退数量 | `hls_list_size`(窗口大小) | `window_size`(分片数量) |
| 关键帧对齐 | 依赖 `-g` + `-keyint_min` | 依赖 `-g` + `-forced-idr` |
| 加密支持 | `-hls_key_info_file` | `-encryption_system`(SAMPLE‑AES) |
常见错误:若 HLS 采用 TS 封装而 DASH 要求 fMP4,FFmpeg 会在同一个输出文件夹中混写入两种封装格式。此时必须通过
### 3.5 调试步骤
成功执行后,输出目录应包含:
-hls_segment_filename 和 DASH 的 init_seg_name 明确区分后缀(例如 .ts 与 .m4s),否则覆盖冲突。
output/
├── master.m3u8 # HLS 主清单
├── stream_0.m3u8 # 高清变体媒体清单
├── stream_1.m3u8 # 标清变体媒体清单
├── 0_0000.ts # HLS TS 分片(变体0)
├── 1_0000.ts # HLS TS 分片(变体1)
├── manifest.mpd # DASH 主清单
├── init_0.m4s # DASH 初始化段(变体0)
├── seg_0_0000.m4s # DASH 媒体分片(变体0)
├── init_1.m4s # DASH 初始化段(变体1)
└── seg_1_0000.m4s # DASH 媒体分片(变体1)
**验证命令:**
- 检查 MPD 内部时间线是否与分片实际时长一致:
`ffprobe -v error -show_entries packet=pts_time,duration output/seg_0_0000.m4s 2>&1 | head`
- 检查 M3U8 中 `#EXTINF` 时长是否等于 `hls_time` 设定值。
### 3.6 部署与性能优化
| 优化方向 | 操作 | 效果 |
|----------|------|------|
| GPU 硬件编码 | `-c:v h264_nvenc`(NVIDIA) | 降低 CPU 占用 60%~80%,适合多路并发 |
| 分片时长调整 | 将 `hls_time` 和 `seg_duration` 统一设为 4~10 秒 | 更短分片 → 更低延迟(≤4s);更长分片 → 更少文件,降低 CDN 请求数 |
| 并行 muxer | 分离为两个独立 FFmpeg 进程(先转码输出原始流,再分别传入 hls/dash muxer) | 可独立优化每个 muxer 参数,但增加 I/O 负载 |
| 缓存临时流 | 使用 `-f tee` 或命名管道(FIFO)减少中间磁盘写入 | 适用于高吞吐环境,避免大量 .tmp 文件 |
### 3.7 内联流程图:双协议转码与打包流程
### 3.8 常见错误与排障
| 错误现象 | 根因 | 解决方案 |
|----------|------|---------|
| MPD 中 `@segmentDuration` 与分片实际时长偏差 >0.1s | GOP 长度与 `seg_duration` 不匹配 | 设置 `-g` 为 `seg_duration * 帧率`,并添加 `-force_key_frames` |
| HLS 主清单无法加载变体 | `-map` 顺序与 `-var_stream_map` 不一致 | 使用 `-map 0:v` + `-map 0:a` 依次重复,索引从 0 开始 |
| DASH 初始化段缺失 | 未设置 `-use_template 1` 或 `init_seg_name` 与分片冲突 | 显式指定 `-init_seg_name "init_$RepresentationID$.m4s"` |
| 输出文件夹内 `.tmp` 文件残留 | FFmpeg 异常中断,未完成 muxer 关闭 | 增加 `-y` 覆盖;使用信号处理脚本清理;确保磁盘空间 |
| 两个 muxer 共享参数冲突(如 `-hls_time` 与 `-seg_duration` 不一致) | 分片边界错位,导致播放器跨协议切换时跳帧 | 在命令中始终保持两者值相同,且小于等于 GOP 宽限 |
生产建议:将双协议输出命令封装为 Shell 脚本,统一管理参数。在 CI/CD 流水线中增加校验步骤:使用
### 3.9 扩展:使用 tee muxer 分离流程
对于超大规模转码集群,可将 FFmpeg 的 `-f tee` 配合 `[f=hls]` 和 `[f=dash]` 写入同一个管道,减少一次文件读取。但需注意 tee muxer 不支持动态 `-map`,需预先规划好流索引。
mediainfo 或自定义 Python 脚本检查 MPD 与 M3U8 中的分片总数与带宽标签,确保两个清单均正确发布。
ffmpeg -i input.mp4 -c copy \
-f tee -map 0:v -map 0:a \
"[f=hls:hls_time=6:hls_list_size=10:hls_playlist_type=vod:var_stream_map='v:0,a:0']output/stream.m3u8| \
[f=dash:seg_duration=6:window_size=10:adaptation_sets='id=0,streams=v id=1,streams=a']output/manifest.mpd"
此种方式适用于转码后仅需 remux(同编码)的场景,避免二次编码。如需不同码率,仍应使用第三章主命令中的多变体编码方案。