FFmpeg 操作 M3U8 流:切片、合并、转码与直播推流进阶

哈喽,我回来了。最近又掉进流媒体坑里折腾,跟 M3U8 这个格式彻底干上了。FFmpeg 到底有多神我不吹,但每次遇到新场景都得狂翻文档是真的。干脆把自己常碰的几种玩法记下来,当备忘,也给需要的人丢个参考。

先承认,我第一次见到 .m3u8 完全不知道是啥,心想找个视频出来怎么是一堆 .ts 小文件。后来才知道这是苹果的 HLS 直播协议,说白了就是个索引加一堆切片。不过嘛,如果你只是临时想看某个 m3u8 链接里的内容,真不用像我一样装 FFmpeg 折腾命令行,直接扔给 M3U8Player(https://m3u8player.link/)那个在线站就能播,省心省力。但如果你跟我一样想自己处理、下载、转码或者推流,那才需要继续往下看。

先说切片

把自己手里的视频切成 HLS 流这个操作最日常。我一般这么写:

ffmpeg -i input.mp4 -c copy -hls_time 10 -hls_list_size 0 -hls_segment_filename "seg%d.ts" playlist.m3u8

几个参数解释一下:-hls_time 10 是每 10 秒一切,-hls_list_size 0 表示保留所有切片(直播的话一般设个数字只留最近几个),-hls_segment_filename 自定义文件名,不然默认会变成 output%03d.ts 那种。-c copy 不重新编解码,速度飞快。不过有时候原视频的音频流不规范,直接复制出来没声音,得补一个 -bsf:a aac_adtstoasc,但普通 mp4 切片一般不用。

有次我切一个长视频,忘了改输出目录,结果文件名把上一批直接覆盖了,血的教训。建议大家每次切片规规矩矩扔到新文件夹里。

合并下载

现在不少在线视频直接给你一个 m3u8 地址,想拉下来变成 mp4 很简单:

ffmpeg -i "https://...playlist.m3u8" -c copy -bsf:a aac_adtstoasc output.mp4

一行搞定,默认会选最高码率。但有防盗链的时候要加个 -user_agent "Mozilla/5.0",不然一直 403。有次我排查了半天才发现是默认 user-agent 被 ban 了,换了个 UA 秒解。

如果拿到的是多码率 master playlist(里面套了好几个不同分辨率的流),直接用上面的命令会只挑一个最高的。想选特定的版本,可以先跑 ffmpeg -i "master.m3u8" 看信息,它会显示出 #EXT-X-STREAM-INF 下面各个流的索引号,然后用 -map 0:v:0 -map 0:a:0? 指定要哪个流的视频和音频,最后合并成单文件。

转码进阶

有时候原始 m3u8 分辨率太高或者格式不对,想重新压制一下再输出成新的 m3u8:

ffmpeg -i "input.m3u8" -vf scale=1280:720 -c:v libx264 -crf 23 -c:a aac -hls_time 10 out.m3u8

输入直接是 m3u8 链接,ffmpeg 会边下边处理。注意这样速度慢、硬盘临时占用大,最好先合并成一个临时文件再切片。不过一次搞定也挺方便。

如果要搞自适应多码率,一般需要先把原视频转码成不同分辨率和码率的版本,各自切片,再手写一个 master playlist。ffmpeg 本身不能一步生成自适应流,得靠脚本编排。

转码里有个坑:不同码率之间切片要对齐关键帧,不然点击切换分辨率时画面会花一下。我习惯加个 -g 48,让每 48 帧一个关键帧,大概 2 秒间隔(按 24fps 算),再配合同样时长的 -hls_time 2,这样切片点就整整齐齐了。

直播推流进阶

这个我纯属测试着玩。纯 FFmpeg 直接推 HLS 到本地目录:

ffmpeg -re -i input.mp4 -c copy -f hls -hls_time 4 -hls_list_size 10 -hls_flags delete_segments live.m3u8

-re 是强制按原帧率读取(否则会狂奔)。这样会在当前目录生成不断刷新的 m3u8 和 ts 文件,再用 nginx 把目录暴露出去,就是一个简单的直播源了。适合小范围自己测。

更常见的做法是先推 RTMP 到服务器(比如用 nginx-rtmp-module),再由服务端转成 HLS。但如果你不想搭服务器,ffmpeg 也可以直接推流到云平台,参数大同小异。

之前我跑本地推流,播放器老是缓冲,后来把 -hls_time 压到 2 秒,-hls_list_size 只留 3 个,延迟明显降了。但切片越短对服务器压力越大,自己取舍。

还有一个坑是时间戳:如果你循环推同一个文件(比如加 -stream_loop -1),时间戳可能会重置导致 HLS 流断掉。解决办法是加 -fflags +genpts 或者用 -re 配正确的时间基。这个我折腾了俩小时才搞定。

其实我现在直播大部分直接开 OBS,但自动化写脚本的时候还是得靠 FFmpeg 硬上,没辙。

好了,关于 M3U8 和 FFmpeg 的常用操作就这些。每个玩法就那么几个核心参数,但组合起来千变万化。对了如果你只是想快速预览一个 m3u8 看看自己生成的对不对,我偶尔也会用上面的 M3U8Player 那个网站丢进去瞄一眼,省得再开本地播放器。反正大家多试试就熟了,有什么新坑也欢迎交流。