
众所周知,FFmpeg输出hls时,m3u8索引文件是在不断动态更新的,每当新的ts切片生成,就会更新一次m3u8文件。但是,其中有很多细节需要我们深入探究,比如m3u8文件在更新时,如何避免读写不一致问题?所有ts切片的时长都是一样的吗?
这个过程主要是生成一个空的m3u8索引文件,然后在其中输入一些初始化标签,比如 #EXTM3U、#EXT-X-VERSION和#EXT-X-TARGETDURATION 等。
示例如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0hls处理模块不断接收音频流和视频流的数据包,把音频帧和视频帧写入到ts切片中,这里可能涉及到交错存储,先不展开说了,后续文章再详细说明。当满足切割条件时,比如关键帧到达或者时间阈值到达时,就会停止当前ts切片的写入并存储到物理磁盘,同时,生成新的 index+1 的 ts 切片继续写入。
为了保证m3u8索引文件读写一致,会进行原子更新。首先,创建一个临时 .m3u8.tmp 文件,添加新切片条目,以 #EXTINF 标签开头,后面追加新切片的时长。如果此时播放列表已经满了,则移除最旧条目并递增媒体序列号(#EXT-X-MEDIA-SEQUENCE),.m3u8.tmp 文件的写操作完成后,直接替换原 .m3u8 索引文件。这个过程可以通过系统的重命名操作完成,实现原子化操作。
如果启用了 delete_segments 参数,那么在过程中会自动删除从 m3u8 文件中移除的 ts 切片文件。当输入流结束时添加 #EXT-X-ENDLIST 结束标记,但如果是直播流的话,不会加这个标识,只有点播 m3u8 文件才有。
如果上述文字介绍过于枯燥,可以通过下面的流程图加强理解:

流程图的源代码如下:
flowchart TD
A([开始HLS输出]) --> B[初始化参数]
B --> C[创建初始.m3u8文件]
C --> D[设置媒体序列号( #EXT-X-MEDIA-SEQUENCE:0)]
D --> E[接收音视频流数据]
E --> F{达到切片条件?(关键帧 or 时间阈值)}
F -- 是 --> G[生成新.ts切片文件]
F -- 否 --> E
G --> H[写入.ts文件到磁盘]
H --> I[更新.m3u8索引文件]
subgraph 更新索引文件 [原子化更新]
I --> J[创建临时文件 .m3u8.tmp]
J --> K[添加新切片信息 #EXTINF:时长, 文件名]
K --> L{播放列表满?(hls_list_size限制)}
L -- 是 --> M[移除最旧切片条目]
L -- 否 --> N
M --> N[递增媒体序列号 (#EXT-X-MEDIA-SEQUENCE+1)]
N --> O[写入临时文件内容]
O --> P[重命名临时文件替换原.m3u8]
end
P --> Q{启用删除旧切片}
Q -- 是 --> R[删除播放列表外的旧.ts文件]
Q -- 否 --> S
R --> S
S --> T{输入流结束?}
T -- 否 --> E
T -- 是 --> U[添加结束标记(#EXT-X-ENDLIST)]
U --> V([结束])控制切片的频率,多久生成一个 ts 切片,从而影响 m3u8 索引文件的更新频率。值越小,切片越频繁,更新越快,延迟越低(但也可能增加服务器和客户端负担)。比如 -hls_time 10 则表示 10 秒一个 ts 切片。
控制播放列表的窗口大小,也就是 .m3u8 中有多少个 ts 切片。
开启后会实现磁盘空间的自动清理,避免旧的ts 切片文件无限累积,需要与 -hls_flags 一起使用。
这是一个特殊的控制参数,也需要与 -hls_flags 一起使用。启用后,FFmpeg 会将所有切片数据写入一个巨大的 .ts 切片文件。.m3u8 文件会动态更新,通过 #EXT-X-BYTERANGE 标签指向不同字节范围来模拟独立的切片。更新 .m3u8 时,只需添加新的 #EXTINF 和 #EXT-X-BYTERANGE 条目指向新写入的数据范围即可。
标识文件是 m3u8 文件播放列表,必须放在文件第一行。
指定 hls 协议的版本,比如3、4、7等,不同版本支持的标签有所不同。
描述每个媒体 ts 切片的时长(秒)和标题。注意:duration 必须是浮点数。
指定所有切片的目标时长。
标识播放列表中第一个切片的序列号,一般从0开始。
标识播放列表结束,不再更新,直播场景中没有,但是点播场景中必须有。
对应上面的主要过程,我们来看一下底层的代码。
解析 -hls_time 等参数,创建初始 ts 切片文件,初始化 m3u8 播放列表。
生成 ts 切片,同时处理 ts 切片的切割逻辑并更新 m3u8 索引文件。
写入最后一个切片,更新 m3u8 文件,追加 #EXT-X-ENDLIST 标签。最后,释放上下文相关的资源。
最后,也可以通过下面的时序图加深理解:

通过本文的介绍,我们了解了 FFmpeg 输出 hls 的关键步骤和主要方法,其逻辑清晰,涉及 init、header、packet、trailer、deinit 五个阶段,实现了高度可扩展的 hls 生成能力,既能处理点播文件转换,也能支持高并发场景下的直播流。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。