从 Web 音频流中识别并跳过无效(或专有?)MP3 帧 headers

Recognize and skip invalid (or proprietary?) MP3 frame headers from Web audio streams

我正在编写一个 MP3 解码器(不是 re-play 任何声音,而是分析频率)。

我可以成功识别 ID3v1 和 ID3v2 标签并完全跳过它们(包括 ID3v2 NULs 填充),因为我对此元数据不感兴趣。我只是在关注频率。

我还可以获取并正确解释所有 MP3 帧 header(进行所有可用的测试,不是太多)。立即 window 的一小段摘录告诉我框架是关于什么的:

...
2131 until pos. 2226975 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
2132 until pos. 2228020 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
2133 until pos. 2229065 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
2134 until pos. 2230110 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
2135 until pos. 2231155 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
2136 until pos. 2232200 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
2137 until pos. 2233245 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
2138 until pos. 2234290 FFFBE264, EMp3Vrs1, EMp3LayIII, 320, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 1009 B
...

如果我将 MP3 帧的整个长度(包括 CRC(如果有的话)、边信息、Huffman-encoded 数据和辅助数据(如果有的话))写回 FileStream object , 命名为 .mp3,我可以很好地聆听标题。

这适用于存储在本地或 LAN 中某处的 MP3 文件,不会遇到错误 header,不会发出任何误报。成功。

进入Web stream。如果我将其提供给我的 FileStream object,几百帧一切正常,但突然传输了很多无效帧:

...
1291 FFFB9264, EMp3Vrs1, EMp3LayIII, 128, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 382 B
1292 FFFB9244, EMp3Vrs1, EMp3LayIII, 128, 44100, 1, EMp3ChMJointStereo, 0, CRC: 0, Data: 382 B
1293 FFFB9264, EMp3Vrs1, EMp3LayIII, 128, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 382 B
1294 FFFB9264, EMp3Vrs1, EMp3LayIII, 128, 44100, 1, EMp3ChMJointStereo, 2, CRC: 0, Data: 382 B
34B5FF96 is not a valid header
FF96C517 is not a valid header
FFFFFFF8 is not a valid header
FFFFF8F1 is not a valid header
FFF8F1E1 is not a valid header
1295 FFF32191, EMp3Vrs2, EMp3LayIII, 16, 22050, 0, EMp3ChMDualChannel, 1, CRC: 0, Data: 68 B
There are 136 B of pre-header-data
...

在下一个有效 header 出现之前,这些无效的 header 后跟一个可变长度的无法识别的字节序列。

这是相关流部分的十六进制转储:

