m3u8直播ts分片下载合并后视频卡顿丢帧问题求助
问题根因
你的脚本下载合并后视频卡顿丢帧,由以下几个核心问题导致:
- 分片合并顺序错误:直接使用
cat *.ts合并时,shell默认按字典序匹配文件名,若分片文件名是数字递增格式(例如seg1.ts、seg10.ts、seg2.ts),排序结果会变成seg1→seg10→seg2,和实际播放顺序完全不符,直接造成时间戳错乱、画面卡顿花屏。 - 空文件占位引入损坏片段:检测到分片不存在时先执行
touch $filename创建空文件,再发起curl下载请求,一旦出现网络波动、CDN超时、请求被拦截等情况导致curl下载失败,空文件会被判定为已下载分片保留,最终合并时插入0字节损坏内容,直接造成丢帧。 - 直播流轮询逻辑漏片:脚本未解析m3u8文件的
EXT-X-MEDIA-SEQUENCE媒体序列号字段,也没有匹配直播分片的固定时长设置轮询间隔,轮询过快会浪费带宽,轮询过慢会错过m3u8滑动窗口中已经被CDN移除的历史分片,直接漏下关键片段。 - 直接拼接TS存在兼容性问题:不同TS分片的编码参数、时间戳基准可能存在微小偏差,直接二进制拼接会导致播放器解码时识别到时间戳不连续,触发卡顿、音画不同步问题。
- 变量引用不严谨:所有URL、文件名变量未加双引号包裹,遇到带特殊字符的分片地址时会出现解析错误,导致分片下载失败;使用
eval拼接curl命令也容易出现转义错误,引发请求失败。
修复方案
针对上述问题调整逻辑:
- 解析m3u8原文提取分片地址,严格按m3u8中列出的分片规则处理相对/绝对路径,不依赖grep简单匹配
- 移除提前touch创建空文件的逻辑,分片先下载到临时文件,只有curl请求成功后才重命名为正式分片文件,避免损坏文件留存
- 用分片URL的哈希值作为唯一文件名存储,避免重名覆盖,合并时按下载记录顺序拼接,从根本上避免通配符匹配顺序错误
- 按直播常规分片时长设置2秒轮询间隔,每次只下载新出现的未下载分片,避免重复请求和漏片
- 所有变量加双引号包裹,用数组存储curl参数替代
eval拼接,避免特殊字符解析错误 - 所有分片下载完成后,优先使用ffmpeg做时间戳校正和封装,替代直接cat拼接,生成兼容性更好的视频文件;如果没有ffmpeg环境,再按顺序拼接
修复后完整脚本
#!/bin/bash # 参数说明:$1=m3u8地址 $2=保存文件名 $3=录制时长(分钟) url_m3u8="$1" title="$2" record_min="$3" end_time=$(date -d "$record_min minutes" +%s) # 分片保存目录 seg_dir="${title}_segs" mkdir -p "$seg_dir" # 公共curl请求头,和原有配置保持一致 cmd_curl=(curl -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0' \ -H 'Accept: */*' \ -H 'Accept-Language: en-US,en;q=0.5' \ -H 'Accept-Encoding: gzip, deflate, br' \ -H 'Origin: BASE64(aHR0cHM6Ly9mdXRlbWF4LmFwcA==)' \ -H 'Connection: keep-alive' \ -H 'Referer: BASE64(aHR0cHM6Ly9mdXRlbWF4LmFwcA==)' \ -H 'Sec-Fetch-Dest: empty' \ -H 'Sec-Fetch-Mode: cors' \ -H 'Sec-Fetch-Site: cross-site' \ --retry 5 \ --fail \ --compressed \ -s) while [[ $(date -u +%s) -le $end_time ]] do # 拉取最新m3u8内容 m3u8_content=$("${cmd_curl[@]}" "$url_m3u8") m3u8_prefix=$(dirname "$url_m3u8") # 提取m3u8中的ts分片地址,处理相对路径/绝对路径两种情况 while IFS= read -r line; do # 跳过注释行、空行 [[ $line =~ ^# ]] && continue [[ -z $line ]] && continue # 补全相对路径为完整地址 if [[ ! $line =~ ^https?:// ]]; then line="${m3u8_prefix}/${line}" fi seg_hash=$(echo -n "$line" | md5sum | cut -d' ' -f1) seg_path="${seg_dir}/${seg_hash}.ts" # 分片已存在则跳过 [[ -f $seg_path ]] && continue # 下载分片到临时文件,成功后再重命名为正式文件 "${cmd_curl[@]}" -o "${seg_path}.tmp" "$line" && mv "${seg_path}.tmp" "$seg_path" done <<< "$m3u8_content" # 按直播分片常规间隔2秒轮询,避免请求过于频繁 sleep 2 done # 合并视频:优先用ffmpeg校正时间戳,无ffmpeg则按顺序拼接 if command -v ffmpeg >/dev/null 2>&1; then # 生成ffmpeg concat所需的文件列表 : > "${seg_dir}/list.txt" for seg in "${seg_dir}"/*.ts; do echo "file '$(realpath "$seg")'" >> "${seg_dir}/list.txt" done ffmpeg -f concat -safe 0 -i "${seg_dir}/list.txt" -c copy -y "${title}.mp4" else # 无ffmpeg环境时按文件生成时间排序拼接 find "${seg_dir}" -maxdepth 1 -name "*.ts" -type f -print0 | xargs -0 ls -tr | xargs cat > "${title}.ts" fi # 清理临时分片目录 rm -rf "$seg_dir"
运行示例
./hls-download.sh BASE64(aHR0cHM6Ly9uZXdlZGdlLmV1LWNlbnRyYWwtMS5lZGdlLm15Y2RuLmxpdmUvbGl2ZS92aW50ZXF1YXRyb2hyczIvdmludGVxdWF0cm9ocnMyXzIwMDAvaW5kZXgubTN1OA==) program 1
注:如果录制环境可以安装ffmpeg,强烈建议优先使用ffmpeg做最终合并,能解决90%以上的时间戳不连续导致的卡顿问题。
内容的提问来源于stack exchange,提问作者btafarelo
相关产品推荐
相关产品推荐

