找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3237

积分

0

好友

445

主题
发表于 昨天 22:35 | 查看: 3| 回复: 0

1 背景

在一个春暖花开的午后,团队内部接连出现了几个诡异的现象:

  • 网络运维同事反馈:“哪个域名流量这么大,太占用公司办公网资源了!”
  • 业务方询问:“想问下这个问题需要怎么解决呢,我们已经遇到好几例了,上次有个店员一天消耗了60G,这个店员130G。”
  • 与此同时,我收到了CDN带宽告警,心想:“哪个CDN域名用了这么多带宽啊!”

2 现象定位

2.1 问题流量定位

通过对CDN流量日志的分析,定位到消耗流量最高的几个资源均是MP4视频文件。点击流量最高的视频链接进行分析,发现这是一个大小约 1G、时长约 30分钟 的视频文件。

与业务同学确认后得知,这是一个学习培训视频,推送给了2500人学习观看。然而,CDN监控显示的流量消耗高达120TB。如果按2500人完整观看一次(1GB/人)计算,正常流量消耗应为2.5TB左右。多出的近50倍流量从何而来?

2.2 流量使用验证

为了验证问题,我使用谷歌浏览器访问了业务上传的另一个视频(大小约500MB,时长40分钟),在观看的同时开启开发者工具的Network面板进行分析。

在Network面板中,可以看到播放过程中产生了大量网络请求。状态栏显示:共发送了3696/3709个请求,已传输约8.75GB数据,而视频资源总大小约为8.89GB。这意味着播放一个500MB的视频,实际消耗了接近8.8GB的流量。

进一步观察请求详情,所有请求的状态码均为206(部分内容)。其中一张记录了时间与字节范围的表格数据如下所示:

时间 (Time) 字节范围 (Range)
363 ms bytes=0-
314 ms bytes=360022016-
40.53 s bytes=65536-
515 ms bytes=5046272-
408 ms bytes=24838144-
266 ms bytes=9863168-
397 ms bytes=26509312-
532 ms bytes=10223616-
262 ms bytes=27918336-
665 ms bytes=10059776-
300 ms bytes=28409856-
667 ms bytes=8945664-
265 ms bytes=30212096-

问题现象总结:

  1. 视频开始播放后,会立即出现 3个206请求
  2. 之后,浏览器会随时间发起 数千个206请求
  3. 请求的 Range 范围在视频文件的不同位置 来回跳动,例如在相近的时间点(如408ms和397ms),请求的字节范围跳跃很大(从约24MB跳到约26MB)。

经过搜索,发现可以通过一条简单的 ffmpeg 命令处理视频来解决此问题:

ffmpeg -i bad.mp4 -c copy -movflags faststart good.mp4

将处理后的新视频上传并分析,发现206请求数量大幅减少至正常水平(通常为1个),多206请求现象消失,播放视频消耗的流量等于视频原始大小。

在处理长视频时,更优的方案是将其转为流媒体格式(如HLS)。推动业务进行相应改进后,从监控图表可以看到,流量消耗的曲线在最初的峰值后迅速下降并趋于平稳。

3 问题探究

要分析这个奇异现象,需要理解整个播放流程。我们主要探究以下四个问题:

  1. 为什么一开始会出现3个206请求?
  2. 3个206请求后,为什么会发起多个206请求?
  3. 为什么请求头 Range 会来回跳跃?
  4. 为什么使用 ffmpeg 处理视频后能解决这个问题?

3.1 MP4 文件结构

MP4 文件由多个 Box(盒子)组成,其基本结构如下:

MP4 文件结构:
┌─────────────────────────────────────┐
│ ftyp (文件类型)                      │  ← 文件开头
├─────────────────────────────────────┤
│ mdat (媒体数据)                      │
├─────────────────────────────────────┤
│ moov (元数据)                        │  ← 可能在开头或结尾
│  ├─ mvhd (文件头信息)                │
│  ├─ trak (视频轨道)                  │
│  │   ├─ stco (Chunk偏移表)           │  ← 记录数据位置
│  │   ├─ stsz (样本大小表)            │  ← 记录每帧大小
│  │   └─ stsc (样本到Chunk映射)       │  ← 记录帧的分布
│  └─ trak (音频轨道)                  │
│      ├─ stco (Chunk偏移表)           │
│      ├─ stsz (样本大小表)            │
│      └─ stsc (样本到Chunk映射)       │
└─────────────────────────────────────┘

