查看: 880|回复: 4

網上的資料良莠不齊

[复制链接]

签到天数: 651 天

连续签到: 5 天

[LV.9]以坛为家II

108

主题

5707

积分

0

支持

发表于 2018-12-9 12:30:21 来自手机 | 显示全部楼层 |阅读模式

马上注册,享受积分奖励和更多功能,让您轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册

x
本帖最后由 TonyDeng 于 2018-12-9 13:12 编辑

最近研究了MP3文檔的格式,在網上搜索資料,很多來源於CSDN的,幾乎都指向同一個出處,但那些資料卻竟然有問題的。大的方向、文字性的描述,基本不錯,但具體到細節和給出的代碼,就有問題,迫使我根據那些文字描述,自己設計測試檢驗,才最終把情況弄清。由此我覺得,很多人說上網搜解答,估計比較懸。何況那麽多人回復和探討,也有人提出了疑問,卻不見作者回復正確的解答,不了了之,但現實又很多程序能夠正常使用,估計都是抄現成的代碼或拿庫,沒有人自己研究弄清究竟的。

舉個例子,MP3文檔的頭標簽,裏面記載標簽幀總大小的那個解碼公式,網上資料就是錯的,但文章卻說自己的驗證正確,還拿一個MP3歌曲來具體示例分析,然而我用他這個公式去算自己保存的MP3音樂,卻總是錯誤的結果(最終的算法不是他那樣,其實也很簡單,逐個移位罷了)。這是一例,其實裏面還有若干很陰的陷阱,如果不是分析足夠多不同的MP3文檔,根本不可能發現,要寫出完善解讀的程序,照他們那樣,肯定是不行的。

下圖是網上的資料,大多源自這篇文章:




下圖是我最後的結果,這個才是對的:



来自:MS-7982 WIN10 PC版客户端

签到天数: 651 天

连续签到: 5 天

[LV.9]以坛为家II

108

主题

5707

积分

0

支持

 楼主| 发表于 2018-12-10 15:23:21 来自手机 | 显示全部楼层
續:現在把讀和寫都搞定了,可以隨便編輯MP3的標簽信息,增、查、刪、改都可以,包括向裏面添加專輯封面/封底圖和内嵌歌詞。
来自:MS-7982 WIN10 PC版客户端
[你知道吗]:

签到天数: 5 天

连续签到: 1 天

[LV.2]偶尔看看I

0

主题

13

积分

0

支持

发表于 2018-12-10 16:14:20 | 显示全部楼层
嗯,楼主的代码可以给我看一下吗
[你知道吗]:

签到天数: 651 天

连续签到: 5 天

[LV.9]以坛为家II

108

主题

5707

积分

0

支持

 楼主| 发表于 2018-12-10 17:00:43 来自手机 | 显示全部楼层
南宫珏 发表于 2018-12-10 16:14
嗯,楼主的代码可以给我看一下吗

你想要哪一塊?正在重構和完善中,原理可以解釋一下,這個論壇發代碼的功能不好看。
来自:MS-7982 WIN10 PC版客户端

签到天数: 651 天

连续签到: 5 天

[LV.9]以坛为家II

108

主题

5707

积分

0

支持

 楼主| 发表于 2018-12-10 21:00:32 来自手机 | 显示全部楼层
本帖最后由 TonyDeng 于 2018-12-10 21:10 编辑
南宫珏 发表于 2018-12-10 16:14
嗯,楼主的代码可以给我看一下吗

