HLS与M3U8通俗解读:从分片原理到线上排障实践

发布时间:2026/10/15 15:18:11
HLS与M3U8通俗解读:从分片原理到线上排障实践 终于有人愿意把 HLS 和 M3U8 讲得通俗一点了。事情是这样的。有一次线上反馈点播视频一直转圈我打开播放地址结尾是.m3u8第一反应就是“问题多半出在清单上”。把文本拉下来一看果然有十来个分片编号压根没有出现在列表里。播放器卡在加载不是网络连不上而是 M3U8 告诉它的时间线本身就是断的。这次排查之后我觉得有必要把 HLS 协议里里外外讲一遍。这篇东西不搞玄学就从原理拆解到 M3U8 结构、播放器内部流程再到实际切片、加密和部署排障把「HLS 播放视频流」这件事彻底说透。适合刚接触流媒体的人也适合那些已经被线上播放事故折腾过一会儿的运维和客户端同学。1. 为什么是 HLS分片播放到底解决了什么1.1 从“一个文件播到底”到“切片清单”早期的视频播放思路有两种一种是类似直接用播放器连接流媒体服务器服务器源源不断把流推给客户端另一种是普通文件下载浏览器边下边播。前者在公网环境下很容易被各种路由器、防火墙折腾后者虽然老百姓友好但网络一抖播放器很容易卡住。而且文件一旦损坏后面全都白瞎。更麻烦的是你想做多清晰度切换还得靠播放器自己猜猜错了用户体验马上崩。HLS 的思路完全不一样。它把一段连续的视频切成一个个几秒钟的小文件一般叫分片或切片然后用一个文本清单把这些小文件的播放顺序记下来。这个清单就是 M3U8。播放器要做的事情非常简单先读清单按顺序拉取小文件然后逐段播放下去。整个播放过程表面上还是“顺序拉数据”但底层逻辑已经变了。视频不再是一个必须完整可达的大文件而是一堆可以被缓存、可以跳过、可以并行下载的小文件。1.2 为什么这个思路能统治视频流HLS 最大的优点是它只需要普通 HTTP 协议。任何一台能托管静态文件的服务器都能直接用来分发视频。不需要特殊端口不需要自定义 TCP 协议不需要额外握手。CDN 厂商最喜欢这种模式因为这和分发一堆图片、JS 文件没有本质区别缓存系统直接复用。另一个优点是网络抖动时的自愈能力。某个分片下载失败播放器可以重试或者跳到同码率的下一段继续播用户最多感觉轻微卡一下而不是整个画面死掉。更关键的是HLS 天然适合多码率自适应。源站在切片的时候可以同时把视频编码成 480p、720p、1080p 多份生成一个“主清单”。播放器会根据当前带宽、播完分片的速度和缓冲情况自动选择最合适的码率去拉下一段。这个过程被称作 ABR用户看起来就是网好的时候画质清晰网差的时候自动降清晰度而且不用暂停。1.3 HLS 不擅长什么一切方案都有边界。HLS 的标准延迟通常在一个分片时长以上如果每个分片六秒那基本就有十秒甚至更高的端到端延迟。所以它不适合实时互动场景——比如视频连麦、远程操作、多人游戏同步。如果是监控摄像头这类虽然“实时”但不要求毫秒级互动的场景HLS 完全够用。真正低延迟直播通常会用 LL-HLS 或者 WebRTC 之类的方案。这里要注意LL-HLS 虽然名字里还带 HLS但实现复杂度比普通 HLS 高不少不是简单地把分片切小就完事。所以我的建议是先想清楚你要的是“在线视频”还是“实时互动”。如果是前者HLS 是现在最稳的默认选项。2. M3U8 里面的门道一次把清单文件读明白2.1 一个最基础的点播清单逐行拆解先看一个标准的点播 M3U8#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.0, segment0.ts #EXTINF:9.8, segment1.ts #EXTINF:10.2, segment2.ts #EXT-X-ENDLIST第一行#EXTM3U是文件类型的标志没有它播放器会直接认为这不是有效的 HLS 清单。#EXT-X-VERSION:3是协议版本号不同版本支持不同的标签和加密方式现在大多数普通清单用3就够。#EXT-X-TARGETDURATION表示单个分片的最长时长单位是秒。它不是一个硬性限制而是一个声明值。播放器会拿它来估算缓冲时间和直播边缘。真正决定每个分片播放时长的是分片前面的#EXTINF。#EXTINF:10.0,里的 10.0 表示这个分片时长 10 秒后面那个逗号后面一般跟分片名也可以留空。紧接着下面一行是分片的相对路径。这里的路径是相对于当前 M3U8 文件的如果你把清单和分片放在不同目录必须写对相对路径否则播放器 404。#EXT-X-MEDIA-SEQUENCE是直播场景最重要的字段它表示清单中第一个分片的序号。点播场景一般从 0 开始直播场景会不断滚动。最后一行#EXT-X-ENDLIST表示这是点播文件播放完最后一个分片就结束了。没有这一行的是直播清单会被认为是“现在还在持续更新”。2.2 直播清单是怎么“滚动”的直播和点播在 M3U8 上的核心区别就是直播清单没有#EXT-X-ENDLIST并且它会不断更新。假设直播切片器每隔六秒生成一个新分片文件名是seg_0000.ts、seg_0001.ts、seg_0002.ts。每生成几个新分片清单就被重写一次去掉文件已经被删除的旧分片加上新分片。比如第一次请求返回#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:6 #EXT-X-MEDIA-SEQUENCE:100 #EXTINF:6.0, seg_0100.ts #EXTINF:6.0, seg_0101.ts #EXT-X-DISCONTINUITY #EXTINF:6.0, seg_0102.ts第二次请求可能就变成#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:6 #EXT-X-MEDIA-SEQUENCE:102 #EXTINF:6.0, seg_0102.ts #EXTINF:6.0, seg_0103.ts播放器每次刷新清单都拿#EXT-X-MEDIA-SEQUENCE对比自己已经下载到哪个分片从而决定下一个要拉哪个。如果播放器的进度已经落后于当前清单的最早分片播放器会直接报错表现就是直播画面一卡然后重新开始追赶。#EXT-X-DISCONTINUITY这个标签在直播中很有用。它表示分片之间存在时间戳不连续常用于切换到广告、插播片源、信号中断后重连。播放器看到这个标签后会重置一些时间戳相关的计算避免音画不同步。2.3 主清单与子清单多码率是怎么组织的一个码率的清单叫媒体清单或者子清单。多个码率合在一起的主清单叫做 master 播放列表它的作用不是直接提供分片而是告诉播放器“你有哪些清晰度可选”。一个典型的多码率主清单长这样#EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:BANDWIDTH2500000,RESOLUTION1920x1080 1080p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH1400000,RESOLUTION1280x720 720p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH800000,RESOLUTION854x480 480p/index.m3u8#EXT-X-STREAM-INF后面是逗号分隔的属性BANDWIDTH是这个码率流的峰值带宽估算单位是 bpsRESOLUTION是分辨率。下一行就是这个子清单的 URL。给用户播放时你只需要给他主清单的地址。播放器先下载主清单解析出若干个子清单再根据带宽和分辨率参数去拉其中一个子清单。这个过程用户无感知。2.4 加密分片是怎么标记的HLS 最常见的加密方式是 AES-128。加密信息通过#EXT-X-KEY标签声明#EXT-X-KEY:METHODAES-128,URIhttps://media.example.com/key.bin,IV0x00112233445566778899AABBCCDDEEFFMETHOD表示加密方式URI是密钥文件地址IV可写可不写。如果不写播放器默认使用分片的序列号做 IV 计算如果写了必须严格按照十六进制字符串解析。要注意M3U8 清单本身不加密分片文件才加密。播放器拿到清单后会先去URI拉取密钥下载分片然后用密钥解密最后送进解码器。3. 播放器拿到清单之后从请求到画面的完整链条3.1 构建分段时间轴播放器解析完 M3U8 后第一件事不是立刻下载分片而是把清单里的每个分片转换成内部的一个时间轴对象。这个对象至少包含分片 URL、时长、序号、解码时间戳起始值。点播场景直接按顺序排列直播场景则需要计算当前应该从哪个分片开始。如果遇到#EXT-X-DISCONTINUITY播放器会标记时间轴上的一个断点后续分片的时间戳会重新开始累加。这一步做不好后面所有“音画不同步”的锅都会甩到播放器头上。3.2 缓冲策略和码率切换播放器不会傻傻地下载一个分片播一个分片。它通常预下载多个分片到内存里形成缓冲。常见的缓冲目标在 20 到 40 秒之间。缓冲足够时播放器进入稳定播放状态缓冲低于阈值时播放器停止播放进入缓冲等待。ABR 算法就在这个阶段参与决策。播放器会统计最近几个分片的下载速率、请求耗时、往返时间、缓冲长度预测下一个分片用什么码率最安全。这套逻辑里有个坑如果网络状态在“够看 1080p”和“只够看 360p”之间反复横跳播放器会不停切换码率。每次切换都意味着播放器要重新解码画面可能出现瞬间卡顿。所以好的播放器切码率有“冷却时间”不会每个分片都切。3.3 直播中途进场时的“追平”逻辑如果你在一个已经直播了两个小时的直播间里点进来播放器自然不可能从第一个分片开始播。它会读取清单末尾的分片计算出一个叫做“直播边缘”的位置。为了兼顾实时性和缓冲量播放器不会直接贴到最新分片而会退后两三个分片开始下载。这样做是给网络抖动留出缓冲空间。如果你希望直播更接近实时可以把播放器的直播延迟参数调低代价是更容易因为网络波动而卡顿。这段逻辑具体到项目里通常不是业务方需要自己写的但排查问题时要理解直播为什么总是比源站慢因为播放器“故意”落后了几个分片。3.4 解密流程带 AES-128 的 HLS播放器会先请求密钥文件。密钥文件的获取是普通 HTTP 请求和分片一样受跨域规则限制。解密本身通常在播放器的解复用模块里完成对上层业务透明。但这里有一个经常忽略的细节密钥文件接口如果做了鉴权会让播放器无密可用。某些安全方案会把密钥地址换成短时有效的签名 URL必须确保签名过期时间大于播放器可能缓存密钥的最长时间否则播一会儿就突然黑屏。4. 从零做一个可播放的 HLS 流切片、多码率与 AES 加密4.1 生成最基础的点播清单准备一个本地 MP4 文件用开源的命令行转码工具来转 HLS。我用的命令行大致长这样ffmpeg -i demo.mp4 \ -c:v libx264 -preset veryfast -g 48 -sc_threshold 0 \ -b:v 2000k -maxrate 2500k -bufsize 4000k \ -c:a aac -b:a 128k \ -f hls \ -hls_time 4 \ -hls_list_size 0 \ -hls_segment_filename seg_%03d.ts \ index.m3u8这里每条参数都有讲究不是随便填的。-g 48是设置 GOP 大小也就是每隔 48 帧出一个关键帧。我这里默认视频 24 帧每秒48 帧就是两秒一个关键帧。这个参数决定了分片能不能精确切到想要的长度。如果-g和-hls_time不匹配切片器只能在关键帧位置切分实际分片时长会和预期差很多。-sc_threshold 0是关闭场景检测。转码工具默认会在画面剧烈变化时强行插入关键帧这样虽然画质更好但分片时长会更不均匀。对于 HLS 切片我一般关掉它除非你有特殊需求。-b:v 2000k -maxrate 2500k -bufsize 4000k是控制码率波动的一组参数。HLS 的带宽估算值基于码率上限maxrate如果设太高客户端会误判带宽导致选错码率。我用 VBR 时会把maxrate控制在码率的 1.25 倍。-hls_time 4表示目标分片时长 4 秒。-hls_list_size 0表示清单里保留所有分片这个参数只对直播有意义默认值是 5如果你做点播不设置成 0那最后生成的清单只保留最后几个分片前面的全被丢掉了。-hls_segment_filename指定分片文件名模板%03d意思是三位数字补零。生成后目录里会有一个index.m3u8和一堆seg_000.ts。4.2 做一个多码率主清单多码率最简单稳妥的做法是把同一个源分别转成多个码率的 HLS 子目录然后手写主清单。比如目录结构是streams/ 1080p/index.m3u8 720p/index.m3u8 480p/index.m3u8每个子目录的生成命令基本一样只是把-b:v、-maxrate、-bufsize和分辨率调整一下。1080p 用 2500k 码率720p 用 1400k480p 用 800k。分辨率通过-vf scale1920:1080这类参数控制。然后写一个主清单#EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:BANDWIDTH2500000,RESOLUTION1920x1080 1080p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH1400000,RESOLUTION1280x720 720p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH800000,RESOLUTION854x480 480p/index.m3u8这里的 URL 是相对路径播放器会基于主清单的地址去拼子清单的完整地址。所以子目录名和文件名不要乱起带空格或中文比较容易出问题。4.3 给分片加 AES-128 加密先生成一个 16 字节的密钥文件openssl rand 16 key.bin再准备一个密钥信息文件我通常叫key_info格式是两行https://media.example.com/key.bin ./key.bin第一行是播放器拉取密钥时用的 URL第二行是本地密钥文件路径。第三行可以写 IV也可以留空。然后重新切片ffmpeg -i demo.mp4 \ -c:v libx264 -preset veryfast -g 48 -sc_threshold 0 \ -c:a aac -b:a 128k \ -f hls \ -hls_time 4 \ -hls_list_size 0 \ -hls_key_info_file key_info \ -hls_segment_filename enc_seg_%03d.ts \ enc_index.m3u8生成的 M3U8 里会出现#EXT-X-KEY:METHODAES-128,URIhttps://media.example.com/key.bin。播放器播放时会先请求https://media.example.com/key.bin再解密分段。注意密钥文件必须能被播放器直接 HTTP 访问而且要处理跨域。同一域名下没问题跨域名就需要服务器返回正确的 Access-Control-Allow-Origin。4.4 本地验证把生成的 HLS 目录放到任意静态文件服务器中然后先用命令行工具验证清单能正常拉取curl -s http://127.0.0.1/streams/index.m3u8清单返回后再验证第一个分片curl -sI http://127.0.0.1/streams/seg_000.ts确认状态码是 200Content-Type 是video/mp2t或类似内容Content-Length 不是 0。然后把主清单地址丢进支持 HLS 的播放器里。在默认支持 HLS 的浏览器里可以直接打开其他浏览器需要引入开源的 HLS 播放器库播放逻辑都一样。播放器画面出来能 seek能暂停就说明这个流基本没问题。5. 最容易让播放器“卡在转圈”的坑以及我的排查顺序5.1 清单滚动太快分片已经被删了直播场景最常见的坑是切片器把分片文件写到磁盘播放列表也在更新但服务器上的旧分片被清理了。播放器拿到一份旧清单里面记录的还是旧序号比如seg_0100.ts但服务器上这个文件已经被回收。播放器拉取时 404进入重试重试失败后如果播放器策略比较激进就报错退出。这类问题的表现非常典型直播刚开始能看播几分钟后卡住刷新页面重进又是几分钟后卡住。排查时先看清单的#EXT-X-MEDIA-SEQUENCE拿当前服务器上实际存在的分片文件和清单里最早的序号对比。如果清单还引用已经删除的文件说明保留策略太短要调大清理延迟。5.2 分片时长和清单声明对不上#EXTINF告诉播放器这个分片应该播几秒但分片里实际封装的音视频时间戳并不一定和这个值一致。如果转码参数配置不当会出现清单写 6 秒实际分片只有 5 秒或 7 秒。播放器按清单来播放但解码器按时间戳来输出画面。两边不一致会出现画面和声音逐渐错位或者播放到分片边界时出现几十毫秒的跳动。这种情况我一般先查两个点GOP 是否固定以及转码时是否开了场景检测。正常情况下把-g设置成关键帧间隔并且把-sc_threshold关掉分片时长就会稳定很多。5.3 直播的 M3U8 被 CDN 缓存了静态文件服务器和 CDN 是 HLS 的好搭档但直播场景的清单文件和分片文件的缓存策略是完全不同的。最基本的原则是直播清单必须允许短时间过期最好设置成Cache-Control: no-cache而分片文件可以长时间缓存。如果清单被 CDN 缓存了十秒播放器反复拿到同一份清单但清单里引用的分片很快过期用户就会看到直播画面重复、跳变。更麻烦的是不同的播放器可能拿到不同版本的清单各自按自己的理解去拉分片用户之间的画面进度差异会非常大。排查方法很简单在几个不同时间点请求同一个 M3U8 URL对比#EXT-X-MEDIA-SEQUENCE是否有变化。如果一直不变基本就是缓存层的问题。5.4 CORS 和 HTTPS 混合内容在非原生支持的浏览器里播放 HLS播放器本质上是先用 JavaScript 去请求 M3U8 和分片。这就会受到浏览器跨域规则的限制。如果页面是https://a.com清单在https://b.com那么b.com必须返回允许a.com跨域的响应头。而且不只是清单分片请求也要允许跨域否则播放器会报 CORS 错误然后静默卡在缓冲状态。更隐蔽的是混合内容问题。如果页面是 HTTPS但清单里面写的是 HTTP 地址浏览器会直接阻止播放器请求甚至连报错都非常轻描淡写。我遇到过线上资源全部是 HTTPS但转码时清单里的 URI 写成 HTTP 的情况排查到最后发现是#EXT-X-KEY里写了 HTTP 地址。这个坑提醒我所有 M3U8 里出现的完整 URL都要在部署前用脚本检查一遍确保协议头统一。5.5 静默失败的通用排查顺序如果播放器一直转圈但没有明确报错我的排查顺序是固定的先抓播放入口请求看是否能拉取到 M3U8再看清单状态码和内容是否正常然后随机挑两三个分片请求确认状态码和时间间隔最后看密钥请求是否成功。把这四步走完问题基本就能定位。为了更好对照我把常见表现整理成了下面这张表现象可能原因优先排查方向播放前就一直转圈清单无法获取检查 M3U8 URL、CDN 缓存、CORS播一会儿后卡住分片 404检查分片保留策略、序列号画面跳变、时间错乱分片时长不均检查 GOP、EXTINF 一致性画面黑屏但进度在走解密失败检查密钥 URL、密钥过期时间部分设备能播部分不行客户端兼容性检查协议版本、编码参数、加密方式这套顺序在我手上解决过不少看起来无解的问题。流媒体问题有个特点只要能看到底层请求和数据答案基本就摆在那里。6. 上线前值得做的检查清单与长期经验6.1 用一个简单脚本盯住清单健康状态我个人的习惯是任何 HLS 流上线前先写一个几行脚本检查清单。不需要复杂的监控系统一个命令行就能做curl -s http://your-domain.com/live/index.m3u8 | grep -E ^#EXTINF | wc -l这个命令统计清单里有多少个分片。对于直播如果这个数字一直不变说明清单没有滚动要么流断了要么缓存层出了问题。再检查一下分片序号是否连续curl -s http://your-domain.com/live/index.m3u8 | grep -E ^media || \ curl -s http://your-domain.com/live/index.m3u8 | grep -E ^#EXT-X-MEDIA-SEQUENCE更严格的校验建议用一段简单的 Python 脚本解析所有#EXTINF的时长和分片 URL然后逐个发 HEAD 请求验证状态码。我现在做新项目时这个检查是门槛。6.2 参数调优心得分片时长不是越短越好。分片越短直播延迟越低但请求数量越大CDN 回源压力越高。普通场景四到六秒是平衡点。追求极低延迟时再去研究 LL-HLS但那时你要处理的就不是分片时长一个变量了还有播放器对低延迟参数的支持度。码率切换也不是码率越多越好。码率档位太多会让 ABR 算法频繁切换用户感知反而更差。我见过一个项目把码率分了九档实际上网速稍微波动播放器就在四档和五档之间反复横跳画面高频变糊又变清。后来我砍到四档体验稳定很多。加密密钥的过期时间要和 CDN 缓存策略一起设计。密钥文件缓存如果比签名有效期还长相当于签名失效后用户还在用旧密钥一旦缓存被清除就会踩雷。6.3 不要盲目把所有场景都套上 HLSHLS 不是万能的。如果你做的是在线教育点播、视频网站、活动直播HLS 是非常成熟的方案。但如果是视频通话、连麦、在线低延迟互动HLS 天生不占优势这时候还硬套 HLS等于把性能瓶颈焊死在架构里。我参与过一个在线答题竞赛项目最初想统一用 HLS 做直播结果互动延迟高到观众能看到答案在画面上提前几秒出现。后来互动部分切换成低延迟通道HLS 只负责普通观众观看问题才解决。我的经验是一个系统的传输层方案从来不是单一协议一统天下HLS 和低延迟协议完全可以共存。你只需要在最外面按场景做分流该走哪条路就走哪条路。做 HLS 相关项目这几年我最大的体会是协议本身不复杂真正复杂的是分片、缓存、播放器、CDN 这几层凑在一起时的交互。遇到问题不要慌把 M3U8 拉到本地把分片请求逐条看一遍答案往往就藏在那些文本行和状态码里。希望这篇能把你看 HLS 的最后一层窗户纸捅破。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询