关键点:

  • moov box 包含了所有的索引信息(类似目录),播放器必须先读取它才能知道数据在哪里。
  • mdat box 包含了实际的音视频数据。

3.2 浏览器播放流程

通过查看谷歌浏览器开发者工具中“Media”标签页下的日志信息,可以得知Chrome浏览器使用 FFmpegDemuxer 来解析读取 MP4 文件。参考源码,其播放时的数据读取流程可以概括为以下步骤:

  1. HTML5 <video> 标签 发起播放。
  2. FFmpegDemuxer 接管,它封装了 FFmpeg 的 libavformat 库。
  3. 调用 mov_find_next_sample() 函数查找下一个音/视频样本(sample)的位置(offset)。
  4. 调用 avio_seek() 检查 FFmpeg 的内部缓冲区。
  5. 判断跳跃距离:
    • 如果跳跃距离 大于 32KB,则未命中FFmpeg缓冲区。
    • 进入下一级缓存检查(如谷歌的2MB缓冲区)。
    • 若仍未命中,则最终发起一个新的HTTP 206范围请求

3.3 源码分析

mov_find_next_sample 函数中,其核心逻辑是遍历所有流(音频、视频),找到“下一个”应该读取的样本。其中一个关键策略是:

  • 当不同轨道的样本解码时间戳(DTS)差值小于等于1秒(AV_TIME_BASE)时,优先选择文件位置(pos)更靠前的样本,目的是减少寻址(seek)操作。

这个函数受一个名为 interleaved_read 的开关影响。其注释说明:“在解复用器层对多个轨道的包进行交错。对于交错不良的文件,这可以防止由于不同轨道之间包的大间隙而引起的播放问题……然而,对于非常交错不良的文件,这可能会导致过多的寻道操作。”

什么是理想的交错?
一个交错良好的 MP4 文件会将音频和视频数据包按照时间顺序交替排列:

[视频包1] [音频包1] [视频包2] [音频包2] [视频包3] [音频包3] ...

这种存储方式能显著减少磁盘/网络访问次数,降低播放缓冲区需求,并支持高效的渐进式下载。

什么是不良交错?
如果音视频数据是集中存储的,例如:

[视频包1] [视频包2] [视频包3] ... [音频包1] [音频包2] [音频包3] ...

就会导致播放器需要在视频和音频数据块之间频繁跳转,从而引发大量 HTTP Range 请求,导致播放卡顿和带宽浪费。

3.4 测试验证

由于谷歌浏览器使用 FFmpegDemuxer,我们可以直接用 FFmpeg 命令行工具模拟其行为,分析问题。

测试视频: longbad.mp4(一个存在严重音视频交错问题的视频)

1. 模拟浏览器默认(交错读取)行为:

ffmpeg -i https://xxxx/longbad.mp4 -ss 60 -t 5.0 -y output.mp4 -loglevel trace 2>&1 | grep “Range”

统计 grep 输出的行数,发现为了播放5秒钟的视频,竟然发起了 5078次 Range 请求!

结合CDN请求日志和视频轨道分析数据(部分关键记录如下),可以清楚地看到问题:

  • Range 来回剧烈跳动。例如,在相近时间点,请求从字节位置 5967030 跳到了 46622680
  • 相同时间范围内的音频 offset 和视频 offset 在文件中的物理距离非常远。
request_range request_time ...
46641572- 181 ...
5967030- 96 ...
46622680- 83 ...
46654153- 186 ...

2. 模拟关闭交错读取的行为:

ffmpeg -interleaved_read 0 -i https://xxxx/longbad.mp4 -ss 60 -t 5.0 -y output.mp4 -loglevel trace 2>&1 | grep “Range”

统计结果显示,同样的5秒视频,仅发起了 3次 Range 请求。这证明了问题的根源正是 interleaved_read 模式在遇到交错不良文件时的低效行为。

