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

资讯详情

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

ffmpeg与HLS流媒体实战:从点播切片到直播推流参数详解

ffmpeg与HLS流媒体实战:从点播切片到直播推流参数详解 1. 项目概述与需求拆解1.1 为什么突然想研究 ffmpeg HLS做流媒体这块的同行应该都有体会无论你是做直播、点播还是视频处理工具几乎绕不开两个东西一个是ffmpeg另一个就是HLS。ffmpeg 本身不用多介绍音视频领域的老黄牛能解、能编、能转、能切、能推流你想到的常规操作它基本都覆盖了。而 HLSHTTP Live Streaming是苹果搞出来的基于 HTTP 的流媒体传输协议把一段连续的视频流切成一个个小的分片文件一般是 TS 或 fMP4 格式再通过一个索引文件m3u8把这些分片串起来播放器按顺序拉取播放。这次我做的ffmpeg-hls 使用测试研究目标很直接把 ffmpeg 生成 HLS 流的完整流程跑通摸清楚每个参数的实际影响并且验证它在点播和直播两种场景下的表现。不是停留在照抄命令能跑就行的层面而是真的去了解每个参数为什么要这么设、设了之后会有什么效果、遇到问题怎么排查。这个研究过程适合谁参考我觉得有三类人刚接触音视频处理想搞懂 m3u8 和 ts 分片是怎么生成的后端开发做直播或点播系统需要在服务器上自己切流、推流的运维或架构师手头有视频资源想转成 HLS 格式部署到 Web 或 App 上但又不想被商业云服务绑架的独立开发者。我踩过的坑、调过的参数、验证过的方案下面都整理出来了。1.2 测试前的环境准备先说下我这次测试用的环境方便你对照操作系统Windows 11另外在 Ubuntu 22.04 上也做了验证ffmpeg 版本6.1.1 完整版Windows 下建议直接下载 release-full 版本推流测试的服务器SRS 5.0 社区版部署在同一台局域网机器上播放器VLC、浏览器原生 HLS 播放Safari/Chrome 配合 hls.js测试源文件一段 1080p、时长大约 10 分钟的 MP4 视频码率 4000kbpsWindows 下装 ffmpeg 说简单也简单说坑也坑。官网提供的是解压版解压后把bin目录路径加进系统环境变量PATH里命令行里就能直接敲ffmpeg了。这里有个我踩过的坑加完环境变量后一定要重新打开终端窗口否则不生效。另外如果你之前装过旧版本重装系统后再用很大概率是 PATH 里残留了旧路径或者变量值失效解决办法是先where ffmpeg看一下当前生效的是哪个路径再用ffmpeg -version验证版本指向不对就改环境变量。Ubuntu 下装了 265 编码需求的建议别图省事直接用 apt 装老版本。我之前为了测 libx265老老实实用源码编译了一版具体步骤是sudo apt update sudo apt install build-essential yasm cmake libx265-dev libx264-dev wget https://ffmpeg.org/releases/ffmpeg-6.1.1.tar.gz tar -xvf ffmpeg-6.1.1.tar.gz cd ffmpeg-6.1.1 ./configure --enable-gpl --enable-libx264 --enable-libx265 --enable-libmp3lame make -j$(nproc) sudo make install如果你的场景只需要软编 x264官方 release 包就够了但要做 265 编码就必须自己编。另外一个容易忽略的点是源码编译前先确认磁盘空间至少 2GB 以上编译时间视机器性能从几分钟到半小时不等我编译的时候因为选了太多组件中间还因为缺库失败过一次后来按需裁剪 configure 选项才通过。建议一开始就按需选择组件别一股脑全开。2. HLS 核心设计与方案选型思路2.1 HLS 到底解决了什么问题在讲具体用法之前我先花了点时间把 HLS 的原理捋清楚因为这直接决定了后面参数怎么调。HLS 的核心思路可以类比成把一本书拆成单页发快递。传统的视频文件是一个整体播放器必须从文件头开始读边下边播但一旦遇到网络波动很可能缓冲半天也没法播起来。HLS 的做法是把一部视频切成几秒钟一小段的分片比如每 6 秒一个 TS 文件再生成一个目录清单 m3u8。播放器拿到 m3u8 后先看清单里有哪些分片按顺序拉取第一个分片、开始播放同时在后台预加载后面的分片这样只要网速够快用户几乎感知不到文件正在加载的过程。从工程角度来说HLS 最大的优势有两个纯 HTTP 传输不依赖特殊协议CDN、Nginx、对象存储都能直接扛流量没有防火墙和端口封禁的烦恼。自适应码率可以在 m3u8 里声明多档码率的流播放器会根据当前网速自动切换实现无缝升降级。这两点直接决定了为什么这么多年过去RTMP 逐渐退潮HLS 至今稳坐 Web 端直播和点播的头把交椅。而且苹果生态全系原生支持 HLS不用装任何插件。2.2 为什么切流这个活适合用 ffmpeg 干HLS 协议本身并不是 ffmpeg 独有的Nginx 的 nginx-rtmp 模块能切片SRS 服务端也可以直接输出 HLS为什么我仍然建议用 ffmpeg 来做切流因为 ffmpeg 有一个别人比不了的优势它可以灵活地处理输入源。无论你的输入是本地文件、HTTP 拉流、RTSP 摄像头流、还是设备采集的裸流ffmpeg 都能先统一做解码、滤镜处理、再编码最后封装成 HLS。你可以在切流的同时顺手完成分辨率缩放、加水印、音视频增益、抽帧等操作等于一条流水线解决所有预处理。举个实际例子。这次测试时我需要对源视频做响度统一就只加了一个滤镜参数ffmpeg -i input.mp4 -af loudnormI-16:TP-1.5:LRA11 -c:v copy output.mp4这个命令把音频响度统一到 -16 LUFS同时保持视频流原样拷贝-c:v copy实测下来效率很高。如果你用服务端切片功能这些预处理要么不支持要么得提前单独跑一遍链路变长还容易出错。而 ffmpeg 全都能在切片那一步搞定这是它作为瑞士军刀的核心价值。另外一点是ffmpeg 的切片定位非常准确。它依据视频的关键帧位置来切分不会出现切片点落在非关键帧导致播放器花屏的问题。切片命令里的-hls_time是一个目标时长实际切割位置会自动对齐到最近的关键帧这一点在直播场景里极其重要后面细说。2.3 点播 vs 直播同一个 HLS 两种玩法做测试研究时我特意把 HLS 的两种主要场景分开跑了一遍因为它们的需求完全不同点播场景Video on Demand一个完整的视频文件切成几十个分片m3u8 是固定的包含全部分片列表末尾有#EXT-X-ENDLIST标记。这种场景下缓存友好、可以拖动播放、可以倍速对分片的数量没有限制。适合做视频网站、在线课程等。直播场景Live持续产生分片m3u8 里的分片列表是动态更新的旧的会被移除新的不断追加m3u8 末尾不允许有#EXT-X-ENDLIST标记结束标记会直接告诉播放器这是点播流播完即止。这种场景要求边切边播对切片的及时性和生成速度有较高要求而且分片列表窗口大小会直接影响直播延迟。用 ffmpeg 实现这两种场景参数差异非常明显。我把两种场景的核心命令分别整理在下面的实操章节里方便你对照使用。3. ffmpeg 生成 HLS 核心命令与参数深度解析3.1 点播场景一条命令切出完整 m3u8最基础的点播切片命令如下ffmpeg -i input.mp4 -c copy -hls_time 10 -hls_list_size 0 -hls_segment_filename output_%03d.ts output.m3u8拆开看每个参数的意义-c copy直接拷贝音视频流不做转码。这里的坑在于只有输入文件的编码格式符合 HLS 要求比如 H.264 AAC时才能用 copy如果源文件是 AV1 或者 PCM 音频必须改成具体的编码器否则生成的分片播放器根本播不了。-hls_time 10目标切片时长 10 秒。但前面提到了这不是硬性值ffmpeg 会以关键帧为界对齐实际每片可能在 9.5~11 秒之间浮动。-hls_list_size 0播放列表包含所有分片。在点播场景必须写 0否则默认只保留最后 5 个分片用户播放 30 秒后的视频就没法从开头拉流了。-hls_segment_filename分片文件名的模板%03d表示三位数字序号会生成 output_000.ts、output_001.ts 这样的文件。最后的output.m3u8指定 m3u8 文件的输出路径和名字ffmpeg 会在文件里自动写好每个分片的相对路径。跑完这条命令后你会看到同目录下出现了一堆 ts 文件加一个 m3u8 文件。m3u8 的内容长这样#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.000000, output_000.ts #EXTINF:10.000000, output_001.ts ... #EXT-X-ENDLIST这个文件其实就是整个播放过程的剧本播放器按顺序读取即可。3.2 直播场景边推边切的关键配置直播场景下我的典型命令是这样ffmpeg -re -i input.mp4 -c:v libx264 -c:a aac -f hls -hls_time 6 -hls_list_size 5 -hls_flags delete_segments -hls_segment_filename live_%03d.ts live.m3u8这里面有几个关键差异-re以原视频的真实帧率读取输入否则 ffmpeg 会以最快速度把文件处理完直播瞬间变成点播。这个参数理解成按时间轴匀速放送就对了。-hls_list_size 5m3u8 里最多保留 5 个分片形成滑动窗口播放器会跟着窗口走。窗口越小延迟越低但也越容易出现播放器赶不上切片生成速度的情况。-hls_flags delete_segments自动删除已经不在 m3u8 播放列表里的过期分片防止磁盘被撑爆。这个参数在长时间直播场景下必须加否则分片只会越来越多。这里我还想说一个比较深刻的教训直播切片时长直接决定观众的延迟感知。如果分片是 6 秒一片播放器一般要累计 3 个分片才启动约 18 秒再加上网络传输时间实际延迟可能到 20 秒左右这还算正常。如果你想做低延迟直播可以试试把-hls_time压到 1~2 秒并在服务端开启 LL-HLSLow-Latency HLS支持但这对编码器和服务器性能都有要求不是简单改个参数就能完美跑起来的。3.3 编码参数配合GOP 大小与切片时长的关系这是我在研究过程中觉得最容易忽略、但影响最大的一组配合。其实 HLS 切片的起点必须放在关键帧IDR 帧上。如果你的编码器把 GOP两个关键帧之间的间隔设成了 60 帧2 秒但你切片参数写的是-hls_time 10那最终切出来的分片时长不会是 10 秒的整数倍而是 GOP 时长的整数倍——最短也不可能比 2 秒还短。所以实际生产环境里切片时长应当和 GOP 尺寸对齐。比如你打算做 4 秒切片编码参数就要设置对应的 GOPffmpeg -re -i input.mp4 -c:v libx264 -x264-params keyint120:min-keyint120:scenecut0 -c:a aac -f hls -hls_time 4 live.m3u8这里keyint120表示每 120 帧一个关键帧25fps 下就是 4 秒scenecut0是禁用场景切换自动插入关键帧保证关键帧间隔稳定。如果不管 scenecut画面切换剧烈时编码器会提前插入关键帧导致切片时长忽长忽短直播流的更新节奏会被打乱。我在实测中发现GOP 设置得过大会导致延迟飙升设置得过小会浪费码率。一般建议就是根据目标切片时长换算保持两者基本一致或切片略大于 GOP 长度这样每个分片都以关键帧开头播放器拉流时立即能解码出画面。4. 实操全流程从点播切片到直播推流完整复现4.1 本地生成 HLS 点播流并验证播放我实际操作的流程如下。先用一个 10 分钟的 MP4 做测试源放在D:\test\hls_test\目录下cd /d D:\test\hls_test ffmpeg -i input.mp4 -c copy -hls_time 10 -hls_list_size 0 -hls_segment_filename output_%03d.ts output.m3u8执行时间取决于磁盘速度和文件大小。对 10 分钟 1080p 的视频-c copy模式大约 20 秒就完成了几乎不用等待。然后我用 VLC 打开 output.m3u8拖动进度条到任意位置都能秒切画面并且无卡顿说明切片完整。但有一个问题值得注意如果你用浏览器直接file://协议打开本地 m3u8Chrome 大概率会报错。原因在于浏览器不允许通过 file 协议跨文件读取分片内容而且 m3u8 和 ts 的路径解析也会出问题。你需要起一个本地 HTTP 服务来做验证我常用一条命令解决python -m http.server 8080然后浏览器访问http://localhost:8080/output.m3u8配合 hls.js 或直接拖进 Safari 就能正常播放。这一点对测试很有用很多人第一次生成 m3u8 后打不开往往不是切片有问题而是访问方式不对。4.2 把 HLS 流部署到 Nginx 上供公网访问本地验证通过后我把它部署到了 Nginx 上模拟真实点播环境。Nginx 只需要配置静态文件服务关键点是确保 m3u8 和 ts 文件都在同一个公开目录里并且给 ts 文件配置正确的 MIME 类型server { listen 80; server_name media.example.com; location /hls/ { alias /opt/www/hls/; types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } add_header Cache-Control no-cache; } }配置里的Cache-Control no-cache对直播流尤其重要不然 CDN 或浏览器可能把 m3u8 缓存住导致直播列表不更新、观众一直停留在旧画面。如果是纯点播则可以根据实际需求移除这行让分片充分利用缓存加速。部署完成后我测试了 Safari 和 Chrome 下的播放体验。Safari 原生支持 HLS不需要任何前端库Chrome 则需要引入 hls.js 才能播放 m3u8。如果你在做 Web 播放器建议直接用 video.js 配合 hls.js 扩展成熟稳定不用重复造轮子。4.3 直播推流到 SRS完整链路搭建直播场景的完整链路我选了 SRS因为它在 HLS 切片和 RTMP 推流之间做了很好的衔接。整个链路是这样的ffmpeg 推 RTMP 流 → SRS 接收 RTMP 流 → SRS 切片生成 HLS 流 → 播放器拉取 m3u8 播放先启动 SRS配置文件里开启 HLS 输出。SRS 5.0 的配置片段如下listen 1935; max_connections 1000; vhost __defaultVhost__ { hls { enabled on; hls_path /usr/local/srs/objs/nginx/html; hls_fragment 6; hls_window 30; } }然后在 ffmpeg 这边本地视频文件模拟推流ffmpeg -re -i input.mp4 -c:v libx264 -profile:v main -preset veryfast -tune zerolatency -c:a aac -f flv rtmp://192.168.1.100/live/test这条命令在编码参数里特别加了-tune zerolatency这是低延迟场景的经典配置减少编码缓冲。推流过程中SRS 会自动生成 m3u8 和 ts 分片我通过http://192.168.1.100/live/test.m3u8拉流验证VLC 基本 15 秒左右起播。这里有一个非常关键的体验要分享整个链路的瓶颈通常不在 ffmpeg而在 GOP 设置和切片时长。第一次测的时候我没有设置 GOP用的是默认配置结果切片时长忽长忽短播放器缓冲频繁延迟到了 30 秒以上。把编码参数固定为keyint120之后切片稳定了延迟降到 18 秒左右。对于非低延迟场景这个数值可以接受但要做真正的低延迟直播还得上 LL-HLS 或者换 WebRTC 方案。4.4 常用验证手段与复盘方法做完整链路测试后建议养成一个习惯每次推流结束后把产生的 m3u8 内容拉出来看一眼。m3u8 里的时间戳、序列号、分片个数都是重要的诊断信息。比如#EXT-X-MEDIA-SEQUENCE:5说明当前播放列表从第 5 片开始如果这个数字一直涨说明窗口在正常滑动如果数字不变说明列表更新有问题。另外推荐一个小工具ffprobeffmpeg 自带。当播放器播不了流时先用它检查一下源文件和推流后的流信息ffprobe -v error -show_format -show_streams http://192.168.1.100/live/test.m3u8它能列出流的编码格式、分辨率、码率、帧率等关键信息。有一次我推流后播放器黑屏用 ffprobe 一查发现音频编码是 flac 格式而 HLS 并不支持这种编码改成 AAC 后问题消失。很多排查工作其实在 ffprobe 这一步就能解决大半。5. 测试中的常见问题与排查实录5.1 播放器一直转圈起播失败这个现象我测了两次都有一次是切片格式不对一次是 m3u8 路径问题。排查思路按顺序来先看 m3u8 内容用文本编辑器打开确认是否有#EXT-X-ENDLIST点播或#EXT-X-MEDIA-SEQUENCE直播如果 m3u8 是空的或者只有开头几行那说明切片还没生成完或者生成失败。用 VLC 打开 m3u8 文件VLC 对错误格式的容错率高于浏览器如果 VLC 能播而浏览器不能大概率是分片跨域访问或 MIME 类型的问题。ffprobe 检查分片对单个 ts 文件执行ffprobe output_000.ts确认里面确实有视频流和音频流。有时你用-c copy切片但源文件的音频是 PCM 格式ffmpeg 会报错生成的 ts 里可能只有视频没有音频用户看到的就是画面是黑的或者无声。我实际踩过的场景是第一次用-c copy切一个 MKV 文件源文件里带的是 PGS 字幕轨道和一个不兼容的音频流。切出来后在 VLC 里勉强能播但移动端浏览器直接失败。后来加了-map 0:v:0 -map 0:a:0 -c:v copy -c:a aac只保留首个视频轨和首个音频轨并把音频统一转成 AAC问题彻底解决。建议你在切片前先用ffprobe -show_streams input.mp4看清楚流轨道结构再决定是否需要显式-map。5.2 直播列表不更新画面停在旧帧直播场景下最常见的问题是观众看到的画面一直停留在推流开始时的某一帧或者播放器隔几分钟才刷新一次。如果你用的也是 ffmpeg Nginx/SRS 链路优先排查这几个点检查 m3u8 的#EXT-X-MEDIA-SEQUENCE是否持续增长。如果不涨说明切片环节卡住了大概率是 ffmpeg 进程挂了或者编码器报错。看 ffmpeg 终端的输出如果有 error 关键字就要处理。确认 HTTP 层没有缓存。Nginx 默认可能给静态文件加 Expires 头导致播放器拉取 m3u8 时得到的还是旧内容。m3u8 的 location 块里务必加add_header Cache-Control no-cache这是我在生产环境里反复强调的点。检查播放器配置。部分播放器对 HLS 直播有缓存参数比如 hls.js 默认的liveSyncDurationCount是 3也就是至少保留 3 个分片的缓冲才起播如果你想降低延迟可以把这个值改小但太小的代价是缓冲不足导致频繁卡顿。5.3 延迟太高怎么办延迟 20~30 秒是普通 HLS 的正常水平很多人第一次做完直播后觉得这也叫直播不好意思HLS 就是这个调性。它本来就是以牺牲低延迟换取稳定性和 CDN 友好性。如果你的场景真的需要低延迟实测下来有两条出路改进型 HLSLL-HLS把切片切得更细1~2 秒并对 m3u8 增加分片预加载提示#EXT-X-PART播放器可以更早开始播放。ffmpeg 6.0 以上版本可以这样开启ffmpeg -re -i input.mp4 -c:v libx264 -g 25 -f hls -hls_time 1 -hls_flags independent_segmentsll_hls live.m3u8不过要注意LL-HLS 需要播放器端也支持不能指望 VLC 或者所有 Web 播放器都能完美跑通。换 WebRTC直播延迟能压到 1 秒以内但工程复杂度完全不同对信令服务、SFU 服务器的要求都更高适合实时互动场景。我个人在实际项目里的取舍是如果延迟容忍度在 10~30 秒就继续用普通 HLS简单可靠如果要求低于 5 秒直接上 WebRTC别在 HLS 上硬撑。5.4 分片越来越多导致磁盘被占满长时间直播测试中我还真把磁盘跑满过一次。原因就是漏了delete_segments参数。你在本地测试短时间推流可能感觉不到但连续直播几小时后分片文件数量和体积都会很恐怖。按 6 秒一片、每片约 4MB 算一小时就是 600 片、2.4GB一天下来 57GB。所以直播切流必须加-hls_flags delete_segments这个参数保证只有 m3u8 列表里保留的 5 个分片存在磁盘上其余被自动清理。如果你需要更长的回看窗口可以结合-hls_list_size调大到 30 或 60但磁盘消耗也要相应承受。5.5 移动端播放 HLS 常见兼容性问题测试过程中我在 iOS Safari 和 Android Chrome 上各跑了一遍发现一些平台差异iOS Safari对 HLS 原生支持但要求 m3u8 响应内容的 Content-Type 必须是application/vnd.apple.mpegurl或application/x-mpegURL否则直接拒绝播放。Nginx 配置 MIME 时要特别注意。Android Chrome原生不支持 HLS必须用 hls.js 或类似的 JS 库。如果用了 hls.js 还是播不了打开浏览器开发者工具看网络请求确认 ts 请求是否存在 404 或跨域报错。跨域问题m3u8 和 ts 分片如果在不同域名下播放器拉流会被 CORS 策略拦截。需要在服务端加 CORS 头add_header Access-Control-Allow-Origin *;这个头在直播测试时经常被忽略但却是移动端播放失败的最常见原因之一。我在真机上踩过这个坑当时放到线上才发现安卓播放全部黑屏最后加上这个头后才解决。6. HLS 测试中的一些心得与建议6.1 深入研究时不要只看命令要看数据这次测试研究做下来我最大的感受是ffmpeg 的 HLS 相关参数并不复杂真正复杂的在于理解你手头流媒体的特性。比如 GOP、码率、分辨率、音视频编码格式这些指标之间互相牵制任何一个不合理最终都会反映到播放端的体验上。建议你像我一样测试完每一组参数后都记录一张表格对比切片时长、延迟、文件大小、播放器起播时间。不要觉得麻烦真实项目里排查问题时这些历史记录就是你最可靠的参考资料。我这次整理了十几个不同参数组合的测试数据在写这篇文章时帮了大忙。6.2 生产环境里别迷信某个最佳参数很多人喜欢在网上搜一套万能命令直接上生产这在简单场景下问题不大但在复杂场景下很容易翻车。比如直播流的hls_time点播场景可能用 10 秒没问题但直播场景同样的参数会导致起播时间成倍增加再比如hls_list_size点播必须为 0直播却要根据窗口期合理设置。没有万能参数只有适配具体场景的参数。6.3 最后一个建议从测试走向生产前做好监控如果你打算把 ffmpeg HLS 方案放到生产环境除了验证功能流程一定要补上监控。具体来说三个点ffmpeg 进程是否存活、m3u8 文件是否持续更新、分片生成有没有积压。这三个指标基本能覆盖大多数故障场景。我自己在测试时会写一个简单的脚本定时拉取 m3u8 并检查最新的#EXT-X-MEDIA-SEQUENCE是否递增一旦发现长时间不变就触发告警。这种小工具成本很低但关键时刻能救命。这次 ffmpeg-hls 的测试研究到这里算是告一段落。回头看看好像也没有特别高深的技术在里面但正是这些琐碎的参数验证和问题排查把 HLS 从文档里的一个协议变成了我手里一个随时能用的工具。希望这篇记录能帮你少走一些弯路。
返回列表