
1. 为什么流媒体转码绕不开 FFmpeg 与 DASH/HLS做音视频这行的朋友迟早会碰到一个需求把手里的一堆 MP4、MOV、MKV 之类的文件转成能在网页、App、机顶盒上流畅播放的流媒体格式。这时候绕不开两个关键词——HLS和DASH也绕不开一个工具——FFmpeg。我最早接触这块是帮一个做在线教育的小团队处理课程视频。他们原本的做法是直接扔 MP4 到服务器用户点开就整文件下载结果一个 40 分钟的视频进度条拖一下要等半天移动网络下更是卡到没法看。后来改成 HLS 切片体验立刻不一样了起播快、能自适应码率、拖动响应也快。这套流程的核心工具就是 FFmpeg它几乎能把你听说过的所有封装格式和编码格式互相转换而且命令行就能搞定特别适合做自动化批处理。这篇文章我想聊的是怎么用 FFmpeg 把普通视频转成 HLS 和 DASH 格式包括单码率、多码率自适应、切片参数怎么定、常见坑怎么排。适合两类人看一是刚接触流媒体、想快速跑通一条转码流水线的开发者二是已经在用 FFmpeg 但总被切片时长、码率阶梯、播放兼容性这些问题困扰的运维或后端同学。我不打算堆一堆理论而是按我实际踩过的路子把每一步为什么这么做讲清楚你照着改改参数就能用。先说清楚一个前提HLS 和 DASH 本质上都是基于 HTTP 的自适应流媒体协议。它们把一整个视频切成很多小片段segment再配一个索引文件manifest/playlist告诉播放器每个片段在哪、什么码率。播放器根据当前网速自动选择合适码率的片段网速好就切高清网速差就切低清这就是所谓的 ABR自适应码率。FFmpeg 在这中间扮演的角色就是把源文件切成符合协议规范的片段和索引。2. HLS 与 DASH 的核心差异与选型思路2.1 两种协议到底差在哪很多人一上来就问“我该用 HLS 还是 DASH”其实这个问题得看你的播放端。我先把两者的关键区别摆出来你对照自己的场景选。对比维度HLSDASH索引文件m3u8 播放列表MPD 清单文件切片格式通常是 TS也支持 fMP4通常是 fMP4原生支持Safari、iOS、部分安卓原生支持需要 dash.js 等播放器库延迟表现传统较高低延迟方案在改善天生更适合低延迟编码标准以 H.264/H.265 为主编解码支持更开放生态成熟度非常成熟兼容性最好稍逊但标准化程度高简单说HLS 胜在兼容性DASH 胜在开放性和低延迟潜力。如果你的用户里有大量 iOS 设备HLS 基本是必选项因为 Safari 和 iOS 原生播放器对 HLS 支持最好。如果你做的是自己的 App 或网页用 dash.js、shaka-player 这类库那 DASH 也完全没问题。2.2 我的选型经验实际项目里我一般这么决策面向大众、要最大兼容性优先 HLS必要时 HLS DASH 双协议输出。追求低延迟直播DASH 或 LL-HLS低延迟 HLS切片时长压到 1-2 秒。纯点播、自有播放器DASH 更灵活尤其是需要多音轨、多字幕时。存储和带宽敏感两者都支持多码率但要注意切片格式fMP4 通常比 TS 更省空间。提示HLS 用 TS 切片时每个切片都带 PAT/PMT 表有一定冗余开销。如果对存储敏感可以考虑 HLS 也用 fMP4 切片但要注意老设备兼容性。这里有个容易被忽略的点HLS 和 DASH 并不是互斥的。很多生产环境会同时输出两套让播放器根据能力自己选。FFmpeg 完全支持一次转码、多路输出后面我会给具体命令。3. 转码前的准备工作与参数规划3.1 环境与工具确认动手之前先把环境理清楚。FFmpeg 的安装方式很多我建议直接用系统包管理器或者官方静态编译版本避免自己编译踩依赖坑。# 确认 FFmpeg 是否可用及版本 ffmpeg -version # 查看是否支持需要的编码器比如 libx264、aac ffmpeg -encoders | grep -E libx264|aac版本上我建议用 4.4 以上的版本对 fMP4、DASH 的支持更完善。如果你要用硬件加速比如某些 GPU 编码还得确认编译时带了对应的编码器。3.2 源文件信息探查转码前一定要先看源文件别上来就转。用ffprobe看分辨率、码率、帧率、音轨情况ffprobe -v error -show_format -show_streams input.mp4重点看几个东西分辨率决定你要不要缩放、原始码率决定码率阶梯怎么定、帧率决定 GOP 设置、音轨数量和编码决定音频怎么处理。我见过有人直接拿 4K 源去转 360p结果没加缩放参数转出来还是 4K白白浪费算力。3.3 码率阶梯怎么定多码率自适应流的灵魂是码率阶梯bitrate ladder。定得好用户切换顺滑定得差要么浪费带宽要么切换时画质跳变明显。我的经验是阶梯不要太多3-5 档足够。档位之间码率大致按 1.5-2 倍递增分辨率按常见档位走。下面是一个我常用的参考表档位分辨率视频码率音频码率适用场景低426x240300 kbps64 kbps弱网、移动中640x360800 kbps96 kbps普通移动较高1280x7202500 kbps128 kbps宽带、桌面高1920x10805000 kbps128 kbps高清需求这些数字不是死的得根据你的内容类型调。比如动画类内容压缩率高码率可以低一些体育、动作类画面复杂码率要给足。我一般会先转一版实际看看画质和文件大小再微调。注意码率阶梯里相邻档位的分辨率最好是整数倍关系比如 360p 和 720p这样播放器切换时缩放更自然也方便某些硬件解码器处理。4. 单码率 HLS 转码实操4.1 最基础的 HLS 切片命令先从最简单的开始把 MP4 转成单码率 HLSffmpeg -i input.mp4 \ -c:v libx264 -preset medium -crf 23 \ -c:a aac -b:a 128k \ -f hls \ -hls_time 6 \ -hls_playlist_type vod \ -hls_segment_filename output/segment_%03d.ts \ output/index.m3u8逐段解释一下-c:v libx264视频用 H.264 编码兼容性最好。-preset medium编码速度与压缩率的平衡点追求速度用 faster追求体积用 slow。-crf 23恒定质量模式数值越小画质越好、文件越大23 是常用默认值。-c:a aac -b:a 128k音频转 AAC128kbps。-f hls输出格式指定为 HLS。-hls_time 6每个切片 6 秒。-hls_playlist_type vod点播类型播放列表里会保留所有切片。-hls_segment_filename切片文件命名规则。跑完之后output目录里会有一堆.ts文件和一个index.m3u8。用支持 HLS 的播放器打开 m3u8 就能播。4.2 切片时长怎么选-hls_time这个参数很关键。切片太短文件数量多、请求频繁、索引文件大切片太长起播慢、切换码率迟钝。我的经验值点播6-10 秒比较稳妥兼顾起播速度和请求开销。直播2-4 秒追求更低延迟可以到 1 秒但对服务器和网络要求高。低延迟场景配合 LL-HLS 的 partial segment可以做到 1 秒以内。还有一个坑实际切片时长不一定等于你设的值。FFmpeg 会在关键帧I 帧处切所以如果 GOP 是 2 秒你设 6 秒实际可能切在 6 秒附近的关键帧上。要精确控制得同时设置 GOP-g 48 -keyint_min 48 -sc_threshold 0假设帧率 24-g 48就是每 48 帧一个关键帧即 2 秒一个 I 帧。-sc_threshold 0禁用场景切换插入关键帧保证 GOP 规整。4.3 音频单独处理的情况有时候源文件音轨有问题比如采样率不标准、多音轨。我一般显式指定音频参数-c:a aac -ar 48000 -ac 2 -b:a 128k-ar 48000统一采样率-ac 2转立体声。如果源是 5.1 声道直接转立体声能避免很多播放兼容问题。5. 多码率自适应流的完整实现5.1 为什么要做多码率单码率 HLS 只是入门。真正上线用户网络环境千差万别必须做多码率自适应。原理是同一份内容转出多个码率版本每个版本单独切片然后一个 master playlist 把它们串起来播放器根据网速选。5.2 用 filter_complex 一次转多路FFmpeg 可以用一个命令同时输出多个码率核心是filter_complex做分流和缩放ffmpeg -i input.mp4 \ -filter_complex \ [0:v]split3[v1][v2][v3]; \ [v1]scalew426:h240[v1out]; \ [v2]scalew1280:h720[v2out]; \ [v3]scalew1920:h1080[v3out] \ -map [v1out] -c:v:0 libx264 -b:v:0 300k -preset medium \ -map [v2out] -c:v:1 libx264 -b:v:1 2500k -preset medium \ -map [v3out] -c:v:2 libx264 -b:v:2 5000k -preset medium \ -map a:0 -c:a aac -b:a 128k \ -f hls \ -hls_time 6 \ -hls_playlist_type vod \ -master_pl_name master.m3u8 \ -var_stream_map v:0,a:0 v:1,a:0 v:2,a:0 \ -hls_segment_filename output/v%v/segment_%03d.ts \ output/v%v/index.m3u8这段命令信息量比较大我拆开讲split3把视频流复制成三路分别缩放。-map把每路视频映射到不同的输出流-b:v:0这种带索引的写法指定每路的码率。-master_pl_name master.m3u8生成主播放列表。-var_stream_map定义每个 variant 包含哪些流v:0,a:0表示第一路视频配第一路音频。-hls_segment_filename里的%v会替换成 variant 编号自动分目录。跑完你会得到v0、v1、v2三个目录每个里面有切片和 index.m3u8根目录有 master.m3u8。播放器打开 master 就能自适应。5.3 音频复用的小技巧上面命令里音频只编码了一次被三个 variant 共用。这是常见做法能省算力。但如果你的场景需要每个码率配不同音质也可以分别编码。我一般点播场景共用一份音频就够了因为音频码率占比小共用不影响体验。提示-var_stream_map的写法容易出错尤其是多音轨时。建议先用单音轨跑通再逐步加复杂度。报错时重点看 FFmpeg 提示的 stream map 不匹配信息。5.4 DASH 多码率输出DASH 的命令结构和 HLS 类似只是输出格式和参数不同ffmpeg -i input.mp4 \ -filter_complex \ [0:v]split3[v1][v2][v3]; \ [v1]scalew426:h240[v1out]; \ [v2]scalew1280:h720[v2out]; \ [v3]scalew1920:h1080[v3out] \ -map [v1out] -c:v:0 libx264 -b:v:0 300k \ -map [v2out] -c:v:1 libx264 -b:v:1 2500k \ -map [v3out] -c:v:2 libx264 -b:v:2 5000k \ -map a:0 -c:a aac -b:a 128k \ -f dash \ -seg_duration 6 \ -use_template 1 \ -use_timeline 1 \ -adaptation_sets id0,streamsv id1,streamsa \ output/manifest.mpd关键参数-seg_duration 6切片时长对应 HLS 的 hls_time。-use_template 1用模板方式生成切片 URL索引文件更简洁。-use_timeline 1生成时间线信息播放器定位更准。-adaptation_sets定义自适应集视频一组、音频一组。DASH 默认输出 fMP4 切片.m4s比 TS 更现代也更省空间。6. 常见问题排查与避坑经验6.1 播放器打不开或黑屏这是最常见的问题。排查顺序我一般这么走先确认切片文件生成了没有。如果目录里只有 m3u8 没有 ts/m4s说明切片环节就失败了。看 m3u8 内容。用文本编辑器打开检查切片路径对不对、时长信息是否正常。确认编码格式。H.264 AAC 是兼容性最好的组合如果你用了 H.265很多浏览器播不了。检查跨域。网页播放时m3u8 和切片的 HTTP 响应头要允许跨域否则播放器拿不到数据。6.2 切片时长和预期不符前面提过切片在关键帧处切。如果你发现切片时长忽长忽短八成是 GOP 不规整。解决办法就是显式设置-g、-keyint_min、-sc_threshold 0让关键帧间隔固定。6.3 音画不同步多码率转码时音画不同步通常是因为音频和视频的时间基不一致或者切片边界没对齐。我的处理办法加-vsync cfr强制恒定帧率。音频加-async 1做时间戳校正。确保所有 variant 用同一份音频减少对齐问题。6.4 转码速度太慢多码率转码是算力大户。几个提速思路用-preset faster或veryfast牺牲一点压缩率换速度。用硬件编码比如某些 GPU 的 H.264 编码器速度能快好几倍。把多码率拆成多个 FFmpeg 进程并行跑充分利用多核。下面这张表是我整理的常见问题速查现象可能原因解决方向播放黑屏编码不兼容改用 H.264 AAC切片缺失路径或权限问题检查输出目录写权限时长异常GOP 不规整固定关键帧间隔音画不同步时间基不一致加 vsync/async 校正转码慢算力不足降 preset 或上硬件编码切换卡顿码率阶梯不合理调整档位和分辨率6.5 几个我踩过的坑第一个坑输出目录不存在导致失败。FFmpeg 不会自动创建多级目录-hls_segment_filename里的路径目录得提前mkdir -p。第二个坑master playlist 里路径写错。用%v变量时如果 segment 和 playlist 的相对路径没算对播放器找不到切片。建议转完后手动打开 master.m3u8 核对一遍。第三个坑CRF 和码率混用。多码率场景要用-b:v指定码率别用 CRF否则每个 variant 的实际码率不可控自适应逻辑就乱了。第四个坑忽略音频采样率。源音频是 44100Hz转出来没统一某些播放器会有杂音。统一成 48000Hz 最稳。7. 生产环境的进阶考量7.1 批量转码的脚本化实际项目不可能手动敲命令。我一般写个 shell 脚本或 Python 脚本遍历目录批量处理。核心是把上面的命令参数化输入文件、输出目录、码率阶梯都做成变量。跑之前先小批量测试确认参数没问题再全量跑。7.2 存储与 CDN 配合切片文件数量多尤其是低延迟场景。建议切片目录结构清晰按 variant 分目录方便 CDN 缓存。m3u8 这类索引文件缓存时间要短切片文件可以长缓存因为它们内容不变。7.3 加密与版权保护如果内容需要保护HLS 支持 AES-128 加密DASH 支持多种 DRM 方案。FFmpeg 可以生成加密密钥和加密后的切片但密钥管理和授权逻辑得自己实现。这块复杂度较高建议有明确需求再上。我在实际项目里最大的体会是转码参数没有万能模板必须结合内容类型和用户网络实测调优。同一套码率阶梯用在动画片和体育直播上效果完全不同。我的做法是先转一版找几个典型网络环境实测看起播时间、切换次数、卡顿率再回头调参数。这个过程可能要迭代两三轮但调好之后体验提升非常明显。另外一个小技巧转码时保留一份原始的高码率源文件别转完就删。后续如果要加新码率档位或者换协议可以直接从源文件重新转不用二次压缩损失画质。这个习惯帮我省过好几次返工的时间。