尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HLS-M3U8直播点播详解:视频切片+AES加密+多码流自适应

HLS-M3U8直播点播详解:视频切片+AES加密+多码流自适应 直播点播里的HLS绝对不是只有“切片”这么简单。很多人看网上的教程只会拿着M3U8文件往播放器里一塞能出画面就完事。可真到了生产环境你会碰到花屏、起播慢、音画不同步、加密后黑屏、码流切换卡顿这一堆糟心事。这篇文章就围绕“HLS-M3U8直播点播详解【视频切片AES加密多码流自适应】”这个主题把我多年折腾HLS协议的经验、踩过的坑、优化过的方案全部摊开讲清楚。无论你是用Vue写播放器页面、用ffmpeg做转码切片还是直接对接安防设备的HLS流地址这篇文章应该都能给你一个比较完整的参考答案。1. 内容整体设计与思路拆解1.1 HLS到底解决了什么问题HLSHTTP Live Streaming是Apple提出来的一套基于HTTP的流媒体协议核心思路是“化整为零”。一个完整的视频流不再是被播放器一次性拉到底的单个大文件而是被切成无数个只有几秒钟的小片段每个片段通过普通的HTTP请求去拉取。为什么这么做最大的好处是穿透性好。HTTP协议是互联网的通用语言不需要专门的流媒体服务器任何能放静态文件的服务器都能当HLS的源站CDN也能以最标准的方式缓存和分发这些片段。第二个好处是自适应能力强播放器可以根据当前网速动态地从多个码率的切片中选一个最合适的去拉这就是多码流自适应。第三个好处是容错性好某个分片失效最多卡一下不太会整个播放失败。但HLS也有明显的代价延迟比较高。因为必须要等到一个切片生成完毕并且播放器还要缓冲几个切片之后才开始播放所以HLS直播的延迟通常在10秒到30秒之间。这在演唱会直播、赛事直播里还能接受但在连麦、视频会议这种强交互场景里就完全不行。所以在做技术选型时你要先问自己一个问题这个直播场景到底需不需要低延迟1.2 为什么这三点要放在一起讲视频切片、AES加密、多码流自适应这三个词看着是独立技术点但在实际项目里是强耦合的。切片是基础只有把视频切成小片段多码流自适应才有实施的空间——因为每个片段都可以单独选择码率AES加密也依赖切片——每个片段都被独立加密解密片段时通过索引文件里的密钥信息去获取对应的解密key。换句话说这三者是同一套索引体系下的三个层面的问题。我见过很多项目切片用一套方案加密用另一个人写的模块播放器又是第三方的结果一到联调就各种对不上。其实问题不在于某个环节做得不好而是没有把切片、加密、自适应放在同一个框架里去设计。这篇文章的思路是先建立一个总体的协议视角再分别拆解每个技术环节最后用一个完整的示例串起来这样你在做方案设计的时候心里会有一条清晰的链路。1.3 哪些人适合读这篇用Vue、React等前端框架做播放器开发的经常碰到“vue播放m3u8报错”的同学。后端或运维同学需要用ffmpeg把视频转成m3u8切片或者需要给点播视频加一层简单的加密保护。做安防、物联网平台集成的对接海康、大华等设备的HLS流地址例如“海康综合安防管理平台的web取hls流的api”。对流媒体原理感兴趣想搞明白“m3u8索引”背后到底是什么规则的开发者。2. 视频切片技术详解2.1 M3U8索引文件的结构很多人第一次打开m3u8文件看到一堆“#”开头的文本就懵了。其实它是一个播放列表playlist本质就是一个UTF-8编码的文本文件里面记录的是视频分片的地址和播放顺序。一个最基础的点播m3u8长这样#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.0, segment0.ts #EXTINF:10.0, segment1.ts #EXTINF:5.0, segment2.ts #EXT-X-ENDLIST这里的每一行都有讲究#EXTM3U声明这是个m3u8播放列表没有它播放器不认。#EXT-X-VERSION协议的版本号一般3或4比较通用。#EXT-X-TARGETDURATION每个分片的最大时长限制播放器会根据这个值设置缓冲策略。#EXTINF后面跟的是分片的时长单位秒紧接着的下一行就是这个分片的URL。#EXT-X-ENDLIST表示点播列表结束了。直播列表没有这一行且会不断更新。如果是多码流的场景m3u8文件里面装的不是分片地址而是子播放列表的地址这个叫Master Playlist。我后面会专门讲。2.2 切片时长到底选几秒切片时长的大小直接影响观众的起播体验、播放稳定性和服务器压力。2秒到4秒起播快直播延迟低但分片数量多HTTP请求数成倍增长对服务器和CDN的压力大。6秒到10秒比较折中的选择也是多数点播平台的默认值。15秒以上分片数量少服务器压力小但起播慢、直播延迟高而且一旦中间有坏片观众的“卡顿感”会被放大。我的实际经验是直播优先选4秒到6秒点播按内容类型选6秒到10秒。不推荐用2秒除非你的CDN和源站性能非常好否则在弱网环境下播放器还没来得及把2秒的切片下载完就会出现缓冲。用ffmpeg转切片时控制切片时长的核心参数是-hls_time。比如ffmpeg -i input.mp4 -c:v libx264 -c:a aac -hls_time 6 -hls_playlist_type vod output.m3u8这个命令生成的是点播类型的hls-hls_playlist_type vod会让ffmpeg在生成完所有切片后自动添加#EXT-X-ENDLIST。2.3 切片格式TS还是FMP4传统HLS用的是MPEG-TS格式扩展名通常是.ts。TS格式的好处是兼容性好几乎所有播放器都支持。但TS封装比较“古老”体积大而且它必须从分片边界开始解码如果切片边界没有对齐关键帧会出现花屏或者起播时黑屏几秒。后来Apple主导了**HLS支持FMP4Fragmented MP4**的方式分片文件扩展名是.m4s或直接用.mp4。FMP4的优势是封装效率更高、和DASH等其他协议可以共用切片但兼容性不如TS老一点的播放器可能不支持。给个建议如果你的播放器环境是你自己控制的比如自研App可以优先考虑FMP4如果是要对外分发给各种来源不明的播放器稳妥起见还是用TS。2.4 ffmpeg切片的实战命令下面是我在点播转码场景中比较常用的一套命令ffmpeg -i input.mp4 \ -c:v libx264 -preset veryfast -g 48 -keyint_min 48 \ -sc_threshold 0 \ -c:a aac -b:a 128k \ -f hls \ -hls_time 6 \ -hls_playlist_type vod \ -hls_segment_filename output_%03d.ts \ output.m3u8解释一下几个关键参数-g 48设置GOP关键帧间隔大小为48帧。在25fps的视频里48帧就是约2秒一个关键帧。切片点尽量对齐关键帧这样播放器在切换分片时不需要从头解码。-keyint_min 48最小关键帧间隔避免编码器在某些场景下插入过多关键帧。-sc_threshold 0禁用场景切换检测自动插入关键帧保证切片对齐。-hls_segment_filename指定分片文件的命名规则。-preset veryfast加快转码速度适合大规模并行转码。如果对画质要求很高可以换成medium或slow但CPU开销会明显增加。切片对齐这件事说实话是新手最容易忽略的。如果GOP长度大于切片长度播放器在切片边界会遇到没有关键帧的情况就只能等下一段关键帧表现为“画面卡在首帧很久才出来”。2.5 直播切片的特殊点点播切片是“一次性切完”直播切片是“边推流边切边更新索引”。ffmpeg也能做直播转切片ffmpeg -i rtmp://input/live/stream \ -c:v copy -c:a copy \ -f hls -hls_time 4 -hls_list_size 6 \ -hls_flags delete_segments \ /var/www/hls/live.m3u8这里的-hls_list_size 6表示m3u8索引里只保留最新6个分片的记录配合-hls_flags delete_segments旧的切片会被自动删除。这样直播索引文件不会无限变大磁盘占用也能控制住。但直播场景里切片文件和索引的更新有一个“同步窗口”问题。如果播放器在索引更新的间隙来拉取可能拿到一个删掉了部分历史分片的列表导致起播失败。解决方法是规划好切片的保留策略比如live窗口保留30到60秒既能让新观众快速起播又不会占用太多存储。3. AES-128加密原理与实现3.1 HLS的加密机制到底长什么样HLS的加密采用的是AES-128-CBC模式。意思是每个视频分片都用AES-128算法以CBC模式加密。密钥通常是一个16字节的值。m3u8索引文件中通过#EXT-X-KEY标签来告诉播放器解密信息。一个典型的加密m3u8长这样#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-KEY:METHODAES-128,URIkey.key,IV0x00000000000000000000000000000000 #EXTINF:10.0, segment0.ts #EXTINF:10.0, segment1.ts #EXT-X-ENDLIST#EXT-X-KEY里的三个字段非常重要METHOD固定为AES-128表示加密方式。URI密钥文件的地址可以是相对路径也可以是绝对URL。IV初始化向量可以显式指定也可以不写。如果不写默认用分片在序列中的序号作为IV。这里的逻辑要注意密钥是用来对称解密分片的它本身必须能被播放器安全地取到。如果你把key直接放在和m3u8同一个目录那么任何能访问m3u8的人也能访问key这个加密就只能防“误点链接”的普通用户防不了懂技术的人。3.2 为什么不建议自己发明加密算法有些同学会想既然要防下载能不能把分片里的字节做一次异或、做一次翻转甚至base64伪装一下这样播放器就播不了了这个思路方向是好的但很难落地。原因很简单任何自定义加密都需要播放器端做对应的自定义解密。如果你的播放器是开源的、自己写的那没问题但如果你用的是现成的播放器库比如video.js、hls.js它们只认HLS标准里的AES-128加密方式。一旦你用了“魔改”方案播放器就废了。另一个问题是自定义加密很容易出错。你以为你“加密”了其实只是给数据做了个变形而CBC模式下的数据必须按16字节对齐稍微处理错一个字节解密出来的画面就会是花的。所以我强烈建议除非你是播放器SDK的开发者否则遵循HLS标准使用AES-128加密。它足够成熟所有主流播放器都内置支持不需要你自己写解密逻辑。3.3 密钥怎么生成、怎么存、怎么分发密钥是16字节的随机数据。用openssl生成很直接openssl rand 16 key.key有了key文件之后用ffmpeg加密切片ffmpeg -i input.mp4 \ -c:v copy -c:a copy \ -hls_key_info_file enc.keyinfo \ -hls_playlist_type vod \ output.m3u8这个enc.keyinfo文件是一个三行文本key.key key.key iv_hex.txt第一行是密钥文件的路径第二行是播放器可以访问到的密钥URI注意区分本地路径和公网URL的区别第三行是IV值。举个例子如果你的m3u8部署在https://example.com/video/playlist.m3u8密钥放在同一个目录下那么enc.keyinfo里的第二行就写key.key这样播放器会相对路径去请求。如果你想单独用一个密钥服务器接口去分发密钥这行就写成https://example.com/api/video/key?id123这样的接口地址。密钥分发是我要重点强调的环节。很多项目把key文件和m3u8放在同一个目录甚至直接用静态路径暴露出去那等于加密形同虚设。我比较推荐的做法是动态接口下发密钥播放器请求m3u8时拿到key的URI再向这个URI发起请求。服务端校验请求是否带有合法身份凭证比如用户的登录态、防盗链签名校验通过才返回密钥字节。这样即使m3u8被转发出去没有合法身份的人也拿不到key。URL签名防盗链如果你不想做太复杂的鉴权系统可以给key的URL加一个有效期签名比如时间戳MD5几十分钟就过期。这样被抓到的key链接过一段时间自然失效。密钥定期轮换如果是直播场景建议周期性地更换密钥。HLS协议支持在m3u8中间更新#EXT-X-KEY标签配合切片序列号可以实现加密密钥的滚动更新。3.4 加密后播放黑屏/报错的排查思路我见过太多人加密完之后视频死活放不出来然后就开始怀疑加密算法不对。其实大部分问题出在下面几个点第一URI路径不对。播放器去请求key的时候如果返回404加密后的切片就无法解密报错通常是hls error, type: mediaError, details: fragParsingError。我遇到过一个案例key文件路径写的是本地绝对路径/home/user/key.key这在服务端可以读取但播放器在浏览器里访问这个URI直接404。正确的做法是把密钥放到静态资源目录或者用一个URL映射过去。第二IV不匹配。HLS的AES-128默认IV是分片序号如果用mux.js或者某些播放器库对IV的处理有差异会导致解密失败。最稳妥的做法是在#EXT-X-KEY里显式写上IV0x...并且保证它和加密时一致。第三CORS问题。如果播放器页面在a.com而m3u8和key在b.com那么浏览器请求key时b.com必须返回允许a.com跨域的响应头。这个坑前端同学特别容易踩因为本地开发的时候页面都是localhost一旦部署到测试环境域名变了就开始报各种奇怪的跨域错误。3.5 关于“AES加密盐放后端”的思考热词里提到了“AES加密盐放后端”。在HLS的语境下“盐”这个理解会有些偏差。HLS加密本身不需要“盐”密钥就是那个16字节随机数。但如果你是在自己构建一个视频上传、转码、播放的完整系统并且对存储和转码中间环节有更高安全要求那么你可以把密钥生成、密钥存储都放在后端。具体来说前端上传视频到后端后端生成一个随机key把它存到专门的密钥管理服务里。转码服务从密钥服务里读取key用这个key做AES-128加密切片。播放器请求m3u8时先通过后端鉴权后端动态生成一个临时有效的key URI这个URI带有签名播放器拿着这个临时URI去获取密钥。这套流程的好处是用户拿到的一切都是“一次性”或者“短期有效”的即使被录屏或者被抓包也很难长期复用。4. 多码流自适应实现与调优4.1 Master Playlist到底是什么多码流自适应不是播放器“猜”出来的而是通过一个**Master Playlist主播放列表来声明的。它里面不直接包含视频分片而是包含多个Media Playlist媒体播放列表**的地址以及各自对应的码率、分辨率等元数据。看一个典型的Master Playlist#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH800000,RESOLUTION640x360,CODECSavc1.4d401e,mp4a.40.2 stream_360.m3u8 #EXT-X-STREAM-INF:BANDWIDTH1500000,RESOLUTION1280x720,CODECSavc1.4d401e,mp4a.40.2 stream_720.m3u8 #EXT-X-STREAM-INF:BANDWIDTH3000000,RESOLUTION1920x1080,CODECSavc1.4d401e,mp4a.40.2 stream_1080.m3u8BANDWIDTH是预估带宽单位是bps它包含音频和视频的码率总和。播放器在选择码流时并不是纯看当前网速测出来的数字而是先看一下这个BANDWIDTH值再结合自己的缓冲情况做决策。4.2 播放器如何做自适应切换以hls.js为例它会周期性监测当前的下载速度和缓冲区余量。当发现当前码率的下载速度远高于码率本身时会尝试切换到更高码率的码流让画面更清晰当发现下载速度跟不上码率且缓冲区越来越少时会主动降到更低码率的码流保证不卡顿。这里要注意播放器切换码流时是在两个媒体播放列表之间横跳。切到新的码流后播放器要从新的列表里寻找一个时间点这个时间点附近必须有关键帧否则就无法开始解码。这也就是为什么转码时必须保证所有码流的切片边界对齐到相同的时间点。如果你的多码流切片之间对不齐会出现什么问题最典型的就是切换的瞬间出现画面冻结、花屏或者在直播场景里播放器一切换码流就跳到之前的某个时间点观众看到的是“回退”了几秒的画面。4.3 用ffmpeg生成多码流切片转多码流切片时我一般分两条路走。第一条路一条命令直接生成多个码流变体。ffmpeg支持多个map可以用一条命令生成多码率的切片和对应的m3u8。ffmpeg -i input.mp4 \ -filter_complex [0:v]split3[v1][v2][v3]; \ [v1]scale640:360[v1out]; \ [v2]scale1280:720[v2out]; \ [v3]scale1920:1080[v3out] \ -map [v1out] -map 0:a -c:v:0 libx264 -b:v:0 800k -c:a aac -b:a 96k \ -f hls -hls_time 6 -hls_playlist_type vod -hls_segment_filename hls/360p_%03d.ts hls/360p.m3u8 \ -map [v2out] -map 0:a -c:v:1 libx264 -b:v:1 1500k -c:a aac -b:a 128k \ -f hls -hls_time 6 -hls_playlist_type vod -hls_segment_filename hls/720p_%03d.ts hls/720p.m3u8 \ -map [v3out] -map 0:a -c:v:2 libx264 -b:v:2 3000k -c:a aac -b:a 128k \ -f hls -hls_time 6 -hls_playlist_type vod -hls_segment_filename hls/1080p_%03d.ts hls/1080p.m3u8这条命令很长但原理不复杂先把视频源切成三路分别缩放成不同分辨率再用不同的码率编码最后各自输出切片。这种方式适合在你自己的转码服务器上做批处理。第二条路分步转码用工具自动生成Master Playlist。实际生产里我更推荐分步做因为一旦输入规格变了比如遇到竖屏视频、超高清视频一条大命令要调整的参数太多很容易出错。分步转码后再手动生成或用一个脚本生成Master Playlist便于排查问题。4.4 自适应参数选择的心得码率档位的设置有一个基础原则档位之间的码率差值不要过大并且最低档必须能覆盖最差网络环境。我常用的一套档位设计档位分辨率视频码率音频码率带宽估算(BANDWIDTH)低480x270350kbps64kbps500000中854x480800kbps96kbps1000000高1280x7201500kbps128kbps1800000超清1920x10803000kbps128kbps3300000如果只做两档比如只分720p和1080p中间会有个“断档”。在网速刚好卡在720p能播但1080p不行的区间时播放器会频繁上下切换观感很差。另外CODECS字段要写准确。如果CODECS写错了比如实际编码是H.264 Baseline却写成了High有些播放器可能会错误地认为设备不支持直接拒绝播放。4.5 自适应在实际直播中的表现直播的多码流自适应比点播更考验系统的稳定性。直播源是实时的每个码率的播放列表都在持续更新。如果转码服务器的性能不够可能导致某个码率的分片生成延迟别的码率都更新到第100个分片了这个码率还在第95个分片打转。播放器切换到这个慢码率时看到的画面就会比别的码率慢几秒甚至出现时间错位。解决思路是转码模块要保证所有码率的切片输出节奏保持一致如果某个码率编码太慢宁可适当降低它的分辨率也不要让它拖慢整个自适应链路。5. 工具、播放器与常见应用场景5.1 Vue播放m3u8的正确姿势热词里出现了“vue播放m3u8”这是前端开发的高频需求。如果你用的是video.jsm3u8是通过hls.js或者videojs-contrib-hls插件来支持的。我的习惯是直接用hls.js因为它的控制力更强错误处理也更透明。一个最简单的Vue组件思路是import Hls from hls.js export default { mounted() { const video this.$refs.video if (Hls.isSupported()) { const hls new Hls({ maxBufferLength: 30, maxMaxBufferLength: 60 }) hls.loadSource(this.src) hls.attachMedia(video) hls.on(Hls.Events.ERROR, (event, data) { if (data.fatal) { switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: hls.startLoad() break case Hls.ErrorTypes.MEDIA_ERROR: hls.recoverMediaError() break default: hls.destroy() break } } }) } else if (video.canPlayType(application/vnd.apple.mpegurl)) { video.src this.src } } }几个参数补充说明maxBufferLength最大缓冲长度秒设置太大会导致高码流下下载量过大设置太小又容易频繁缓冲。manifestLoadingTimeOutm3u8列表请求的超时时间直播场景建议调大到10000毫秒以上因为直播切片服务器的响应可能不稳定。fragLoadingMaxRetry分片请求的最大重试次数线上环境建议至少3次。报错方面高频出现的hls error, type: mediaError, details: fragParsingError多半是拿到的分片数据不完整或已损坏。先检查网络层是不是有代理拦截了请求再检查源文件是不是在切片生成过程中被覆盖。5.2 ffmpeg m3u8转mp4为什么总失败“ffmpeg m3u8转为mp4命令”这个需求更多出现在视频下载、素材剪辑场景。最简单的方式是ffmpeg -i https://example.com/video/playlist.m3u8 -c copy output.mp4这个命令的底层逻辑是ffmpeg先读取m3u8索引然后逐个下载所有分片再按顺序封装成mp4。如果所有分片编码参数一致且m3u8没有加密那大概率能成功。但如果失败通常是因为加密问题m3u8里有#EXT-X-KEYffmpeg需要你额外提供key文件。用hls_key_info_file参数或者在ffmpeg命令行里指定-hls_key_info_file是转码时用的下载场景需要用支持解密的ffmpeg版本并且确保key的URI可访问。分片时间戳不连续某些直播流转成的点播切片时间戳可能有跳动导致封装mp4时出现错误。可以用-fflags genpts重新生成时间戳。网络问题一个分片下载失败整个任务就中断。可以先把m3u8和分片下载到本地再执行本地转码这样排障更简单。5.3 安防摄像头的HLS流对接海康、大华这些安防设备现在普遍支持输出HLS流。通过它们的HTTP API可以拿到类似http://device_ip:port/.../live.m3u8这样的地址。在Web平台里播放本质上和播放其他HLS流没有区别直接用hls.js或video.js即可。但有一个坑要注意安防设备的HLS流并发能力很弱。一台普通摄像头能承受的同时拉流数量极其有限如果你在Web平台里同时打开几十路监控画面设备很可能直接挂掉。常见的处理方案是流媒体网关——让设备只推一路RTSP或SDK流到一台流媒体服务器由流媒体服务器对外输出多路HLS流。用户看的是网关的流而不是直接打设备的流。我之前遇到过一个真实的案例一个园区监控项目前端Vue平台要同时显示16路摄像头画面。一开始直接拿摄像头的HLS地址往video标签里塞结果设备频繁死机。后来改成流媒体网关统一接入问题才彻底解决。5.4 下载工具的边界网上有不少带“m3u8下载”插件的浏览器或者独立工具比如“x浏览器m3u8插件”、“菠萝.m3u8”这类关键词指向的下载场景。从技术角度看下载m3u8视频的原理都是一致的解析索引 - 并发拉取分片 - 合并转封装。如果你需要下载的是自己的视频、已授权的公开视频没什么问题。但如果拿去扒别人的付费课程政策风险和技术风险都很高。我建议如果你在工作中真正需要批量下载m3u8视频先去确认版权归属。技术讨论归技术讨论别把工具用在灰色地带。5.5 嵌入式场景STM32上的AES热词里有“stm32 aes加密”。如果你在做嵌入式设备比如需要把采集到的视频加密后再通过网络传输出去那么STM32上实现AES加密通常会用到硬件加密加速器有些系列有独立的AES外设或者用mbedtls这样的库来做软件AES。HLS的AES-128-CBC模式在STM32上实现也是可以的但要注意性能问题。如果你的嵌入式处理器主频不高对每个视频分片做全量软件加密CPU占用会很高。实际项目中我更推荐在嵌入式设备里只做“分片后的封装加密”把切片这个重活交给上位机或者边缘计算网关完成。6. 常见问题与排查技巧实录6.1 花屏、黑屏、起播慢这几个问题看似不同但根因常常是相通的。黑屏且无报错先看m3u8内容确认#EXT-X-KEY和分片文件路径是否正确。再看播放器是否加载了正确的解码器浏览器环境下H.264没问题但如果源视频是H.265编码很多浏览器是不支持的。起播慢直播场景尤其明显。罪魁祸首通常是播放器必须等到第一个分片生成并且缓冲到一定时长才开始播放。优化入手点一个是缩短切片时长另一个是让播放器的liveSyncDurationCount设小一点允许它在拿到较少的缓冲时就先播起来。花屏优先怀疑关键帧对齐出了问题。确认切片工具是否做到每个分片都以关键帧开头且关键帧间隔不大于切片时长。6.2 分片下载失败与重试策略弱网环境下播放器请求某个分片失败是常态。hls.js默认有重试机制但如果你发现一片区域经常卡住很可能是因为CDN上某个分片缺失了。排查思路看浏览器Network面板找到一直在重试的分片URL。直接访问这个分片URL确认是404还是超时。如果404回到源站检查对应分片文件是否存在。有可能是在发布时漏传了某个分片。如果超时考虑是不是CDN回源链路的问题。6.3 多码流切换时卡顿的排查前面提到过多码流切换需要各码流的时间轴对齐。用ffprobe可以检查各码流分片的时间戳ffprobe -show_frames -select_streams v hls/720p_000.ts ffprobe -show_frames -select_streams v hls/1080p_000.ts对比起始PTS值如果差异很大说明转码时没有对齐切片边界。解决方法是统一-g参数同时用-force_key_frames expr:gte(t,n_forced*6)这种方式强行在固定时间点插入关键帧。6.4 播放器报“network error”但网络正常这种情况下建议抓包看m3u8请求的响应头。常见响应头问题包括Content-Type不是application/vnd.apple.mpegurl。缺少Access-Control-Allow-Origin头。Cache-Control设置不当导致播放器拿到旧的m3u8。特别是直播流如果CDN对m3u8做了强缓存播放器会一直拿到旧列表导致直播画面永远停在某一刻。直播场景的m3u8响应头要设置为Cache-Control: no-cache或者很短的过期时间。6.5 小技巧如何快速判断一个m3u8是否有加密、是否有自适应码流拿到一个m3u8第一件事不要急着丢给播放器先用文本编辑器打开看看。顶部如果出现多个#EXT-X-STREAM-INF说明是多码流Master Playlist。如果有#EXT-X-KEY说明有加密还要注意URI字段是不是可访问的。最后一行如果只有#EXT-X-ENDLIST是点播如果一直在变、没有ENDLIST是直播。这个习惯能帮你避开很多无效排障。我排过太多“播放器报错”的问题最后发现是拿错了文件类型——明明是一个Master Playlist却被媒体播放器当成Media Playlist去解析。7. 从零搭一个HLS点播加密小系统7.1 总体结构光讲原理不够给出一套最小可运行的方案。假设我们要做一个加密HLS点播服务流程是后端上传原始视频。转码服务调用ffmpeg生成多码率加密切片。Nginx托管切片、m3u8和密钥。Web前端通过Vue播放器播放。7.2 转码与加密的完整命令行假设原始视频是input.mp4密钥文件已经生成并且我们写好了enc.keyinfo/opt/keys/key.key /key.key 0x00000000000000000000000000000000转码命令ffmpeg -i input.mp4 \ -filter_complex [0:v]split2[v1][v2];[v1]scale854:480[v1out];[v2]scale1280:720[v2out] \ -map [v1out] -map 0:a -c:v:0 libx264 -b:v:0 800k -c:a aac -b:a 96k \ -f hls -hls_time 6 -hls_playlist_type vod -hls_key_info_file enc.keyinfo \ -hls_segment_filename hls/480p_%03d.ts hls/480p.m3u8 \ -map [v2out] -map 0:a -c:v:1 libx264 -b:v:1 1500k -c:a aac -b:a 128k \ -f hls -hls_time 6 -hls_playlist_type vod -hls_key_info_file enc.keyinfo \ -hls_segment_filename hls/720p_%03d.ts hls/720p.m3u8执行完成之后hls/目录下会有两套切片和两个媒体播放列表。接下来手动创建Master Playlist或者写个脚本自动生成。7.3 Master Playlist的手动生成#EXTM3U #EXT-X-STREAM-INF:BANDWIDTH1000000,RESOLUTION854x480,CODECSavc1.4d401e,mp4a.40.2 480p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH1800000,RESOLUTION1280x720,CODECSavc1.4d401e,mp4a.40.2 720p.m3u87.4 密钥接口的实现思路密钥不能做成静态文件否则加密没有意义。我用Node.js做过一个简单的密钥接口const crypto require(crypto) const fs require(fs) app.get(/key.key, (req, res) { const token req.query.token if (verifyToken(token)) { const key fs.readFileSync(/opt/keys/key.key) res.set(Content-Type, application/octet-stream) res.send(key) } else { res.status(403).send(Forbidden) } })这个接口的逻辑是m3u8里的key URI指向/key.key?tokenxxx前端播放器在请求m3u8时后端会在m3u8内容里动态替换token参数或者由前端从登录态里获取token拼上去。verifyToken可以校验token有效期比如30分钟过期。7.5 前端播放Vue组件要点前端要做的很简单拿着Master Playlist的URL交给hls.js处理即可。需要注意的点是如果key接口有鉴权那么hls.js在请求key时也要带上凭证。可以用hls.config.loadPolicy或者xhrSetup给请求添加自定义header比如Authorization头。const hls new Hls({ xhrSetup: (xhr, url) { if (url.includes(/key.key)) { xhr.setRequestHeader(Authorization, Bearer getToken()) } } })这样整个链路就通了播放器拉取m3u8 - 解析分片地址和key地址 - 带token请求key - 解密分片 - 播放。8. 最后的一些经验和建议做HLS这一整套东西我最大的感受是协议本身不复杂复杂的是各种播放器和环境之间的兼容性差异。同一个m3u8在Chrome里能播在Safari里能播在微信内置浏览器里就可能出问题。同一个加密流在hls.js里正常在video.js里就报密钥错误。所以接入HLS时一定要把目标播放器和目标用户的网络环境纳入设计考量。在实际运维中我一直保持着几个习惯分享出来供你参考。第一个习惯是保留转码日志。每次转码、切片、加密操作都把完整的ffmpeg命令行和输出日志存下来出问题能快速回溯到底是什么参数导致的。第二个习惯是对密钥密钥的访问要严格。就算只做一个内部小系统也建议给key接口加上最简单的token校验而不是直接把key文件放在m3u8旁边。HLS的AES加密从来就不是绝对安全它的目的是提高下载门槛而不是防住所有黑客。第三个习惯是在正式上线前做弱网测试。Chrome DevTools可以模拟网络限速按3G网络条件测试一下起播时间、卡顿频率、码流切换表现。很多在办公室千兆网络下跑得好好的流到了移动网就一塌糊涂。最后再补充一个小技巧排查m3u8问题的时候要多看一眼响应头。我见过一个项目播放器一直报分片解析错误抓了好久的包最后发现是服务器给m3u8返回了Content-Type: text/plain部分播放器因为MIME类型不对直接拒绝解析。把响应头改成application/vnd.apple.mpegurl后问题立刻消失。这种细节不起眼但往往就是整条链路上最坑的一点。
返回列表