[原創] 關於MP3文檔格式的解剖

TonyDeng UID.2870126
2019-01-08 发表

本帖最后由 TonyDeng 于 2019-1-8 16:51 编辑

目前流行的MP3歌曲,一般是ID3 v2.3格式文檔,它由三部分組成: 1.頭部(Header) 2.一系列幀(Frame)信息,分別記錄相關的信息,比如專輯名稱、歌手、版權信息等等 3.音頻數據,這部分才是歌曲的真正數據,供播放器解碼播放。 對ID3格式的詳細説明,參考網址:http://id3.org/Home 下面是UWP框架下C#寫的解讀代碼: public class MP3 { public StorageFile SongFile { get; set; } public Header Header { get; set; } public List<Frame> Frames { get; set; } public byte[] Audio { get; set; } public async Task Load() { using (Stream stream = (await SongFile.OpenAsync(FileAccessMode.ReadWrite, StorageOpenOptions.AllowReadersAndWriters)).AsStream()) { byte[] buffer = new byte[stream.Length]; if (await stream.ReadAsync(buffer, 0, buffer.Length) == buffer.Length) { int index = 0; Header = new Header(buffer, ref index); Frames = new List<Frame>(); FrameTag frameTag = new FrameTag(buffer, ref index); while (!frameTag.IsEmpty) { Frame frame = new Frame(buffer, ref index, frameTag); Frames.Add(frame); frameTag = new FrameTag(buffer, ref index); } index = Header.TagSize + Header.Size; Audio = new byte[stream.Length - index]; Array.Copy(buffer, index, Audio, 0, Audio.Length); } } } public async Task Update() { Header.Size = (Frames.Count + 1) * FrameTag.TagSize + Frames.Sum(f => f.Tag.Size); using (Stream stream = (await SongFile.OpenAsync(FileAccessMode.ReadWrite, StorageOpenOptions.AllowReadersAndWriters)).AsStream()) { await stream.WriteAsync(Header.GetBytes(), 0, Header.TagSize); foreach (Frame frame in Frames) { await stream.WriteAsync(frame.Tag.GetBytes(), 0, FrameTag.TagSize); await stream.WriteAsync(frame.GetBytes(), 0, frame.Tag.Size); } await stream.WriteAsync(new byte[FrameTag.TagSize], 0, FrameTag.TagSize); await stream.WriteAsync(Audio, 0, Audio.Length); stream.SetLength(stream.Position); } } } public class Header { public static int TagSize => 10; public string ID { get; set; } // MP3文檔的標識,必須為“ID3“ public byte Ver { get; set; } // 版本號,若為3則表示V2.3版本 public byte Revision { get; set; } // 副版本號,通常為零 public byte Flag { get; set; } // 附加標識,bit[0]為非同步編碼標識,bit[1]為擴展標簽頭標識,bit[2]為測試標識 public int Size { get; set; } // 頭部數據尺寸 public bool IsAvailable => (ID == “ID3“) && (Ver == 3) && (Size > 0); public Header() { ID = ““; Ver = 0; Revision = 0; Size = 0; } public Header(byte[] buffer, ref int startIndex) : this() { ID = Encoding.ASCII.GetString(buffer, startIndex, 3); Ver = buffer[startIndex + 3]; Revision = buffer[startIndex + 4]; Flag = buffer[startIndex + 5]; Size = (buffer[startIndex + 6] << 7 << 7 << 7) + (buffer[startIndex + 7] << 7 << 7) + (buffer[startIndex + 8] << 7) + buffer[startIndex + 9]; startIndex += TagSize; } public byte[] GetBytes() { Encoding.ASCII.GetBytes(ID, 0, ID.Length, _bytes, 0); _bytes[3] = Ver; _bytes[4] = Revision; _bytes[5] = Flag; _bytes[6] = (byte)((Size >> 7 >> 7 >> 7) & 0x7F); _bytes[7] = (byte)((Size >> 7 >> 7) & 0x7F); _bytes[8] = (byte)((Size >> 7) & 0x7F); _bytes[9] = (byte)(Size & 0x7F); return _bytes; } public override string ToString() { return $“{ID}v2.{Ver}.{Revision} {Convert.ToString(Flag, 2)} {Size:N0}Bytes“; } private byte[] _bytes = new byte[TagSize]; } 這裏關於頭部尺寸Size的計算公式,才是正確的。在網上比如CSDN上有大量的討論,基本都栽在這個計算上,沒幾個是正確的,所以不斷地有人反饋解讀錯誤,無法達成目的。 另外,對幀集合的讀取,有一點必須注意:每個幀之間是沒有明確分界標志的,必須逐個從其幀頭標簽中提取後隨的幀體尺寸,依次讀入。判斷幀表結束的標志與C/C++的傳統做法一樣,就是最後會遇到一個空標簽(10個字節的0)。關鍵之處,有些編碼器是不止寫入10個字節的空白區域,我見過有寫入4K字節空白的,所以,解讀的時候,千萬不要自以爲是讀到空白的後面就是音頻數據了,真正的音頻數據開始區域,必須根據頭部Header中Size尺寸指示來尋找。在我這個代碼中,當自己重寫文檔的時候,統一把這部分空白截成10個字節,免得浪費空間。 一些MP3歌曲,裏面不單有v2.3的信息,還可能有v1的信息。v1這部分好辦,它必定在文檔最尾部的128個字節處,定位到那裏查找有沒有v1的標簽信息就可以了。

