直播录像回放全链路:从ffmpeg录制到HLS切片实践 直播录像回放是直播平台里非常常见的需求直播结束之后用户要能回看整场内容运营也要能沉淀精彩片段。问题在于直播过程中录下来的原始文件并不能直接当作点播视频使用。一场晚场直播可能持续两三个小时录制文件受推流端、录制服务器和网络状态共同影响编码参数、时间戳、关键帧间隔、文件封装都存在大量不确定性。直接把这些文件放到播放器里用户很容易遇到黑屏、卡顿、进度条拖不动、音频正常但画面静止等现象。直播录像回放系统的核心工作就是把直播现场产生的录制文件处理成格式统一、可稳定点播、可拖拽、可分发的回放内容。这篇内容围绕一条完整处理链路展开录制原始文件、检查文件完整性、统一编码和时间戳、生成 HLS 切片、发布到存储和分发节点、播放验证、常见问题排查。适合直播业务开发、音视频后端和运维同学参考。实际项目中不必一开始就搭复杂系统先手工跑通流程、验证参数再逐步自动化。1. 直播录像回放为什么不能直接拿原始文件上线1.1 录制文件天然带着“直播现场”的问题直播录制和普通视频上传有一个本质区别普通视频在上传前已经是一个完整、封闭、确定的内容而直播录制文件是在直播过程中边接收数据边写磁盘的产物。直播流一旦出现断流、重推、切码率、编码器异常录制文件里就可能留下痕迹。常见问题包括推流端中途掉线录制文件出现明显的时长空洞。推流端在直播中切换清晰度同一个文件里出现多段不同分辨率、不同码率的视频流。直播流的 GOP 间隔不固定关键帧位置散乱后续切片困难。FLV、TS、MP4 各自封装方式不同有的文件录制中断后索引缺失播放器读不出总时长。推流端时间戳异常文件里出现时间戳回退或跳变导致音画不同步。这些问题的共同点是录制文件只适合作为“素材”不适合直接作为“成品”。如果强行把原始 FLV 或 MP4 丢给播放器部分片段可能能播但用户一旦拖拽进度条、切换清晰度、或者播放到断流位置问题就会暴露。1.2 回放系统要完成的处理链路直播录像回放系统的目标不是“保存录像”而是“让录像变成可播放、可管理、可检索的在线视频”。一套典型的处理链路如下。直播推流 ↓ 直播录制RTMP / SRT / HLS 等 ↓ 原始录制文件 ↓ 完整性检查与修复 ↓ 统一转码 / 转封装H.264 AAC ↓ HLS 切片VOD 类型 m3u8 ↓ 上传存储与分发对象存储 / CDN ↓ 播放验证与用户访问每一个环节解决一个具体问题录制环节解决“怎么能稳定拿到一份完整文件”。检查与修复环节解决“文件是否可用损坏到什么程度”。统一转码环节解决“编码格式、分辨率、关键帧间隔是否一致”。切片环节解决“用户拖拽时能否快速定位”。分发环节解决“大量用户同时访问时是否稳定”。这条链路不能省。省掉检查后续转码和切片可能在中间报错最后才发现源文件有问题省掉统一转码不同场次录像的参数可能差异很大播放器兼容性难以保证省掉切片直接发整个 MP4也会增加拖拽和网络播放的复杂度。2. 先解决录制环节拿到一份可处理的原始文件2.1 常见直播录制协议和工具录制方案取决于直播推流和播放使用的协议。常见组合如下。录制方式适用场景产出文件注意事项RTMP 拉流录制推流端使用 RTMP 推流FLV录制端需要保持连接断流后需重连SRT 拉流录制对弱网要求较高的直播链路TS 或 MP4SRT 抗丢包能力较强但需服务端支持HLS 拉流录制直播已经通过 HLS 分发TS 分片需要合并多个 TS 分片注意分片之间时间戳连续云端直播录制使用云直播产品平台生成的文件依赖平台回调需要处理回调延迟和录制模板配置如果直播链路已经存在推荐优先使用平台自带录制功能稳定性比自建脚本更高还能获得录制事件回调。自建录制适合需要自定义存储、自定义转码或者直播链路本身是自建服务的场景。2.2 用 ffmpeg 录制拉流的基本命令自建录制最常用的工具是 ffmpeg。下面是一个 RTMP 拉流录制命令。ffmpeg \ -i rtmp://your-live-server/live/your_stream_key \ -c copy \ -f flv \ /data/live_records/live_session_001.flv这里几个参数的含义-i指定输入流地址。-c copy表示不重新编码直接复制原始音视频流录制时 CPU 开销最低。-f flv指定输出封装格式为 FLV。直播源使用-c copy时输出到 FLV 最常见因为 FLV 对直播流封装友好录制中断后也更容易继续处理。最后一个参数是输出文件路径。注意-c copy只适合编码流本身没有问题的场景。如果直播流中间切换了清晰度复制出来的 FLV 里可能包含多段不同分辨率的视频流后续处理时必须检测。直播录制脚本还需要考虑断线重连。ffmpeg 执行一次-i拉流如果中途连接断开进程通常会直接退出。常见的做法是在外层用 shell 循环重试。while true; do ffmpeg -i rtmp://your-live-server/live/your_stream_key \ -c copy -f flv \ /data/live_records/live_session_$(date %Y%m%d%H%M%S).flv sleep 5 done每次重连都生成一个新文件可以避免把断流前后的异常时间戳写进同一个文件。之后再通过录制事件或日志把这些小文件按时间顺序合并。2.3 录制环节最容易踩的三类坑问题现象常见原因检查方式处理建议录制进程静默退出直播流断开ffmpeg 进程退出查看进程状态和 ffmpeg 日志外层增加循环重连并接入进程探活告警文件只有几秒就结束拉流地址带签名或凭证过期后断开检查拉流地址有效期使用独立录制流地址或定时刷新凭证录制文件中有多段分辨率直播过程中推流端切换了清晰度ffprobe 查看流信息录制后增加多视频流检测必要时转码统一录制环节最容易忽略的是文件完整性。录制完成后不要马上进入处理流程先确认文件已经写入完成、时长接近预期、没有明显的流异常。3. 拿到录像后先检查和修复再谈转码3.1 检查文件信息的标准命令无论源文件来自自动录制还是手工下载建议先用 ffprobe 查看文件基本信息和流信息。ffprobe -v error \ -show_format \ -show_streams \ /data/live_records/live_session_001.flv输出结果可能较长重点关注以下几项。以一段模拟输出为例。Input #0, flv, from live_session_001.flv: Duration: 02:15:41.32, start: 0.000000, bitrate: 3120 kb/s Stream #0:0: Video: h264 (High), yuv420p, 1920x1080, 25 fps Stream #0:1: Audio: aac, 48000 Hz, stereo, 190 kb/s检查要点Duration 是否接近直播时长。是否有且只有一路视频流和一路音频流。视频流编码是否为 H.264分辨率、帧率是否合理。音频流编码是否为 AAC采样率以 44100 或 48000 Hz 为主。start 如果出现较大的负值或正值说明时间戳起点有问题需要后续处理。还要检查时间戳是否连续。可以使用如下命令导出包的时间戳信息。ffprobe -v error -show_packets \ -select_streams v \ -show_entries packetpts_time,dts_time,duration_time \ /data/live_records/live_session_001.flv输出中如果出现pts_time回退、大幅跳变或重复说明源文件时间戳异常。3.2 识别文件损坏的几种典型现象现象含义常见场景播放器显示时长为 00:00:00MP4 的 moov 索引缺失或未写入录制进程被 killMP4 没有 finalize能播放但拖拽定位不准关键帧缺失或时间戳错乱网络录制过程中断流重连后时间戳不连续播放到某个时间点突然跳段源文件存在时间戳空洞推流端掉线重推中间有一段时间没有数据音画逐渐不同步音频流和视频流时长偏差累积转码或复制时没有统一时间基准ffprobe 报moov atom not foundMP4 索引缺失没有使用faststart或录制作坊异常退出识别完问题才能决定是直接 remux、重新封装还是需要完整转码。3.3 原始文件修复策略修复优先选择“不重新编码”的方式因为复制流的速度远快于转码也能保持原画质。如果是 FLV 转 MP4 且提示索引缺失可以尝试重新封装ffmpeg -i live_session_001.flv \ -c copy \ -movflags faststart \ output_normalized.mp4如果时间戳明显错乱可以先让 ffmpeg 重新生成时间戳再做封装转换。ffmpeg -fflags genpts \ -i live_session_001.flv \ -c copy \ -movflags faststart \ output_normalized.mp4如果一段直播被拆成多个录制文件需要先按时间顺序写入列表文件再合并。file /data/live_records/part_001.flv file /data/live_records/part_002.flv file /data/live_records/part_003.flvffmpeg -f concat -safe 0 -i filelist.txt -c copy merged.flv使用 concat demuxer 时所有文件的编码参数必须一致否则输出可能出现花屏或音画不同步。如果各分段编码参数不一致需要先转成统一格式再拼接。修复完成后再次使用 ffprobe 检查时长、流信息和时间戳。只有通过检查才进入下一步转码切片。注意修复过程中不要覆盖原始录制文件。原始文件应保留一段时间作为后续排查和重新处理的素材。处理链路越靠前保留原始文件的成本越低价值越高。4. 统一视频参数为切片做准备4.1 为什么目标编码建议选 H.264 AACHLS 切片后播放端会频繁切换分片。如果编码格式不统一播放器在分片之间往往会出现兼容性问题。H.264 AAC 是目前兼容性最好的组合浏览器、移动端、各类播放器几乎都默认支持。如果原始素材是 H.265回放主要面向 Web 端时建议优先转成 H.264。原因是 H.265 在部分浏览器和操作系统上需要额外解码支持用户环境不确定时兼容性成本很高。如果回放平台有明确的端上解码能力要求再评估是否保留 H.265。音频统一使用 AAC采样率推荐 48000 Hz。声道数可以根据原素材保留如果原素材是多声道也可以统一转为立体声避免部分播放器声音解析异常。4.2 转码或转封装时的关键参数在已经完全统一为 H.264 AAC 的情况下切片可以直接-c copy复制流。如果源文件编码不统一则需要先转码。一个较稳的转码命令ffmpeg -i source.flv \ -c:v libx264 \ -preset veryfast \ -crf 23 \ -r 25 \ -g 50 \ -keyint_min 25 \ -sc_threshold 0 \ -c:a aac \ -b:a 128k \ -ar 48000 \ -ac 2 \ -movflags faststart \ output.mp4关键参数解释参数含义建议-preset veryfastx264 编码速度和体积的平衡学习环境可以用 veryfast生产环境可调为 medium 或 slower-crf 23恒定质量值越小质量越高常用 18 到 2823 是常见默认-r 25输出帧率与源帧率不一致时ffmpeg 会做帧率转换-g 50关键帧间隔帧数25fps 下每 2 秒一个关键帧30fps 下建议写 60-keyint_min 25最小关键帧间隔避免关键帧过于密集-sc_threshold 0关闭场景切换自动插关键帧保证切片时长均匀这是 HLS 切片的关键-b:a 128k音频码率语音为主可降到 96k音乐类内容可提高到 160k 或更高-ar 48000音频采样率与常见播放器兼容-movflags faststart将 moov 索引移到文件前部适合网络播放和后续处理-sc_threshold 0值得单独说明。ffmpeg 默认会检测场景切换并自动插入关键帧这在普通视频压缩里是优化但对 HLS 切片未必是好事。HLS 切片器切分时通常需要在关键帧处对齐如果在任意位置自动插入额外关键帧切出来的分片时长会很不均匀用户拖动进度条时某些分片的加载时间会明显变长。4.3 统一时间戳避免音画不同步转码前如果发现源文件时间戳有问题输出文件也可能带上同样的问题。常见处理方式如下。使用-fflags genpts让 ffmpeg 重新生成 PTS。转码时让 ffmpeg 按目标帧率重新分配时间基准这通常会在转码过程中自动完成。如果音频和视频时长偏差较大可以先分离查看。ffmpeg -i source.flv -map 0:v:0 -c copy video.h264 ffmpeg -i source.flv -map 0:a:0 -c copy audio.aac分离后再逐段检查音视频时长。如果音频比视频长需要决定是裁剪静音段还是保留原样具体取决于现场情况。实际项目中这类问题没有固定答案关键是先定位偏差量再决定处理策略。注意时间戳修复一定要在切片之前完成。切片之后再去修时间戳需要对每个分片重新处理成本会成倍增加。5. HLS 切片把长录像变成可拖拽的回放5.1 HLS 回放的基本原理HLS 的原理是把一个完整视频切成若干小分片再用一个 m3u8 播放列表文件记录分片顺序。播放器先加载 m3u8再根据列表逐个下载分片。用户拖动进度条时播放器只需要定位到对应分片即可。直播录像回放使用的是 VOD 类型播放列表即#EXT-X-PLAYLIST-TYPE:VOD表示这个列表是静态的不会在播放过程中追加新分片。直播事件的 HLS 是动态追加的两者不能混用。分片时长需要权衡。分片太短比如 2 秒用户拖动定位更精细但请求数会多几倍源站和 CDN 压力都更大分片太长比如 10 秒以上文件数量减少但用户拖动到某个时间点时需要下载更大体积的分片才能开始播放。常见选择是 4 到 6 秒。5.2 使用 ffmpeg 生成 HLS 分片如果前面的转码产物已经是 H.264 AAC切片可以直接复制流不需要二次转码。ffmpeg -i output.mp4 \ -codec copy \ -map 0 \ -f hls \ -hls_time 6 \ -hls_list_size 0 \ -hls_playlist_type vod \ -hls_segment_filename /data/replay/session_001/%d.ts \ /data/replay/session_001/index.m3u8参数说明参数含义作用-codec copy复制音视频流不重新编码速度最快不损失画质-map 0使用源文件中的所有流防止漏掉音频轨-hls_time 6目标分片时长 6 秒控制分片数量和拖拽粒度-hls_list_size 0保留全部分片点播回放必须保留所有分片不设此项时可能只保留最近若干分片-hls_playlist_type vod生成静态点播播放列表播放器不会等待动态追加分片-hls_segment_filename分片文件名模板%d表示序号从 0 开始切片完成后index.m3u8的内容类似下面这样。#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:6 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:6.000000, 0.ts #EXTINF:6.000000, 1.ts #EXTINF:4.320000, 2.ts #EXT-X-ENDLIST最后一行#EXT-X-ENDLIST表示列表结束这是点播回放的重要标志。如果列表缺少这行播放器可能认为直播还在进行一直在等待新分片。5.3 切片成功后如何验证结果切片完成后不能只看命令是否执行成功要实际检查分片和播放列表。检查分片时长是否接近目标值ffprobe -v error -show_entries formatduration -of csvp0 0.ts ffprobe -v error -show_entries formatduration -of csvp0 1.ts检查单个分片是否包含完整音视频流ffprobe -v error \ -show_entries streamcodec_name,codec_type \ -of csvp0 \ /data/replay/session_001/0.ts验证 m3u8 是否能被播放器解析ffprobe -v error -show_format /data/replay/session_001/index.m3u8最后用浏览器或其他播放器打开 m3u8 地址拖动进度条到开头、中间、接近结尾三个位置确认都能快速起播。还要检查分片之间是否有关键帧对齐问题具体表现是拖到某个时间点后画面花屏或者停顿。6. 回放系统的存储、索引与分发6.1 文件目录和命名规则HLS 回放会产出一堆文件如果目录和命名规则不固定后续发布、清理、排错都会很混乱。推荐按会话维度组织目录。/data/replay/ session_001/ source/ original.flv work/ normalized.mp4 hls/ 0.ts 1.ts index.m3u8 manifest.json命名规则建议满足以下几点会话标识唯一稳定例如session_001避免使用带空格或不适合 URL 的名称。源文件、转码产物、HLS 分片分目录存放避免混在一起。使用manifest.json记录处理状态和关键信息方便自动化任务读取。manifest.json示例{ sessionId: session_001, title: 晚间场直播录像, status: published, duration: 8112, videoCodec: h264, audioCodec: aac, resolution: 1920x1080, segmentCount: 1352, hlsPath: https://replay.example.com/session_001/index.m3u8 }这些字段在后续播放列表生成、数据统计、清理任务里都有用。6.2 生成播放列表与发布接口业务端展示录像列表时需要知道每个会话的播放地址和时长。最简单的做法是在切片完成后将 m3u8 地址和元数据写入数据库或 Redis前端通过接口读取。{ sessionId: session_001, title: 晚间场直播录像, url: https://replay.example.com/session_001/index.m3u8, duration: 8112, cover: https://replay.example.com/session_001/cover.jpg, status: published }发布流程要注意一个窗口期问题HLS 切片是逐文件生成的如果一边生成一边就允许外部访问用户可能看到已经上传的 m3u8但某个2.ts还没上传完成播放就会中断。解决方案是先上传到临时目录全部上传完成并校验后再切换到正式目录或者先更新服务器端数据库状态再开放播放地址。6.3 分发与访问控制HLS 分片文件体积大、数量多生产环境一般不建议直接用单台业务服务器提供访问而是把 HLS 目录同步到对象存储再通过 CDN 分发。对象存储或静态文件服务需要配置正确的 Content-Type.m3u8使用application/vnd.apple.mpegurl.ts使用video/mp2t如果 Content-Type 错误某些播放器可能拒绝播放。目录的访问权限也应该关闭浏览只允许通过具体 URL 访问文件。访问控制方面可以按业务需求做 URL 鉴权或签名过期。不要直接把所有 HLS 文件暴露成无防护的公网文件尤其是包含用户内容或付费内容的回放。签名过期时间需要根据回放时长设置避免用户播放中途签名失效。7. 回放中的常见问题排查7.1 黑屏、卡顿、音画不同步分别查哪里不同现象指向不同问题。黑屏但有声音优先检查视频流。可能是视频流编码不兼容也可能是转码时某个视频分片损坏。用 ffprobe 检查对应分片的codec_name、width、height再换一个播放器或换 H.264 转码验证。卡顿优先检查分片请求链路。逐段打开 m3u8查看播放器请求了哪些 ts哪些 ts 返回慢或者返回错误。还要检查分片时长是否均匀如果某个分片长达 15 秒而目标值是 6 秒播放器加载这个分片时体验就会差很多。音画不同步优先检查时间戳。录制源文件时间戳异常时转码和切片都会继承问题。可以回到转码前的 MP4 用 ffprobe 查看音视频流时长差异如果偏差持续扩大基本可以确定源文件时间戳基准不一致。拖动进度条失败优先检查播放列表和关键帧。确认 m3u8 是 VOD 类型并且分片从关键帧开始。如果转码时没有关闭场景切换插帧切片位置可能对齐不上关键帧拖拽定位就会出现问题。7.2 排查顺序与验证工具回放问题建议按以下顺序排查。先抓 m3u8 内容确认播放列表是否完整是否有#EXT-X-ENDLIST。用 ffprobe 校验 m3u8 和几个关键分片是否能被解析。检查播放器请求的 HTTP 状态码、响应时间、Content-Type。找到出问题的时间点定位到对应的 ts 分片。对比正常分片和异常分片的时长、编码参数、关键帧位置。如果单个分片正常但播放仍然失败回到转码环节检查源文件时间戳。验证工具主要是 ffprobe 和播放器抓包。先用 ffprobe 排除文件问题再去查 CDN 和网络问题顺序不要倒过来。7.3 问题现象与处理方案速查表问题现象可能原因检查方式处理建议黑屏但有声音视频流损坏或编码不兼容ffprobe 检查视频流编码和分辨率统一转成 H.264 后重新切片只有画面没有声音音频流缺失或编码异常ffprobe 检查音频流是否存在转码时显式映射音频流-map 0:a:0播放卡顿分片时长不均、源站响应慢、缺少分片检查 m3u8 和 ts 响应状态重新转码并统一关键帧间隔拖拽进度条失败m3u8 非 VOD 类型或缺 ENDLIST查看 m3u8 内容使用-hls_playlist_type vod重新切片拖到某处花屏分片不是从关键帧开始ffprobe 检查 I 帧位置转码时关闭场景切换插帧保证关键帧对齐视频时长显示错误MP4 索引未写入ffprobe 查看 Duration用-movflags faststart重新封装分片能下载但播放失败分片本身不完整验证单个 ts 是否可解码重新生成该分片或完整切片8. 直播录像回放的工程化建议8.1 从手动处理升级到自动化流水线手工跑通命令只是第一步。实际直播业务中每天可能有多场录像需要处理人工介入越多遗漏和错误越多。自动化流水线可以按如下思路设计录制完成事件触发或定时扫描源目录。将待处理任务写入队列任务包含会话 ID 和源文件路径。Worker 依次执行检查、修复、转码、切片、发布。每个任务都有幂等标识重复执行时不会重复处理。失败任务记录错误日志并保留原始文件。全部处理完成后更新数据库状态并通知播放服务刷新。事件触发比定时扫描更及时但事件通道可能丢消息所以定时扫描兜底仍然是必要的。8.2 学习环境与生产环境的差异项目学习环境生产环境文件规模一个短视频或一段几十分钟录像多场次并发处理文件数量大执行方式手工执行 ffmpeg 命令队列 Worker 自动处理日志终端输出即可结构化日志按会话 ID 聚合故障恢复失败后手动重跑自动重试、告警、人工介入机制分发本机播放对象存储 CDN清理手动删除定时清理临时文件和过期录像依赖版本ffmpeg 新版本即可锁版本避免升级导致参数行为变化ffmpeg 版本差异会影响部分 HLS 参数尤其是-hls_flags相关行为和生产环境常用的一些高级参数。线上环境建议固定 ffmpeg 版本变更前先在一台测试机上验证输出结果。8.3 上线前检查清单直播录像回放功能上线前可以按这份清单逐项确认。录制进程是否支持断线自动重连。录制完成后是否触发事件并校验文件完整性。原始文件是否保留未被处理链路覆盖。是否统一目标编码格式、帧率和关键帧间隔。切片是否从关键帧开始分片时长是否均匀。m3u8 是否为 VOD 类型是否有#EXT-X-ENDLIST。m3u8 和 ts 的 Content-Type 是否正确。发布流程是否避免“m3u8 可访问但分片未上传完成”的窗口期。回放地址是否有鉴权、签名或防盗链保护。是否有关键监控指标录制成功率、处理失败率、播放成功率、分片请求状态码。是否有清理任务处理临时文件和过期录像。是否有处理失败后的重试和人工介入通道。直播录像回放的核心并不是“录下来能看”而是“原始录制文件到可稳定点播内容”之间的整条链路是否可靠。很多问题都不是到最后一步才出现而是在录制文件本身、时间戳、关键帧和切片策略里埋下的。建议从一小段真实直播录像开始手工把检查、转码、切片、播放验证全部跑通再把这些步骤逐步自动化和加监控。能稳定复现链路比堆砌更多功能更有价值。