Skip to content

[Bug] 云盘歌曲带 ID3 前缀的 MP4/M4A 无法播放:FFmpeg 报 "moov atom not found" #329

Closed

Description

@acgmnc

[Bug] 云盘歌曲带 ID3 前缀的 MP4/M4A 无法播放:FFmpeg 报 "moov atom not found"

问题描述

播放网易云音乐云盘中的部分歌曲时无法播放,报错:

[error] (player) NETWORK_ERROR: Error: [DecodeFailed] decode failed:
  打开网络音频失败: http://m703.music.126.net/.../jd-musicrep-privatecloud-audio-public/obj/.../xxx.mp3?vuutv=...
  FFmpeg error -1094995529: Invalid data found when processing input

-1094995529 = AVERROR_INVALIDDATA

复现步骤

  1. 向网易云音乐云盘上传一个 M4A / AAC(MP4 容器) 格式的音频文件
  2. 在 SPlayer-Next 中登录网易云账号,进入音乐云盘
  3. 播放该歌曲 → 失败

特征:与该歌曲的音频内容、时长、码率无关;换成同账号云盘里上传格式为 MP3/FLAC 的歌曲则一切正常。

根本原因

⚠️ 2026-10-01 更正:本节原先对机制的描述("id3v2 demuxer 不会把数据接力给 mov demuxer")不准确。经 @apoint123 指正并独立验证,真正原因是 FFmpeg 上游 2025-07 引入的回归(FFmpeg@9d037c54):该提交引入 AVFMT_FLAG_ID3V2_AUTO,仅对 mp3 / aac / tta / wav 剥离 ID3v2,mov / mp4 不在白名单内,于是 Rewind 回偏移 0 后 ID3 不再被跳过,mov_read_default 把 'ID3\x03' 当作 box 长度(约 1.14 GiB)解析,Seek 越界后扫不到 moov。

本节以下的事实描述仍然成立(文件确实是 ID3+MP4、剥掉即可播放、报错文本一致),仅"为什么"的归因以本条更正为准。完整分析与独立验证见下方评论。

网易云在云盘文件最前面附加了一个 ID3v2.3 标签(含内嵌 JPEG 封面,约 35 KB)。文件实际结构变成:

[ID3v2.3 标签 (35 KB)] + [MP4/M4A 容器 (ftyp / moov / sidx / moof / mdat, HE-AAC 音频)]

mov demuxer 从偏移 0 开始按 MP4 box 解析,读到的前 8 字节 49 44 33 03 00 00 00 00 被当作 box 长度 0x49443303(约 1.14 GiB),尝试跳过该 payload 导致 Seek 越界至文件末尾,最终报:

[mov,mp4,m4a,3gp,3g2,mj2] moov atom not found
Invalid data found when processing input

验证证据

1. 完整下载该 URL(806606 字节,HTTP 200 / Content-Type: audio/mpeg)

$ file song.mp3
song.mp3: Audio file with ID3 version 2.3.0, contains:
          - ISO Media, MP4 Base Media v5

$ ffprobe song.mp3
[mov,mp4,m4a,3gp,3g2,mj2] moov atom not found
song.mp3: Invalid data found when processing input

2. 切掉前 35326 字节(ID3 标签)后,同一文件完美解码

$ dd if=song.mp3 of=song.m4a bs=1 skip=35326
$ ffprobe song.m4a
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'song.m4a':
  Duration: 00:02:18.20, start: 0.000000, bitrate: 44 kb/s
  Stream #0:0[0x2](und): Audio: aac (HE-AAC) (mp4a / 0x6134706D),
                          48000 Hz, stereo, fltp, 50 kb/s (default)

3. 剥离 ID3 后的 MP4 atom 结构(文件是完好的分片 MP4)

偏移 35326   大小 36       ftyp
偏移 35362   大小 8        free
偏移 35370   大小 74       free
偏移 35444   大小 826      moov      ← 索引头存在
偏移 36270   大小 368      sidx
偏移 36638   大小 1048     moof
偏移 37686   大小 31477    mdat
偏移 69163   大小 580      moof

→ 文件本身完全正常,纯粹是 ID3 前缀导致 FFmpeg 无法识别。

4. 原生音频引擎日志佐证

app-data/logs/native/audio-engine.*.log 中,同一首歌每次加载后都没有后续的"打开音频输出配置":

22:28:24  INFO audio_engine::bindings::player: 加载音频源 source=http://.../obj/woDDmMOBw6PClWzCnMK-/... auto_play=true
          (无后续 → 加载失败)