敬告:
为防止不可控的内容风险,本站已关闭新用户注册,新贴的发表及评论;
你现在看到的内容只是互联网用户曾经发表的言论快照,仅用于老用户留存纪念,且仅与科技行业相关,全部内容不代表本站观点及立场;
本站重新开放前已针对包括用户隐私、版权保护、信息安全、国家政策在内的各种互联网法律法规要求,执行了隐患内容的自查、屏蔽和删除;
本站目前所属个人主体,未有任何盈利安排与计划,且与原WFUN.COM所属公司不存在任何关联关系;
如果本帖内容或者相关资源侵犯到您的合法权益,或者您认为存在问题,那么请您务必点此举报或投诉!
全部回复:
万****城 UID.2956327
2019-01-08 回复

@WFun_ljj9898发帖:***图片停止解析***

HavokPro UID.1198128
2019-01-08 回复

标记马克,以后些许用得上。

太学主 UID.2954924
2019-01-08 使用 Lumia 920 回复

本帖最后由 太学主 于 2019-1-8 20:47 编辑

感谢分享。

TonyDeng UID.2870126
2019-01-08 回复

QuoteHavokPro 发表于 2019-1-8 18:34
标记马克,以后些许用得上。


有點潔癖。下載了許多歌曲,其中有不少是信息不全的,或者是不統一,還有一些歌曲是從無損中轉換格式而來(320K的MP3音質跟無損人耳基本上無法分辨,但體積小許多可以帶在手機上),逐首歌整理是不現實的(Groove雖然也能編輯歌曲信息但那個界面其實不好用),就想寫個程序成批處理。後續的想法,是把歌詞(可以是同步或異步)嵌入到MP3中,供支持的播放器顯示歌詞,其實按現有的資料,自己做播放器顯示歌詞也是可以的了。國行的Groove,並不支持在OneDrive上播放歌曲,自己做的應該可以(還有一點小問題需要解決)。

Co****ME UID.2952099
2019-01-09 使用 Lumia 950 XL 回复

同马克一下,以后说不定会用。 嵌入歌词的歌曲,各家播放器原创的格式见过不少,但是真正下功夫去修改拓展 mp3 的不多,楼主加油

本站使用Golang构建,点击此处申请开源鄂ICP备18029942号-4联系站长投诉/举报