貼一個試試:

    public sealed class Header
    {
        public static int TagSize => 10;                                    // 標簽尺寸
        public bool IsValid => (ID == “ID3“) && (Ver == 3) && (Size > 0);   // 標識是否有效的MP3V2.3文檔

        public string ID { get; private set; }                              // MP3文檔的標識,必須為“ID3“
        public byte Ver { get; private set; }                               // 版本號,若為3則表示V2.3版本
        public byte Revision { get; private set; }                          // 副版本號,通常為零
        public byte Flag { get; private set; }                              // 附加標識,bit[0]為非同步編碼標識,bit[1]為擴展標簽頭標識,bit[2]為測試標識
        public int Size { get; set; }                                       // 頭部數據尺寸

        public Header(FileStream stream)
        {
            byte[] buffer = new byte[TagSize];
            if (stream.Read(buffer, 0, TagSize) == TagSize)
            {
                ID = Encoding.ASCII.GetString(buffer, 0, 3);
                Ver = buffer[3];
                Revision = buffer[4];
                Flag = buffer[5];
                Size = (buffer[6] << 7 << 7 << 7) + (buffer[7] << 7 << 7) + (buffer[8] << 7) + buffer[9];
            }
        }

        private byte[] CreateBytes()
        {
            byte[] data = new byte[TagSize];
            Encoding.ASCII.GetBytes(ID, 0, ID.Length, data, 0);
            data[3] = Ver;
            data[4] = Revision;
            data[5] = Flag;
            data[6] = (byte)((Size >> 7 >> 7 >> 7) & 0x7F);
            data[7] = (byte)((Size >> 7 >> 7) & 0x7F);
            data[8] = (byte)((Size >> 7) & 0x7F);
            data[9] = (byte)(Size & 0x7F);
            return data;
        }

        public void Write(FileStream stream)
        {
            if (stream.CanWrite)
            {
                byte[] bytes = CreateBytes();
                stream.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public sealed class FrameTag
    {
        public static int TagSize => 10;            // 標簽尺寸

        public string ID { get; set; }              // 標簽名,4字符的ASCII文本
        public int Size { get; set; }               // 幀體大小,4位整數
        public short Flag { get; set; }             // 標識

        public FrameTag(FileStream stream)
        {
            byte[] buffer = new byte[TagSize];
            if (stream.Read(buffer, 0, TagSize) == TagSize)
            {
                ID = Encoding.ASCII.GetString(buffer, 0, 4);
                Size = (buffer[4] << 8 << 8 << 8) + (buffer[5] << 8 << 8) + (buffer[6] << 8) + buffer[7];
                Flag = (short)((buffer[8] << 8) + buffer[9]);
            }
        }

        private byte[] CreateBytes()
        {
            byte[] data = new byte[TagSize];
            Encoding.ASCII.GetBytes(ID, 0, ID.Length, data, 0);
            data[4] = (byte)(Size >> 8 >> 8 >> 8);
            data[5] = (byte)(Size >> 8 >> 8);
            data[6] = (byte)(Size >> 8);
            data[7] = (byte)Size;
            data[8] = (byte)(Flag >> 8);
            data[9] = (byte)Flag;
            return data;
        }

        public void Write(FileStream stream)
        {
            if (stream.CanWrite)
            {
                byte[] bytes = CreateBytes();
                stream.Write(bytes, 0, bytes.Length);
            }
        }
    }

    public sealed class Picture
    {
        public string MIME { get; set; }            // 圖像類型
        public byte ID { get; set; }                // 圖像編碼
        public byte[] Data { get; set; }            // 圖像的二進制數據

        public Picture(byte[] data)
        {
            int startIndex = 1;     // 此信息必定是ASCII編碼,data[0]必定是零,故直接從1開始搜索即可
            MIME = MyTools.GetCString(Encoding.ASCII, data, ref startIndex);
            ID = data[startIndex];
            startIndex += 2;
            Data = new byte[data.Length - startIndex];
            Array.Copy(data, startIndex, Data, 0, Data.Length);
        }
    }

    public sealed class Frame
    {
        public FrameTag Tag { get; set; }
        public byte[] Data { get; set; }
        public string Content { get; set; }
        public Picture APic { get; set; }

        public Frame(FileStream stream)
        {
            Tag = new FrameTag(stream);
            if (Tag.Size > 0)
            {
                Data = new byte[Tag.Size];
                stream.Read(Data, 0, Tag.Size);
                if (Tag.ID != “APIC“)
                {
                    Content = ““;
                    int startIndex = FindUnicodeStartIndex();
                    switch (Data[0])
                    {
                        case 0:
                            Content = MyTools.GetCString(Encoding.ASCII, Data, ref startIndex);
                            break;
                        case 1:
                            Content = MyTools.GetCString(Encoding.Unicode, Data, ref startIndex);
                            break;
                        case 2:
                            Content = MyTools.GetCString(Encoding.BigEndianUnicode, Data, ref startIndex);
                            break;
                        case 3:
                            // V2.4 保留用於UTF-8
                            break;
                        default:
                            break;
                    }
                }
                else
                {
                    APic = new Picture(Data);
                    Content = APic.MIME;
                }
            }
        }

        // 對Unicode編碼,有引導符,普通為FFFE,但也有其他外文語言有更多的引導符,故須找出FFFE後的位置
        private int FindUnicodeStartIndex()
        {
            int startIndex = 1;
            if (Data[0] != 0)
            {
                for (int index = startIndex; index < Data.Length - 1; ++index)
                {
                    if (Data[index] == 0xFF && Data[index + 1] == 0xFE)
                    {
                        startIndex = index + 2;
                        break;
                    }
                }
            }
            return startIndex;
        }

        public void SetContent(string content)
        {
            byte[] bytes = Encoding.Unicode.GetBytes(content);
            Data = new byte[3 + bytes.Length];
            Data[0] = 1;
            Data[1] = 0xFF;
            Data[2] = 0xFE;
            Array.Copy(bytes, 0, Data, 3, bytes.Length);
            Content = content;
            Tag.Size = Data.Length;
        }

        public void Write(FileStream stream)
        {
            if (stream.CanWrite && Data.Length > 0)
            {
                stream.Write(Data, 0, Data.Length);
            }
        }
    }

    public sealed class MP3
    {
        public Header Head { get; set; }
        public List<Frame> Frames { get; set; } = new List<Frame>();
        public byte[] Audio { get; set; }

        public MP3(FileStream stream)
        {
            if (stream.CanSeek && stream.Seek(0L, SeekOrigin.Begin) == 0L)
            {
                Head = new Header(stream);
                if (Head.IsValid)
                {
                    do
                    {
                        Frame frame = new Frame(stream);
                        Frames.Add(frame);
                    } while (Frames[Frames.Count - 1].Tag.Size > 0);
                    Audio = new byte[(int)stream.Length - Head.Size - Header.TagSize];
                    if (stream.Read(Audio, 0, Audio.Length) != Audio.Length)
                    {
                        Audio = null;
                    }
                }
            }
        }

        private int GetHeadSize()
        {
            int size = 0;
            foreach (Frame frame in Frames)
            {
                size += FrameTag.TagSize + frame.Tag.Size;
            }
            return size;
        }

        public void UpdateFrames(FileStream stream)
        {
            if (stream.CanWrite && stream.CanSeek)
            {
                stream.Seek(0L, SeekOrigin.Begin);
                Head.Size = GetHeadSize();
                Head.Write(stream);
                foreach (Frame frame in Frames)
                {
                    frame.Tag.Write(stream);
                    if (frame.Tag.Size > 0)
                    {
                        frame.Write(stream);
                    }
                }
                stream.Write(Audio, 0, Audio.Length);
            }
        }
    }

用法是類似這樣:

                FileInfo file = new FileInfo(fileName);
                using (FileStream stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                {
                    MP3 mp3 = new MP3(stream);
                    if (mp3.Head.IsValid)
                    {
                        //DeleteFrame(mp3, "POPM");
                        //DeleteFrame(mp3, "TXXX");
                        foreach (Frame frame in mp3.Frames)
                        {
                            if (frame.Tag.Size > 0)
                            {
                                WriteLine($"{frame.Tag.ID} = [{frame.Content}]");
                            }
                        }
                        //mp3.UpdateFrames(stream);
                        WriteLine();
                    }
                }
来自:MS-7982 WIN10 PC版客户端
[你知道吗]:
您需要登录后才可以回帖 登录 | 注册

本版积分规则

        

网站地图| 小黑屋|京ICP证150706号|京B2-20160045|京网文[2018]3705-313号| 京公网安备11010802018258号

Powered by Discuz! X3.4 / Copyright 2010-2017 © 智机网 WFUN.COM Inc. All rights reserved.

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