0008-40a0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-40b0:  00 00 00 00-00 00 00 34-b5 ff 96 c5-17 59 00 ca  .......4 .....Y.. <- 34B5FF96, FF96C517
0008-40c0:  00 a0 00 67-00 08 00 4f-00 1e 00 1f-00 e2 00 b3  ...g...O ........
0008-40d0:  00 ac 00 cf-00 69 00 bf-00 ff ff ff-f8 f1 e1 d0  .....i.. ........ <- FFFFFFF8, FFFFF8F1, FFF8F1E1
0008-40e0:  00 a0 00 e0-00 78 00 5d-00 c3 00 00-00 09 00 83  .....x.] ........
0008-40f0:  00 20 00 04-00 80 00 dd-00 d0 00 45-00 08 00 80  ........ ...E....
0008-4100:  00 26 00 96-00 c5 00 ed-00 18 00 9c-00 a7 00 a9  .&...... ........
0008-4110:  00 f5 00 1c-00 81 00 43-00 d8 00 61-00 78 00 ed  .......C ...a.x..
0008-4120:  00 d0 00 91-00 7f 00 a8-00 93 00 2a-00 2e 00 a2  ........ ...*....
0008-4130:  00 20 00 ee-00 a3 00 e9-00 35 00 75-00 77 00 ff  ........ .5.u.w.. <- FFF32191
0008-4140:  f3 21 91 19-0c da 9d 48-96 be 61 e2-cc db 5d d1  .!.....H ..a...].
0008-4150:  cd 40 8b bb-a3 8a 22 9e-26 65 36 aa-47 90 63 e2  .@....". &e6.G.c.
0008-4160:  46 72 21 fe-cb 78 0a 08-f1 48 24 da-89 25 55 78  Fr!..x.. .H$..%Ux
0008-4170:  6a 39 d2 65-68 11 14 6d-41 bb b5 45-91 05 3d b0  j9.eh..m A..E..=.
0008-4180:  03 18 4b 39-fb c2 dd 01-8e 95 15 34-39 93 b9 1f  ..K9.... ...49...
0008-4190:  47 c4 bf d8-61 04 85 08-a0 41 8c ca-7b b9 19 aa  G...a... .A..{...
0008-4197:  93 05 18 50-5c 51 d7                             ...P\Q.

我假设 TuneIn 确实在这里传输了一些元数据,但是我无法弄清楚要使用哪个协议,如果有的话。

问题是,这些块确实跨越了比我想象的更多的字节,因为下一个我认为有效的 header 是伪装的无效 header(FFF32191 不适合在其他帧中应用的 128 kbps 44100 Hz JointStereo 模型),因此很可能仍然属于那个可能的元数据块。

我对此很有信心,因为当我也保存这些 MP3 帧时,就像我对本地文件所做的那样,它们播放得很好(就好像我是从 Web 上录制的,所以只有 128 kbps),直到错误出现在几百帧之后。然后断断续续的噪音响起,尖叫声和口哨声持续了几分秒。

令人沮丧的是:如果我在浏览器中从 播放相同的地址,它播放得很好

我的问题:那些浏览器知道哪些我无法弄清楚?(我只想跳过正确的字节数以获得下一个有效帧。 )

(有一次我非常沮丧,我认为 TuneIn 确实恶意插入这些字节以阻止像我这样的人录制 "their" 音乐。但后来:浏览器知道如何处理这些流,所以...抱歉 TuneIn。)


编辑

再分析一下转储,我发现了一个有趣的内容,即一个 ASCII 字符串 "LAME3.98.4"。

0008-3d70:  9c 5f 26 ff-fb 92 64 fb-80 03 07 64-5d eb 0b 39  ._&...d. ...d]..9 <- FFFB9264 (frame 1293)
0008-3d80:  fe 60 89 ab-1d 41 87 1e-0a e1 2f 75-e6 24 a7 e9  .`...A.. ../u.$..
0008-3d90:  75 a6 2d 28-f2 9a ba 2c-23 07 79 68-e8 94 18 a4  u.-(..., #.yh....
0008-3da0:  68 d4 08 0e-f0 48 35 67-7e d2 ef 9e-73 13 ba a5  h....H5g ~...s...
0008-3db0:  fc f2 db d9-07 28 6c ce-3a 15 cb cf-39 af 99 5d  .....(l. :...9..]
0008-3dc0:  25 22 89 19-7c c4 22 a2-3b 51 e9 a7-ff ff ff f4  %"..|.". ;Q......
0008-3dd0:  59 83 1a 84-53 85 d6 99-25 20 49 8b-18 7f 25 5e  Y...S... %.I...%^
0008-3de0:  cd 41 69 75-e5 86 d6 8e-39 a3 96 1c-45 9e 69 66  .Aiu.... 9...E.if
0008-3df0:  d5 a6 b4 6d-e9 99 46 96-eb a3 73 74-4f de f2 96  ...m..F. ..stO...
0008-3e00:  34 48 60 70-10 5c 5f d9-2e dd af 44-2c c5 5a 48  4H`p.\_. ...D,.ZH
0008-3e10:  51 64 63 0d-92 af 62 0f-bb 55 ae b4-9d d1 8a f6  Qdc...b. .U......
0008-3e20:  66 41 e8 c3-68 54 ae 6d-0e 13 32 aa-bd ff ff f1  fA..hT.m ..2.....
0008-3e30:  56 00 4b 2a-24 49 25 15-98 77 98 71-36 d7 2d c2  V.K*$I%. .w.q6.-.
0008-3e40:  29 ce 8a b5-1b 72 84 e9-3f 03 4a da-74 e4 66 29  )....r.. ?.J.t.f)
0008-3e50:  fc 7d e7 fd-53 68 f4 7e-3b bb 2e 1b-97 e1 f1 8a  .}..Sh.~ ;.......
0008-3e60:  ba fd da 8b-8e 73 96 3c-20 40 ce 13-53 20 f0 6a  .....s.< .@..S..j
0008-3e70:  6d 9d cf c6-fa 84 f1 48-84 67 ef 51-af 8c ec 9f  m......H .g.Q....
0008-3e80:  7f ff ce 15-32 ca b1 ac-f5 e5 48 e8-0c 38 23 c3  ....2... ..H..8#.
0008-3e90:  05 02 b5 55-4c 41 4d 45-33 2e 39 38-2e 34 55 55  ...ULAME 3.98.4UU <- LAME3.98.4
0008-3ea0:  55 55 08 83-c5 04 58 55-e4 b3 30 3a-c9 da 85 3d  UU....XU ..0:...=
0008-3eb0:  11 80 7d 6d-62 41 5b d8-42 9a c2 a0-56 72 77 83  ..}mbA[. B...Vrw.
0008-3ec0:  4a d4 79 4b-28 de 4c 7f-2d 2c 7d b9-e0 bb 1d d8  J.yK(.L. -,}.....
0008-3ed0:  b6 fd b6 f3-ed 9a ba 09-49 00 6d 5f-fd 8a 77 cf  ........ I.m_..w.
0008-3ee0:  df 3f f4 70-3a 29 1c 4a-b7 39 6f 15-8c 74 fa fa  .?.p:).J .9o..t..
0008-3ef0:  f3 be 67 1f-db ae 2e 5e-90 dd 74 9c-ae 76 82 c1  ..g....^ ..t..v..
0008-3f00:  7b 3d 6a 03-05 0e aa a7-41 d6 df ff-ff 14 1a e3  {=j..... A.......
0008-3f10:  d8 a2 52 42-09 ff fb 92-64 f8 00 02-ff 4d 57 e1  ..RB.... d....MW. <- FFFB9264 (frame 1294)
0008-3f20:  e5 35 e0 4e-a9 7b dc 2c-c2 7f cb b5-31 75 a7 95  .5.N.{., ....1u..
0008-3f30:  35 f1 88 25-ec f4 f3 0e-73 a0 c0 6d-ee a0 bf 15  5..%.... s..m....
0008-3f40:  d8 b9 5d 7d-ce d4 c5 84-5a 4a 97 15-ba 22 08 09  ..]}.... ZJ..."..
0008-3f50:  b8 ec e8 3f-b1 22 89 b0-72 6d d7 db-75 b7 3b f4  ...?.".. rm..u.;.
0008-3f60:  b7 56 dd e3-43 0e 36 99-33 00 00 00-00 00 00 00  .V..C.6. 3.......
0008-3f70:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3f80:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3f90:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3fa0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3fb0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3fc0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3fd0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3fe0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-3ff0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4000:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4010:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4020:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4030:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4040:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4050:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4060:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4070:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4080:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-4090:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........
0008-40a0:  00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00  ........ ........ <- Start of above dump
0008-40b0:  00 00 00 00-00 00 00 34-b5 ff 96 c5-17 59 00 ca  .......4 .....Y.. <- Invalid header

