以下均为 ChatGPT总结。

从 B站缓存中提取原始音频(Windows + FFmpeg)

最近折腾了一下 B站缓存音频的提取,发现很多所谓“音频下载器”其实会偷偷转码,导致下载后的音频听起来没有在线播放那么“润”。

尤其是:

  • 雨声
  • 白噪音
  • 环境音
  • 空间氛围类音频

非常容易听出区别。

于是研究了一下 B站客户端下载后的缓存结构,以及如何直接从缓存中提取原始音轨。


一、B站客户端下载的视频为什么打不开?

B站客户端下载的视频通常长这样:

1
2
173030787-1-30112.m4s
173030787_nb2-1-30280.m4s

它们不是普通 mp4,而是:

DASH 分离流

也就是:

  • 视频流单独存储
  • 音频流单独存储

播放器播放时再动态合并。

因此:

1
.m4s ≠ 损坏文件

而是:

1
MPEG-DASH 分片媒体流

二、为什么 ffmpeg 会报错?

直接:

1
ffmpeg -i xxx.m4s

可能出现:

1
Invalid data found when processing input

原因并不是文件损坏。

而是:

B站在文件前面插入了额外字节

正常 MP4/M4A 文件开头应该包含:

1
ftyp

这是 MP4 容器的文件头标识。

但 B站缓存文件前面可能塞了一些:

  • 自定义标记
  • metadata
  • 校验数据
  • 客户端头信息

导致:

1
ftyp 不在文件开头

于是 ffmpeg 无法识别。


三、修复方法:寻找真正的 MP4 文件头

使用 Python 搜索:

1
b.find(b'ftyp')

找到真正的 MP4 header。

然后从该位置重新截取文件即可。


四、修复脚本

修复 m4s 文件

1
python -c "from pathlib import Path; p=Path('173030787_nb2-1-30280.m4s'); b=p.read_bytes(); i=b.find(b'ftyp'); print('ftyp at', i); Path('fixed_audio.m4s').write_bytes(b[i-4:] if i>=4 else b)"

运行后会输出:

1
ftyp at 13

说明:

真正的 MP4 文件头在第 13 字节附近。

脚本会自动生成:

1
fixed_audio.m4s

五、为什么是 i-4?

MP4 的 box 结构:

1
[4字节大小][4字节类型]

例如:

1
00 00 00 20 ftyp

因此:

ftyp 前面的 4 字节是 box 长度信息。

不能丢失。

所以需要:

1
i - 4

而不是:

1
i

六、使用 ffmpeg 解析修复后的文件

检测音频信息:

1
ffmpeg -i fixed_audio.m4s

结果:

1
Audio: aac (LC), 44100 Hz, stereo, 323 kb/s

说明:

  • AAC-LC
  • 44.1kHz
  • 323kbps
  • 已经是较高质量音轨

并且:

1
Packed by Bilibili XCoder

说明这是 B站原始缓存流。


七、无损提取音频

提取为 m4a

1
ffmpeg -i fixed_audio.m4s -c copy output.m4a

注意:

1
-c copy

表示:

不重新编码

只是:

1
重新封装(remux)

因此不会产生二次音质损失。


八、为什么不要转 MP3?

很多下载器会:

1
AAC/Opus → MP3

属于:

有损转有损

尤其对白噪音、环境音损失明显。

因此:

如果已经是:

  • AAC
  • Opus

最好直接保留原编码。


九、这次排查得到的结论

很多时候:

1
在线播放比下载更好听

并不是错觉。

原因可能是:

1
2
3
4
5
在线播放:
原始 Opus / 高码率 AAC

下载:
被工具二次转码后的 MP3/AAC

因此:

最稳定的方法

其实是:

1
2
3
客户端下载
→ 获取原始 DASH 缓存
→ ffmpeg 无损提取

而不是依赖各种“音频下载器”。


十、一些额外知识

m4s 不是特殊加密格式

本质仍然是:

1
ISO BMFF / MP4

只是:

  • 分片化
  • 流媒体化
  • header 可能不标准

真正 DRM 加密会复杂得多

例如:

  • Widevine
  • PlayReady

那种需要:

  • license server
  • decryption key

并不是简单修 header 就能解决。

而 B站这里更像:

1
“防止直接拖文件”

并不是真正 DRM。