1. 内容整体设计与思路拆解
1.1 为什么要聊HLS和M3U8:从直播点播的“格式焦虑”说起
先澄清一个很多人踩过的误区:HLS和M3U8不是同一个东西,但互联网上几乎总是把它们绑在一起提。HLS(HTTP Live Streaming)是苹果公司在2009年推出的流媒体传输协议,而M3U8只是HLS协议里用来描述媒体播放列表的文件格式。你可以把M3U8理解成一张“菜单”,视频数据被切成很多小块,每块对应菜单上的一个“菜名”,播放器根据菜单去HTTP服务器上逐个取菜、上菜。
我做视频这块做了不少年头,接触到HLS时最大的感受是:它把“流媒体”这个听起来很高端的问题,硬生生变成了“静态文件管理”问题。为什么这么说?因为HLS的一大核心设计就是“先切碎、后分发”——服务器端不需要维持长连接,不需要专门处理推流状态,客户端自己按节奏去拉取切片文件,天然适配CDN,也天然能穿透绝大多数普通网络环境。
再聊M3U8。虽然M3U8是一种文件格式,但不同场景下M3U8的内容差异非常大。直播场景下,M3U8是“动态更新的播放列表”,旧的切片会被移出,新的切片会不断追加;点播场景下,M3U8是“静态完整的索引”,从第一个切片列到最后一个切片,顺序固定。很多新手拿到一个M3U8地址后,先用浏览器直接访问,发现输出一堆看不懂的URI路径,就开始懵。实际上,M3U8里写的不只是路径,还包含每个切片的时长(EXTINF)、加密信息(EXT-X-KEY)、码流切换关系(EXT-X-STREAM-INF)等,这些字段才是HLS的精髓。
这篇文章适合谁看?如果你是做音视频开发的、写播放器的、做CDN调度的、或者只是想在自家网站上挂一个不那么容易被拖走的视频服务,那么HLS+M3U8这套组合你迟早要碰。我会把视频切片、AES加密、多码流自适应这三个核心点拆开讲透,再用一个完整的实操案例把它们串起来。
1.2 标题背后的三个核心技术锚点
这个项目标题“HLS-M3U8直播点播详解【视频切片+AES加密+多码流自适应】”其实是一张非常标准的HLS技术地图。
第一块是视频切片。切片是HLS的根,没有切片就没有M3U8,也就没有HLS。切片的粒度、格式、命名规则、时长对齐,都直接决定播放体验和兼容性。
第二块是AES加密。HLS最让内容方安心的点就是原生支持AES-128加密,切片即使被下载下来,没有密钥也只是一堆乱码。加密涉及两个关键环节:切片本身的加密方式,以及M3U8里EXT-X-KEY标签的生成。很多人只处理了切片加密,却忘了密钥文件的跨域访问问题,结果播放器在线上环境里报错。
第三块是多码流自适应。HLS不生产码流,它只是码流的“调度员”。不同清晰度对应不同的M3U8子列表,主M3U8用EXT-X-STREAM-INF标签把它们聚合起来,播放器根据带宽自动选择或手动切换。这块涉及编码参数选择、切片对齐、命名规范等多个细节。
三块内容看着独立,实际串联起来就是一次完整的HLS生产链路。下面我先把关键技术点拆开讲,然后给你一套能直接落地的方案。
2. 核心细节解析与实操要点
2.1 视频切片:不是简单把MP4切成几段
视频切片是HLS的基础,但它不是把MP4文件按时间均匀切几刀就完事。家用场景还好,一旦涉及专业制作,切片前就要关注两个编码层面的约束:H.264/H.265编码、AAC音频编码,而且要保证每个切片的GOP(Group of Pictures)结构独立完整。
为什么要强调GOP完整?因为播放器拉到一个切片后,要把这个切片独立解码出来。如果切片内部的I帧不在切片开头,播放器就必须从上一个切片开始解码等待关键帧,那首播时间就会拉长,切换码流时还会出现花屏或黑屏。所以正规的切片工具(比如FFmpeg)都支持在切片时强制做GOP对齐,简单说就是“一个切片内部至少包含一个I帧,而且最好以I帧开头”。
切片时长通常选2到10秒,我自己的项目一般选4到6秒。太短会导致切片文件过多、HTTP请求太频繁,拉高服务器压力和播放器的请求开销;太长则影响直播延迟和拖动进度时的加载速度,因为播放器通常要拿到一个完整的切片才能开始解码。
再就是切片格式。最常见的封装格式是MPEG-TS(Transport Stream),它天生适合流媒体传输,支持时间戳、多音轨、字幕等几乎HLS需要的所有特性。HLS最初只支持TS切片,后来才加入了fMP4(Fragmented MP4)的支持。fMP4体积更小、兼容性也逐步跟上,但如果你的目标是最大兼容面,比如老电视盒子、旧手机,TS仍然是最稳的选择。
实操中我也常用FFmpeg做切片,一个典型的切片命令是:
ffmpeg -i input.mp4 \ -c:v libx264 -c:a aac \ -force_key_frames "expr:gte(t,n_forced*4)" \ -f hls -hls_time 4 -hls_list_size 0 \ -hls_segment_filename "output_%04d.ts" output.m3u8这里-force_key_frames强制每4秒生成一个关键帧,-hls_time 4指定切片时长,-hls_list_size 0表示生成完整的点播索引而不限制列表条目数。对于直播场景,-hls_list_size一般设置为5到10,让播放器只保留最近几秒的切片地址,老切片会被自动清理或进入下一个分片周期。
2.2 AES加密:保护的不是切片,而是密钥
HLS的AES加密并不复杂,核心思路是:对每个TS切片用AES-128-CBC算法加密,密钥(Key)单独保存为一个二进制文件,播放器先下载M3U8,看到EXT-X-KEY标签后下载密钥,再解密切片播放。
这里有一个很多人想不明白的点:既然播放器都能下载到密钥,那加密到底防谁?答案是防“随便下载一个TS文件就能播放”的普通用户,而不是防专业盗取者。密钥文件只要放在HTTP目录下,技术稍好一点的人都能拿到,所以HLS加密本质上是“提高门槛”,而不是“绝对安全”。要进一步提高安全性,一般会把密钥文件的URL做成短期有效,或者结合业务鉴权系统动态生成密钥地址。
EXT-X-KEY标签长这样:
#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/keys/key.bin",IV=0x00000000000000000000000000000001METHOD有三个常见值:NONE表示不加密,AES-128表示使用AES-128加密,而SAMPLE-AES用于HLS对加密内容的多码流支持。URI就是密钥文件的地址,IV是初始化向量。如果M3U8里不写IV,播放器默认使用切片序号作为IV,多数工具生成的切片也都是这个逻辑。
AES-128模式下,很多新手会遇到一个疑惑:为什么同一个明文、同一个密钥,每次加密出来的密文都不一样?这就要说到CBC模式了。CBC(Cipher Block Chaining)模式下,每个明文分组会先和上一个密文分组做异或,再执行AES加密。第一个分组没有“上一个密文”,所以需要IV来充当这个角色。如果你的IV是随机的,那么每次加密结果自然不同;如果IV固定,同样的明文加密结果就是一样的。HLS里IV通常由M3U8指定或默认取切片序号,所以同样一份切片,放在不同序号位置,加密后的二进制也不一样,这是正常现象,不是bug。
网上流传的那些“用OpenSSL手动给TS加密再改M3U8”的做法,原理上可行,但远不如直接用FFmpeg一条命令方便:
ffmpeg -i input.mp4 \ -c:v libx264 -c:a aac \ -hls_time 4 -hls_list_size 0 \ -hls_key_info_file key_info.txt \ -hls_segment_filename "encrypted_%04d.ts" encrypted.m3u8key_info.txt文件内容格式如下:
key.bin http://yourdomain.com/keys/key.bin 0123456789abcdef0123456789abcdef第一行是本地密钥文件名,第二行是M3U8中URI字段要写的密钥的远程访问地址,第三行是IV的十六进制串(可选,不写则默认按切片序号生成)。密钥文件本身可以用OpenSSL生成:
openssl rand 16 > key.bin2.3 多码流自适应:不仅仅是多存几份视频
多码流自适应的核心文件是主M3U8,它的内容不是切片列表,而是指向不同清晰度子M3U8的“路由表”:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1920x1080 1080p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=1280x720 720p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=854x480 480p/index.m3u8播放器读取主M3U8后,根据BANDWIDTH(带宽)和RESOLUTION(分辨率)等信息选择初始码流,然后在播放过程中持续监测网络速度,必要时自动切到更高或更低的子M3U8。这个机制最大的难点在于:不同码流的切片必须保持“时间对齐”,否则切换码流时画面会跳变,甚至出现音画不同步。
什么叫时间对齐?比如你有一个60秒的视频,切成4秒一个切片,那三个码流都应该有15个切片,每个切片覆盖的时间范围也相同。FFmpeg不能直接把一个输入源同时生成多码流切片,但可以逐个处理,关键是输入源相同、切片参数一致,生成的切片数量和时间戳自然能对齐。
我在项目中常用的做法是一次性转出三个清晰度,再用一个本地目录把它们按固定目录结构组织起来:
ffmpeg -i input.mp4 -filter_complex \ "[0:v]split=3[v1][v2][v3]; \ [v1]scale=1920:1080[v1out]; \ [v2]scale=1280:720[v2out]; \ [v3]scale=854:480[v3out]" \ -map "[v1out]" -c:v:0 libx264 -b:v:0 2.8M -c:a aac \ -map "[v2out]" -c:v:1 libx264 -b:v:1 1.4M \ -map "[v3out]" -c:v:2 libx264 -b:v:2 800K \ -f hls -hls_time 4 -hls_list_size 0 ...实际生产中,我更推荐用脚本分别跑三次,逻辑更清晰,也方便调试中间产物。关键是确保每个清晰度都有自己的M3U8文件,然后手动或脚本生成主M3U8。对于动态码率切换,还需要考虑是否启用EXT-X-MEDIA标签来支持多音轨、多字幕,这里不再展开,但思路是一样的:用主列表聚合子列表。
3. 实操过程与核心环节实现
3.1 从零开始准备环境和素材
在动手前,先把环境确认好。我用的是Ubuntu 20.04的服务器,FFmpeg版本为4.4以上,因为4.4对HLS加密和多码流切片的支持更完整。如果你是在Windows或macOS上本地测试,也可以安装静态编译版本,命令完全一样。
准备一段测试素材,我习惯用FFmpeg生成一段带测试图案的合成视频,这样不涉及版权问题,还能验证分辨率切换是否生效:
ffmpeg -f lavfi -i testsrc2=size=1920x1080:rate=30 \ -f lavfi -i sine=frequency=440:sample_rate=44100 \ -t 30 -c:v libx264 -c:a aac test.mp4这条命令生成30秒、1920x1080、30fps、带440Hz正弦音的视频。生成后会得到一个test.mp4,后续所有切片和加密操作都基于这个文件。
3.2 一步步完成切片和AES加密
我们来做一个带AES-128加密的点播HLS。第一步先生成密钥:
mkdir -p hls_demo/keys hls_demo/segments openssl rand 16 > hls_demo/keys/key.bin第二步写key_info.txt:
keys/key.bin http://127.0.0.1:8080/keys/key.bin 000102030405060708090a0b0c0d0e0f注意第一行是相对路径,FFmpeg在读取key_info.txt时,会把这个路径当作本地密钥文件来读取并嵌入加密逻辑;第二行是M3U8中播放器访问密钥的URL。如果你想模拟线上环境,这里可以先用本地HTTP服务测试,IP改成服务器的实际IP。
然后执行切片+加密命令:
ffmpeg -i test.mp4 \ -c:v libx264 -c:a aac \ -hls_time 4 -hls_list_size 0 \ -hls_key_info_file key_info.txt \ -hls_segment_filename "hls_demo/segments/seg_%04d.ts" \ hls_demo/index.m3u8执行后你会看到类似输出:
[hls @ 0x...] Opening 'hls_demo/keys/key.bin' for reading [hls @ 0x...] Opening 'hls_demo/segments/seg_0000.ts' for writing生成的index.m3u8内容大体会像这样:
#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:4 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXT-X-KEY:METHOD=AES-128,URI="http://127.0.0.1:8080/keys/key.bin",IV=0x000102030405060708090a0b0c0d0e0f #EXTINF:4.000000, segments/seg_0000.ts #EXTINF:4.000000, segments/seg_0001.ts ... #EXT-X-ENDLIST到这个阶段,点播HLS带加密的链路已经通了。你只需要在HTTP静态服务里把hls_demo整个目录暴露出即可,播放器访问index.m3u8,会自动下载密钥、解密切片并播放。
3.3 生成和部署多码流自适应版本
多码流自适应要展示的不仅是“有多个清晰度”,还包括“播放器能根据带宽自动选择”。这里做一个三个清晰度的版本:1080p、720p、480p。
先分别执行三次FFmpeg转码切片。第一次:
ffmpeg -i test.mp4 \ -vf scale=1920:1080 \ -c:v libx264 -b:v 2.8M -maxrate 3M -bufsize 6M \ -c:a aac -b:a 128k \ -hls_time 4 -hls_list_size 0 \ -hls_segment_filename "hls_demo/1080p/seg_%04d.ts" \ hls_demo/1080p/index.m3u8第二次:
ffmpeg -i test.mp4 \ -vf scale=1280:720 \ -c:v libx264 -b:v 1.4M -maxrate 1.8M -bufsize 3.6M \ -c:a aac -b:a 96k \ -hls_time 4 -hls_list_size 0 \ -hls_segment_filename "hls_demo/720p/seg_%04d.ts" \ hls_demo/720p/index.m3u8第三次:
ffmpeg -i test.mp4 \ -vf scale=854:480 \ -c:v libx264 -b:v 800K -maxrate 1M -bufsize 2M \ -c:a aac -b:a 64k \ -hls_time 4 -hls_list_size 0 \ -hls_segment_filename "hls_demo/480p/seg_%04d.ts" \ hls_demo/480p/index.m3u8三次转码后,各自目录下会有 index.m3u8 和多段TS文件。接下来手动或通过脚本生成主M3U8。下面是生成主播放列表的参考脚本:
cat > hls_demo/master.m3u8 <<EOF #EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1920x1080 1080p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=1280x720 720p/index.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=854x480 480p/index.m3u8 EOF如果把整个hls_demo目录上传到服务器的Nginx服务下,播放器访问master.m3u8即可实现多码流自适应。这里有个容易踩的坑:如果你把hls_demo直接放在Nginx的/var/www/html下,需要在Nginx配置里允许跨域访问密钥文件,否则播放器从http://yourdomain.com/hls_demo/keys/key.bin取密钥时,只要页面域名和资源域名不同,就可能出现CORS报错。
3.4 直播场景的M3U8动态更新机制
直播和点播最大的区别在于:点播的M3U8静态不变,而直播的M3U8是“窗口滑动式”的。直播切片工具会不断生成新的TS文件,并更新M3U8里的切片列表,移除掉过期的切片。播放器每隔几秒重新拉取一次M3U8,只读取最新列表。
在FFmpeg里做直播切片,常见命令如下(以推流到本地目录为例):
ffmpeg -re -i test.mp4 \ -c:v libx264 -c:a aac \ -f hls -hls_time 4 -hls_list_size 6 -hls_flags delete_segments \ -hls_segment_filename "live/seg_%05d.ts" live/index.m3u8-re参数至关重要,它让FFmpeg以原始帧率速度实时读入源文件,否则FFmpeg会以最快速度转完整个视频,直播列表会瞬间刷完之后停顿。-hls_list_size 6表示M3U8里最多保留最近的6个切片,-hls_flags delete_segments表示网络流直播时自动删除已经不在播放列表中的TS文件。
如果你是做广电级低延迟直播,需要注意HLS天然延迟较大,一般10到30秒,因为切片时长和播放器buffer共同决定。想压低延迟,可以用更短的切片时长,比如2秒,同时开启-hls_flags temp_file避免播放器读到不完整的TS文件。
4. 常见问题与排查技巧实录
4.1 播放器报“404 Not Found”但切片文件确实存在
这个问题的常见原因有两个:一是M3U8里的切片路径是相对路径,但播放器拿到的M3U8URL层级和实际文件层级不一致,导致相对路径解析错误。比如M3U8在/hls_demo/index.m3u8,切片路径写成segments/seg_0000.ts,那播放器会去请求/hls_demo/segments/seg_0000.ts。如果切片实际在另一个目录,自然404。
二是我在上面提到的跨域问题。尤其是密钥文件,经常被单独放在CDN或另一台服务器上,播放器从页面的域名去请求密钥,就会因为CORS跨域被浏览器拦截,表现上就是“请求失败”。排查方法是打开播放器开发者工具,看网络请求里是哪个文件404或CORS报错。如果是404,优先检查相对路径;如果是CORS,需要在Nginx配置里加上:
location /keys/ { add_header Access-Control-Allow-Origin *; }4.2 AES加密后播放黑屏但M3U8能正常解析
这通常是IV不匹配导致的。FFmpeg生成key_info.txt时如果指定了IV,那么M3U8的EXT-X-KEY标签里就应该带上同样的IV;如果没写,FFmpeg默认按切片序号生成IV,而某些播放器对“默认值”的实现并不一致,可能会用全零IV,导致解密失败。解决方案是:在key_info.txt里显式写一个固定IV,并确认M3U8中EXT-X-KEY标签的IV和它一致。
另一种可能是密钥URL返回了错误的MIME类型,比如Nginx把key.bin当文本文件返回,播放器按二进制读取失败。建议在Nginx里给key文件添加正确类型:
location ~* \.bin$ { types { application/octet-stream bin; } }4.3 ffmpeg m3u8转为mp4命令总失败
很多人拿到网上的M3U8地址,想转成MP4,却总是失败。出现这个问题的原因多半是:M3U8是直播类型的,没有#EXT-X-ENDLIST,FFmpeg会一直等待新的切片,命令看起来像“卡住”了。此时要么确认这是点播地址,要么加上-live_start_index -5之类的参数让FFmpeg从直播列表的某个位置开始读取,但转出来的文件往往不完整。
另一种常见错误是加密M3U8没有带密钥,FFmpeg转码时会报“Unable to open key file”之类错误。处理方法是先下载密钥,或者确保密钥URL在转码机器上也能访问。
ffmpeg -i "https://example.com/path/index.m3u8" -c copy output.mp4这条命令适用于点播且未加密的场景。如果遇到加密流,先用浏览器或curl确认密钥可访问,再执行转码。如果FFmpeg仍然报错,可以尝试加-protocol_whitelist "file,http,https,tcp,tls,crypto",因为FFmpeg在访问HTTPS加密流时需要把crypto协议加入白名单。
4.4 vue播放m3u8总出现跨域或格式不支持
前端开发者在Vue项目里播放M3U8时,最常用的方案是hls.js,然后再配一个HTML5 video标签。hls.js对MSE(Media Source Extensions)的依赖导致它不能在所有浏览器上工作,比如iOS Safari的Safari本身原生支持HLS,不需要hls.js;而Android Chrome需要hls.js,否则没法播放。
如果你的M3U8是加密的,hls.js会自行下载密钥并解密,所以同样受CORS限制。在Vue里引入hls.js的基本写法如下:
import Hls from 'hls.js'; if (Hls.isSupported()) { const video = document.getElementById('video'); const hls = new Hls(); hls.loadSource('https://yourdomain.com/hls_demo/master.m3u8'); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, function () { video.play(); }); }如果你的M3U8是直播流,记得在播放前处理自动重连的逻辑,因为直播流可能在网络波动后需要重新拉取M3U8。另一个细节是,如果服务器返回的M3U8包含#EXT-X-DISCONTINUITY标签,hls.js会有额外的处理逻辑,这是正常现象,不用慌。
4.5 直播切片后切片文件越来越多,磁盘被打满
这是直播HLS新手最容易遇到的运维问题。因为点播切片时-hls_list_size 0表示保留所有切片,直播场景如果不加-hls_flags delete_segments,FFmpeg默认不会删除历史切片,时间一久磁盘必然爆掉。
正确做法是在直播切片命令中显式添加:
-hls_flags delete_segments+temp_filedelete_segments控制自动删除过期切片,temp_file让切片先写成临时文件再重命名为正式TS文件,避免播放器在切片写入过程中读取到不完整的数据。如果你需要在切片完成后做CDN预热或备份,也可以换成-hls_flags program_date_time,把当前时间写入M3U8,方便后续做时间戳对齐。
5. 工具选型与生产环境部署的一些体会
5.1 切片器选型:FFmpeg之外还有什么选择
FFmpeg是目前最主流的HLS切片工具,但它不是唯一的方案。商业软件里有Unified Streaming、Wowza Streaming Engine、Nimble Streamer等,它们往往是更完整的流媒体平台,自带打包、DRM、多码流、截图等能力,适合企业级项目。
如果只是做固定点播转码,你也可以用Bento4的mp4split工具,它对fMP4切片的支持很成熟,尤其在Apple生态下表现稳定。另外,云厂商一般也提供转码服务,比如阿里云、腾讯云的媒体处理服务可以直接把MP4转成HLS输出,不过那样上传下载链路会变长,适合不需要自建服务器的场景。
我个人更倾向于用FFmpeg自己包一层脚本,原因有三点:一是成本可控,不依赖外部服务;二是在转码参数上能完全自定义;三是脚本化后容易集成到自动化发布流程里。缺点也很明显,自己做切片要考虑GOP对齐、加密、多码流一致性、CDN预热等问题,但这些问题我在前面的章节里已经给出了可直接复用的方案。
5.2 部署HLS服务的Nginx配置参考
HLS本质上就是一堆静态文件放在HTTP服务器上,所以Nginx配置很简洁,但有几个细节值得注意。
首先是缓存策略。对于点播M3U8,通常希望客户端缓存时间短一些,因为可能后续会有更新;对于TS切片,可以设置较长的缓存时间,减少CDN回源压力。一个常规的Nginx配置如下:
server { listen 80; server_name yourdomain.com; root /var/www/html; location /hls_demo/ { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; application/octet-stream bin; } add_header Access-Control-Allow-Origin *; add_header Cache-Control "no-cache"; } }注意types块里给M3U8配置了application/vnd.apple.mpegurl类型,很多播放器如果没有看到这个MIME类型,会拒绝以“媒体资源”的方式加载M3U8。TS文件类型是video/mp2t,密钥文件是application/octet-stream。加Cache-Control: no-cache是为了让直播M3U8能及时更新,避免播放器缓存住旧列表。
5.3 多码流自适应的带宽估算与切换策略
多码流自适应的关键参数是BANDWIDTH,它不一定是实际码率,而是播放器用来做初始选择的一个参考值。一般我会把BANDWIDTH设置为视频码率+音频码率,再加上一些冗余,比如720p的视频码率1.4M、音频码率96k,那么BANDWIDTH可以写成1600000左右。如果写得太低,播放器可能选择过高的码流导致卡顿;写得太高,则会让低带宽用户选到过低的清晰度。
切换策略方面,不同播放器的算法差异较大,hls.js默认根据最近一段时间的下载速度估算带宽,然后选择带宽允许的最高码流。实际操作中,如果你发现某些用户的播放器频繁切换码流,原因往往是BANDWIDTH标签写得太接近,模糊了清晰度之间的差异。建议让各档码流之间拉开至少50%的差距,这样播放器的决策会更稳定。
还有一点容易忽略:如果不同码流的音频参数不一致(比如音频码率不同),切换码流时可能会出现音量突变或短暂中断。想彻底避免,可以让所有清晰度使用相同的音频编码参数,只改变视频码率和分辨率。这样能提升体验,但会增加部分存储开销。
5.4 一个少有人提但很实用的经验:先测试再全量转码
前面讲的都是“怎么做”,最后补充一个“怎么稳”的经验。在项目里如果要对一批视频做HLS转码,千万别直接全量执行,因为不同源视频的封装格式、编码参数、分辨率差异很大,极有可能遇到几个特殊文件导致整体流程失败。
我的习惯是先抽1到2个具有代表性的文件做全流程验证,验证内容包括:是否能正常切片、加密后是否能在网页上播放、多码流切换是否正常、密钥是否能跨域访问。全部通过后再写一个批量脚本并行转码。
再分享一个小技巧:批量转码前先对源视频做一次ffprobe检查,识别出分辨率、时长、编码信息,放进一个清单文件里。这样遇到奇怪的源文件时,可以先知道问题出在哪一步,而不是盲目重试。
FFmpeg转码本身是很消耗CPU和内存的操作,如果是多路并发,最好是按CPU核心数设置并发数,别一股脑全开。我在实践中发现,并发数设为CPU核心数的1.5倍左右比较合适,再多反而会因为CPU争抢导致单路转码速度下降,整体吞吐不升反降。