4 分析原因

4.1 为什么一开始会出现3个206请求?

原因:moov box 位于文件末尾。

对于正常的MP4文件(moov在头部),播放流程是:读取 ftyp + moov → 开始读取 mdat 数据(通常只需1个206请求)。

问题视频的 moov 元数据盒子位于文件尾部,其文件结构树形视图类似于:

  • ftyp (FileTypeBox)
  • free (FreeSpaceBox)
  • mdat (MediaDataBox)
  • moov (MovieBox)

这导致播放流程变为:

  1. 请求文件开头,试图寻找 moov,未果(第一个206)。
  2. 需要到文件尾部获取 moov(第二个206)。
  3. 拿到索引后,回到文件开始位置播放数据(第三个206)。

4.2 为什么会发起多个206请求,且Range范围来回跳动?

核心原因:视频文件音视频交错不良 + FFmpeg 的 interleaved_read 读取策略。

交错不良的文件布局:
通过分析文件生成的数据包交错模式图可以直观看到问题。图中,X轴代表数据包在文件中的物理位置索引,Y轴代表流类型(红色为视频包,青色为音频包)。在不良交错的文件中,会呈现大块的单一颜色区域,例如文件前半部分全是音频包,后半部分全是视频包。

[音频包1] [音频包2] [音频包3] ... ... [视频包1] [视频包2] [视频包3]

FFmpeg的读取策略导致来回跳跃:
interleaved_read 模式下,mov_find_next_sample() 函数会尝试交替读取音视频包。当它读完一个视频包,需要找下一个音频包时,由于上述不良布局,下一个音频包可能远在文件开头(距离数十MB)。根据“DTS差≤1秒时选择位置靠前样本”的逻辑,FFmpeg会发起一个到文件开头的Range请求去获取那个音频包。紧接着,读完这个音频包后,下一个视频包又远在文件末尾,于是又发起一个跳回文件尾部的Range请求。如此循环,形成了“来回跳动”的大量206请求。

4.3 为什么使用ffmpeg处理视频后能解决这个问题?

原因:ffmpeg 命令在复制流(-c copy)并添加 -movflags faststart 时,会对音视频数据包的物理存储位置进行重新编排(交织)。

处理后的文件,其数据包交错模式图会呈现出红青两色紧密交替的细条纹状,这表明音视频包已经按照时间顺序良好地交错排列。

ffmpeg -i bad.mp4 -c copy -movflags faststart good.mp4 这条命令的作用是:

  • -i:指定输入文件。
  • -c copy:进行流拷贝,不重新编码。
  • -movflags faststart:将 moov 盒子移动到文件开头,并在移动过程中优化音视频包的交错存储

其底层通过 av_interleaved_write_frame()ff_interleave_packet_per_dts() 等函数,依据数据包的解码时间戳(DTS)和音频预加载(audio_preload)等设置,对包进行排序和交错写入,从而生成一个适合流式播放、无需频繁远程寻址的文件。

5 总结

流媒体格式(如HLS、DASH)在带宽使用效率和播放流畅度上更具优势,但存在一定的改造成本。MP4格式因兼容性极强和部署简单,仍是许多场景下的主流选择。

综合成本与收益,我们建议:

  • 对于短视频:统一使用 ffmpeg -i input.mp4 -c copy -movflags faststart output.mp4 命令进行处理。这能确保 moov 盒子位于文件头部,并优化音视频交错,从根本上避免因播放器频繁寻址而引发的CDN带宽激增问题。
  • 对于长视频:采用 HLS 等流媒体格式进行分发,实现按需加载视频分片,从而提升播放体验并优化带宽利用率。

参考资料

[1] 一条命令让CDN视频带宽成本降低90%!!!, 微信公众号:mp.weixin.qq.com/s/EniHocKC5x4eAV9MDgOwOQ

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:NAND闪存坏块对OpenWrt系统启动的影响与NMBM机制验证
下一篇:Skills工程精讲:Claude Skills如何赋予AI代理专业化执行能力
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-10 03:31 , Processed in 0.364859 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表