LAME 3.98.4 的日期是 2010 年 4 月 14 日。但是,它在那里做什么? 回答:这是正常的,请参阅 Brad 在他的回答中的评论。

I assume, that TuneIn does transport some metadata here, but I am not able to figure out which protocol to use, if any.

这与 TuneIn 没有任何关系...它是一个 SHOUTcast 服务器,它使用 ICY-style 元数据。在任何情况下,除非您实际请求元数据(在 HTTP 请求 headers 中使用 Icy-MetaData: 1),否则您不会得到它。您最终会得到一个普通的原始 MPEG 流。

如果您想了解有关数据的更多信息,请在此处查看我的回答:

我在你的转储中没有看到任何 SHOUTcast-style ICY 元数据。

all goes well for a few hundred frames, but all at a sudden a lot of invalid frames are transmitted

有趣的是它最初有效,但后来失败了。

通常对于这些流,前几帧是不稳定的,因为服务器不必知道或关心流经它的数据。它只有一个 128KB 左右的固定缓冲区大小,并且可以任意分块,以便当客户端连接时,它会获得该缓冲区以及它之后的任何内容。也就是说,客户端 "needle dropped" 直接进入流,预计会自行同步。

这是通过同步字 0xFFF*0xFFE* 完成的。同步之前的任何东西都应该被丢弃。任何需要不可用位库的帧都应该被丢弃。最终在几帧之后,您将获得稳定的数据流来解码。

仔细检查以确保您没有来自之前 file/URL 的缓冲区,然后 re-sync 到初始连接时的流。

如果这不是问题所在,老实说,除了某些电台使用损坏的编解码器之外,我不知道还有什么建议。您会惊讶于有多少互联网广播电台正在使用 15 年前的 LAME 副本。

我可能还建议,如果可能的话,坚持使用现有的编解码器,并在将其转换回正常 PCM 后进行分析。

这是一个很难解决的问题。

当涉及到以预先计算的长度读取霍夫曼编码数据的部分时(使用 128 kbps 和 44.1 kHz 的 MP3,即 381 或 382 B),我依赖的事实是 IO.Stream的同步 Read() 方法实际上是 synchronous 并且 waits 请求的字节数可用(因为我的智能书说,当获得较少的字节时,流已经结束)。由于该地址是 Web 流,因此字节是定期提交的。因此我写道:

            ReDim gabMainData(0 To iDataSize - 1)
            s.Read(gabMainData, 0, iDataSize)

事实证明,偶尔(首先是在几百帧之后,但最多 1200 帧),Read 可以 return 更少的字节——尽管 Web 流没有完全结束了——,在适当变暗的字节数组的末尾留下许多 NUL 字节。

在这些情况下,使用额外的 ReadByte() 会有所帮助:

            ReDim gabMainData(0 To iDataSize - 1)
            iNumRead = s.Read(gabMainData, 0, iDataSize)
            Do While iNumRead < iDataSize
                iByte = s.ReadByte()
                If iByte = -1 Then
                    'End of stream activities.
                    ...
                End If
                gabMainData(iNumRead) = CByte(iByte)
                iNumRead += 1
            Loop

此修复后的测试 运行 现在报告 100,000 帧,没有任何问题,就像它是一张 CD,所以我对此很有信心。

现在打算将此作为我的标准 Read() 方法。

如果有更好的方法来同步执行此任务,请告诉我。