22:28:25  INFO audio_engine::bindings::player: 加载音频源 source=http://.../obj/woHCgMKXw6XCmDjDj8Oj/... auto_play=true
22:28:26  INFO audio_engine::audio_output: 打开音频输出配置 id="pipewire:output_default" ...   ← 正常的歌能走到这一步

(ID3 解析失败后,插件兜底 chksz.splayer-source 也因未配置 API Key 而失败,所以没有任何备用音源可用。)

影响范围

上传到云盘的原始格式 网易附加 ID3 后 SPlayer-Next 官方客户端
MP3 ID3 + MP3 ✅ 正常(ID3 是 MP3 原生的一部分) ✅
FLAC ID3 + FLAC ✅ 正常 ✅
M4A / AAC(MP4 容器) ID3 + MP4 ❌ 无法播放 ✅

因此用户会观察到"有的云盘歌能听、有的听不了",且与音频内容无关,只取决于上传时的原始文件格式。

建议修复方向

在把 URL 交给 FFmpeg 之前,检测并跳过 ID3v2 标签。可选方案:

  1. 自定义 AVIOContext:在 read 回调中读取前 10 字节,若为 ID3,用 synchsafe 整数解析出标签长度(4 字节,每字节低 7 位有效)并跳过,其余数据透传给 FFmpeg
  2. 预处理:先拉取并剥离 ID3 头,再交给 FFmpeg 打开
  3. 若能判断内容为 fMP4,也可配合 -f mp4 强制指定 demuxer(仍需解决偏移问题)

环境

  • SPlayer-Next: 1.1.0-1(AUR splayer-next-bin,Electron 构建)
  • 系统: CachyOS(Arch Linux 衍生),GNOME 50.5 / Wayland,PipeWire
  • FFmpeg: Lavc63.1.102(系统版本,用于独立对比验证)
  • 账号: 网易云音乐(云盘歌曲,均为本人上传)

补充说明

该现象不影响官方客户端,说明网易返回的数据本身没有问题;问题出在解码链路的 ID3 处理上。附带说明:所有失败 URL 的路径均含 jd-musicrep-privatecloud-audio-public,即未匹配到曲库的私有云文件(本人原创歌曲必然属于此类)。

Activity

  1. github-actions commented on Sep 27, 2026

    @github-actions

    👋 @acgmnc

    感谢提交 Issue!我们已经收到您的反馈,会尽快确认您的问题。

    Thanks for opening this issue! We have received your report and will review it as soon as possible.

  2. acgmnc commented on Sep 27, 2026

    @acgmnc
    Author

    补充:最小复现(无需网易云账号)

    已拿到该曲目的上传原文件,确认网易云只是在其前面附加了 ID3v2.3 标签,音频数据一个字节都没改;并进一步验证出最小复现——只要在任意 MP4/M4A 前拼一个 ID3v2.3 头(哪怕只有 10 字节的空标签),FFmpeg 就会报完全相同的错。

    # 1. 造一个几秒的 M4A
    ffmpeg -f lavfi -i "sine=frequency=440:duration=3" -c:a aac -b:a 64k mini.m4a
    
    # 2. 在前面拼一个最小 ID3v2.3 头(10 字节,空标签)
    printf 'ID3\x03\x00\x00\x00\x00\x00\x00' > id3.bin
    cat id3.bin mini.m4a > broken.m4a
    
    # 3. 复现
    ffprobe mini.m4a
    #   Duration: 00:00:03.00, Audio: aac (LC), 44100 Hz, mono     ← 正常
    ffprobe broken.m4a
    #   [mov,mp4,m4a,3gp,3g2,mj2] moov atom not found
    #   Invalid data found when processing input                  ← 与播放失败日志一致

    与标签内容、编码参数都无关:真实案例的标签含 35 KB 封面图、音频是 HE-AAC 48kHz 立体声;最小复现用空标签 + AAC-LC 44.1kHz 单声道,行为完全一致。只要 AudioReader::new(http) 读到的字节以 ID3 开头、其后是 MP4 容器,就会落入同一个失败分支。

    真实案例的字节级对照

    文件 字节数 SHA256
    上传的原文件 771280 d19d65e514f6a474a0fa137b9e46b1e01e4a2555908fdc79d445ba7758c222b8
    网易云 CDN 返回的文件 806606 —
    上式剥掉前 35326 字节的 ID3v2.3 标签后 771280 d19d65e514f6a474a0fa137b9e46b1e01e4a2555908fdc79d445ba7758c222b8

    剥离 ID3 后的 SHA256 与原文件完全一致,即网易云未做任何转码或再封装,问题纯粹由「ID3 前缀 + MP4 容器」这个组合引起。

    对正文「影响范围」表的一处精确化

    正文表格中「上传到云盘的原始格式」这一列,更准确的说法是文件的真实容器格式(与扩展名无关)——本例的原文件扩展名是 .mp3,但容器实际是 MP4,同样触发该问题。修正后的表述:

    文件真实容器 扩展名 网易附加 ID3 后 FFmpeg
    MP3 .mp3 ID3 + MP3 ✅ 正常(ID3 是 MP3 原生结构)
    FLAC .flac ID3 + FLAC ✅ 正常
    MP4 / M4A 任意(含被误命名为 .mp3 的情况) ID3 + MP4 ❌ 无法播放
  3. added a commit that references this issue on Sep 30, 2026
    181640d
  4. apoint123 commented on Oct 1, 2026

    @apoint123
    Member

    经过测试,这个问题的根本原因是 FFmpeg 上游在 2025 年 7 月引入的一处回归

    在旧版 FFmpeg 中,avformat_open_input 遇到输入流时默认会无条件探测并读走 ID3v2 标签,流指针会自动 Seek 至 ID3 之后;而 FFmpeg 在提交 FFmpeg/FFmpeg@9d037c5 中引入了 AVFMT_FLAG_ID3V2_AUTO 格式白名单机制(仅包含 mp3、aac、tta、wav、flac),将 mov/mp4 排除在外。虽然 MP4 本身不应使用 ID3 标签,但这一改动导致了探测阶段与打开阶段的割裂:

    1. av_probe_input_format3 探测时仍保留了跳过 ID3 的逻辑,因此 mov_probe 看到的切片是合法的 ftyp box,并给出了匹配满分
    2. 探测完成后,流被 Rewind 至偏移量 0。但由于 mov 不在 is_id3v2_format 白名单中,FFmpeg 不再自动剥离 ID3 头部
    3. mov_read_default 从偏移量 0 开始解析 MP4 Box,读取前 8 字节 49 44 33 03 00 00 00 00:
      • 大端序 0x49443303(ASCII 'ID3\x03')被识别为 Box 长度:1229206275 字节(约 1.14 GiB)
      • 后续 0x00000000 被识别为未知 Box 类型,demuxer 尝试跳过该 Box 的 payload,导致直接 Seek 越界至文件末尾
      • 循环退出时因未扫到 moov box,抛出 moov atom not found(-1094995529 / AVERROR_INVALIDDATA)

    受影响环境信息:

    • 改动引入于 FFmpeg 8.1 及后续所有版本
    • SPlayer Next 目前使用的是基于 ffmpeg-audio 打包的 8.1.2 版本,因此触发该回归
    • 网易云内置较旧的 FFmpeg 4.3,因此表现正常

    提交 181640d 所做的修复目测是没问题的,但它本质上是对网易云不规范行为和 FFmpeg 回归的 Workaround,建议在注释中补充上下文,避免其他人对这段防御性代码产生困惑;长远来看也可以考虑向 FFmpeg 上游报告或提交修复

  5. acgmnc commented on Oct 1, 2026

    @acgmnc
    Author

    感谢指正 —— 这个分析比 issue 正文准确得多。正文里对机制的描述("id3v2 解析器不会把数据接力给 mov 解析器")是错的,真正的原因是 AVFMT_FLAG_ID3V2_AUTO 白名单把 mov/mp4 排除在外,导致 Rewind 后不再剥离 ID3。抱歉把方向带偏了一步。

    在你的结论基础上补一个独立验证:

    系统 FFmpeg(n9.0.2 / libavformat 63)同样复现:

    $ ffmpeg -f lavfi -i "sine=frequency=440:duration=3" -c:a aac -b:a 64k mini.m4a
    $ printf 'ID3\x03\x00\x00\x00\x00\x00\x00' > id3.bin
    $ cat id3.bin mini.m4a > broken.m4a
    
    $ ffprobe mini.m4a
      Duration: 00:00:03.00   Audio: aac (LC), 44100 Hz, mono     ← 正常
    
    $ ffprobe broken.m4a
      [mov,mp4,m4a,3gp,3g2,mj2] moov atom not found                ← 复现
      Invalid data found when processing input
    
    $ tail -c +11 broken.m4a > fixed.m4a
    $ ffprobe fixed.m4a
      Duration: 00:00:03.00   Audio: aac (LC), 44100 Hz, mono     ← 剥掉 ID3 即恢复

    即:该回归在目前最新的 n9.0.2 上仍未修复,与 SPlayer-Next 内 8.1.2 的行为一致。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions