MP3 信息读取

Posted 乌龙哈里

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MP3 信息读取相关的知识,希望对你有一定的参考价值。

MP3 信息读取

运行环境:Window7 64bit,.NetFramework4.61,C# 7.0; 编者:乌龙哈里 2017-03-13


参考:

章节:

  • Mp3 文件结构概述
  • ID3V1 标记
  • ID3V2 标记
  • Mp3 数据头

正文:

一、Mp3文件概述

最近买了个车载mp3播放器,需要下载mp3文件。下载了一堆后发现,很多网上的mp3音质不怎么好,如何来分辨呢。只好研究mp3的文件格式。

从 wiki 上获知,mp3的文件基本有3部分组成:

ID3v2x 标记
MP3 音乐数据
ID3v1 标记

二、ID3v1 标记

Id3v1标记位于 mp3 文件末尾的128 byte,结构如下:

起始长度内容
0 3 标记 Tag
3 30 歌曲名 SongTitle
33 30 表演者 Artist
63 30 专辑 Album
93 4 年份 Year
97 30 备注 Comment
127 1 歌曲类型 Genre

1、标记:
标记是以“TAG”3个大写字母为标记。

2、内容:
歌名、表演者、专辑、年份、备注都是以ASCII编码的字符

3、类型:
类型是1位数字,具体见 8.1. Appendix A - Genre List from ID3v1

三、ID3v2 标记

Id3v2 标记是放在mp3文件头,具体长度要通过计算才知道。结构分为两部分: Id3v2 Tag Header,Id3v2 Frame

1、Id3v2Tag Header:
这个头部放在mp3文件最前面,长 10byte,具体结构:

起始长度内容
0 3 标志 Tag
3 2 版本 version
5 1 Flag
6 4 Frame长度

标志是以“ID3”3个字符为标记;
版本为2个 byte的数字,前面1byte 一般为 03,表示id3v2.3版本,后面1byte的为小版本,一般为0;
Flag具体看ID3 tag version 2.3.0 ,一般为0;
Frame长度是指紧接着 10byte 长的头部数据后的id3v2tag Frame的长度,是4个byte的数字,每byte的二进制位中前面都为0,0xxxxxxxx,具体长度的算法用位计算为:

byte[] data=new byte[10];
//....(从文件中读取前10byte数据放入 data)
int FrameLength=data[6] << 21 | data[7] << 14 | data[8] << 7 | data[9]

2、Tag Frame帧数据。
Tag Frame 是一个以10byte为头,后面不定长的数据帧,结构如下:

部分相对起始长度内容
0 4 标记 Frame ID
5 4 内容长度
8 2 Flages
内容 10 不定长 内容

FrameID 为4个字符,具体参考ID3 tag version 2.3.0
内容长度为 4 byte的数据,和上面的不同,它的二进制是全8位的,但和 C# 存储int32不同,它前面的byte表示高位,具体计算如下:

byte[] data=new byte[10];
//....(读取10byte Frame头部数据放入 data)
int ContentLength=data[6] << 24 | data[7] << 16 | data[8] << 8 | data[9]

(1)FrameId 以“T”开头的,除了“TXXX”,都是字符串信息,具体结构为:

起始长度内容
0 1 Text encoding
1 Frame头计算出来的长度 内容

内容以 FF FE 开头的为 C# 里面的 Encoding.Unicode ,以 FE FF 开头的为 C# 里面的 Encoding.BigEndianUnicode;除上外是以 Ascii 编码的。

(2)FrameId为“APIC”的是放 jpeg 或 png 格式的图像数据,具体见4.15. Attached picture

四、mp3音乐数据头

读完 id3v2tag 的数据后,紧接着下来就是具体的音乐数据。但是并不一定紧挨着 FrameLength+10,不知道是这些mp3录制的时候的软件问题还是其他我还没发现的问题,用ultraEdit看,音乐数据和 id3tag 中间还有一部分全都是0的数据,所以读完 id3v2tag 后还要一个字节一个字节的寻找 FF开头的数据。我只是需要知道mp3的音乐信息,所以只要在id3v2tag 后找到 FF 开头的4个字节的数据就成了。这4个字节全部都是用位来表示信息,具体见 MPEG Audio Layer I/II/III frame header

写了一个程序读写,尚未完全完成,效果如下图:

附练手全程序:

<Window x:Class="Mp3信息.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Mp3信息"
        mc:Ignorable="d"
        Title="Mp3信息1.0  [乌龙哈里2017-03-13]" Height="650" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="32"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="6*"/>
            </Grid.ColumnDefinitions>
            <Button Name="btnFile" Content="Mp3" Margin="3" Grid.Column="0" Click="btnFile_Click"/>
            <TextBox Name="txtFile" Grid.Column="1" Margin="3"/>
        </Grid>
        
        <TabControl Grid.Row="1">
            
            <TabItem Header="单曲">
                <Grid >
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="200"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="200"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <DataGrid Name="dgShow" AutoGenerateColumns="False" SelectionUnit="CellOrRowHeader" Grid.Row="0" Grid.ColumnSpan="2">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="名称"  Binding="{Binding [0]}"/>
                            <DataGridTextColumn Header="ID3V2"  Binding="{Binding [1]}"/>
                            <DataGridTextColumn Header="ID3V1"  Binding="{Binding [2]}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                    <Image Name="imgCover" Grid.Row="1" Grid.Column="0"/>
                    <Grid Grid.Row="1" Grid.Column="1">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="1*"/>
                            <ColumnDefinition Width="1*"/>
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="32"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>
                        <Button Name="btnModify2" Content="修改 ID3V2" FontSize="18" Foreground="Navy" Grid.Row="1" Grid.Column="0" Margin="10" Click="btnModify2_Click"/>
                        <ComboBox Name="cmbGenre" Margin="10,5,10,2" Grid.Row="0" Grid.Column="1" SelectionChanged="cmbGenre_SelectionChanged"/>
                        <Button Name="btnModify1" Content="修改 ID3V1" FontSize="18" Grid.Row="1" Grid.Column="1" Margin="10" Click="btnModify1_Click"/>
                    </Grid>
                </Grid>
            </TabItem>
            <TabItem Header="批量">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Button Name="btnMuti" Content="批量读取" FontSize="16" Margin="5" Grid.Row="0"/>
                    <DataGrid Name="dgMuti" Grid.Row="1">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="文件"  Binding="{Binding [0]}"/>
                            <DataGridTextColumn Header="Mpeg"  Binding="{Binding [1]}"/>
                            <DataGridTextColumn Header="Layer"  Binding="{Binding [2]}"/>
                            <DataGridTextColumn Header="波特率"  Binding="{Binding [3]}"/>
                            <DataGridTextColumn Header="频率"  Binding="{Binding [4]}"/>
                            <DataGridTextColumn Header="声道"  Binding="{Binding [5]}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </Grid>
            </TabItem>
        </TabControl>
    </Grid>
</Window>
View Code
using System.Windows;
using System.Windows.Controls;

namespace Mp3信息
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        private string filteStr = "MP3 File|*.mp3|All File|*.*";
        private Mp3FileInfo mp3 = new Mp3FileInfo();

        public MainWindow()
        {
            InitializeComponent();
 
            dgShow.ItemsSource = mp3.Mp3Infos;
            cmbGenre.ItemsSource = mp3.Genres;
        }
        //---打开文件按钮
        private void btnFile_Click(object sender, RoutedEventArgs e)
        {
            string filename = GetOpenFilePath(filteStr);
            if (!string.IsNullOrEmpty(filename))
            {
                txtFile.Text = filename;
                dgShow.BeginEdit();
                mp3.GetMp3Info(filename);
                dgShow.EndInit();
                if (mp3.CoverExist)
                {
                    imgCover.Source = mp3.CoverPicture;
                }
                cmbGenre.SelectedIndex =int.Parse(mp3.Mp3Infos[5][2]);
            }
        }
       //---取得打开文件名
        private string GetOpenFilePath(string filterstr)
        {
            var dlg = new Microsoft.Win32.OpenFileDialog();
            dlg.Filter = filterstr;
            if (dlg.ShowDialog() == true)
            {
                return dlg.FileName;
            }
            return "";
        }
        //---取得保存文件名
        private string GetSaveFilePath(string filterstr)
        {
            var dlg = new Microsoft.Win32.SaveFileDialog();
            //dlg.FileName = Path.GetFileNameWithoutExtension(memoFilePath);
            //dlg.DefaultExt = ".memo";
            dlg.Filter = filterstr;
            if (dlg.ShowDialog() == true)
            {
                return dlg.FileName;
            }
            return "";
        }

        private void btnModify1_Click(object sender, RoutedEventArgs e)
        {
                mp3.SetV1TagInfo(txtFile.Text);
        }

        private void cmbGenre_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            dgShow.BeginInit();
            mp3.Mp3Infos[5][2] = cmbGenre.SelectedIndex.ToString();
            dgShow.EndInit();
        }

        private void btnModify2_Click(object sender, RoutedEventArgs e)
        {
            mp3.SetV2TagInfo(txtFile.Text);
        }
    }
}
View Code
/******************************************************
 * MP3 音乐格式文件信息读取
 * 作者:乌龙哈里
 * 最后修改:2017-03-07
 * 参考:
 * 1、http://id3.org/d3v2.3.0
 * 2、http://blog.csdn.net/alin0725/article/details/1876253
 * 3、http://www.mpgedit.org/mpgedit/mpeg%5Fformat/MP3Format.html
 * 4、https://en.wikipedia.org/wiki/MP3
 ******************************************************/
namespace Mp3信息
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Media.Imaging;


    /// <summary>
    /// 主程序class
    /// </summary>
    class Mp3FileInfo
    {
        public string Mp3File { get; set; }
        //注意:要先建立一个放置音乐类型的txt文件
        public string GenreFile { get; set; } = @".\\mp3genre.txt";

        private int V1TagLength = 128;
        private int V2TagHeaderLength = 10;
        private string V1TagSymbol = "TAG";
        private string V2TagSymbol = "ID3";
        private string V2TagPictureId = "APIC";
        private int[] V1TagStruct = new int[14];//id3v1数据结构
        public string[] Genres;//音乐类型

        private string[] Mpeg;
        private string[] Layer;
        private string[] Bitrate;
        private string[] Hz;
        private string[] Channel;

        public ObservableCollection<string[]> Mp3Infos = new ObservableCollection<string[]>();
        public BitmapSource CoverPicture;
        public bool CoverExist = false;
        public int  NewCoverType = -1;
        private string[] V2TagFrameIds;

        //---初始化
        public Mp3FileInfo()
        {
            FillV1TagStruct();
            FillGenres();
            FillHeader();
            FillV2TagFrameIds();
        }
        //---批量取得信息(以后再写)
        public void GetMutiInfo(string path)
        {

        }
        //---写id3v2信息(有问题,写成功后用windows的播放器播放正常,图片也出现,但歌名等信息不出现)
        public void SetV2TagInfo(string path)
        {

            List<(string frameid, string content)> list = new List<(string, string)>();
            int n = 0;
            for (int i = 0; i < V2TagFrameIds.Length; i++)
            {
                if (Mp3Infos[i][1].Length > 0)
                {
                    list.Add((V2TagFrameIds[i],Mp3Infos[i][1]));
                    n += Mp3Infos[i][1].Length*2 + 3 + 10;
                }
            }
            n += 10;
            byte[] _data = new byte[n];
            Encoding.Default.GetBytes(V2TagSymbol,0,3,_data,0);
            _data[3] = 3;
            int m = 10;
            foreach (var info in list)
            {
                Encoding.Default.GetBytes(info.frameid,0,4,_data,m);
                int k = info.content.Length*2 + 3;
                _data[m + 4] = (byte)(k >>24);
                _data[m + 5] = (byte)(k >>16);
                _data[m + 6] = (byte)(k >> 8);
                _data[m + 7] = (byte)k;
                _data[m + 10] = 1;
                _data[m + 11] = 255;
                _data[m + 12] = 254;
                Encoding.Unicode.GetBytes(info.content,0,info.content.Length,_data,m+13);
                m =m+ k + 10;
            }
            m = n;
            if (NewCoverType > -1)
            {

            }
            else
            {
                using (FileStream fs=File.OpenRead(path))
                {
                    int _framelength = GetV2TagFrameLength(fs);
                    byte[] _framedata = new byte[_framelength];
                    fs.Read(_framedata, 0, _framedata.Length);
                    var _frames = GetV2TagFrameInfo(_framedata);
                    int _index = -1;
                    for (int i = 0; i < _frames.Count; i++)
                    {
                        if (string.Compare(_frames[i].frameid, V2TagPictureId) == 0)
                        {
                            n += _frames[i].length;
                            _index = i;
                            break;
                        }
                    }
                    n = n+ (int)fs.Length - _framelength - 10;
                    Array.Resize(ref _data, n);
                    int k = m;
                    if (_index > -1)
                    {
                        k = m + _frames[_index].length;
                        for(int i=0; i < _frames[_index].length; i++)
                        {
                           _data[m++]=_framedata[_frames[_index].start + i];
                        }   
                    }
                    _data[6] = (byte)((k >> 21)&0x7F);
                    _data[7] = (byte)((k >> 14) & 0x7F);
                    _data[8] = (byte)((k >> 7) & 0x7F);
                    _data[9] = (byte)(k & 0x7F);

                    fs.Read(_data, m, (int)fs.Length - _framelength - 10);

                }
            }
            using (FileStream fs = File.Create(@"d:\\test\\123.mp3"))
            {
                fs.Write(_data, 0, _data.Length);
            }

        }
        //---写id3v1信息
        public void SetV1TagInfo(string path)
        {
            byte[] info = new byte[V1TagLength];
            info[0] = (byte)V1TagSymbol[0];
            info[1] = (byte)V1TagSymbol[1];
            info[2] = (byte)V1TagSymbol[2];
            for (int i = 0; i < 5; i++)
            {
                string s = Mp3Infos[i][2].Trim();
                Encoding.Default.GetBytes(s, 0, s.Length, info, V1TagStruct[(i + 1) * 2]);
            }
            info[V1TagStruct[12]] = byte.Parse(Mp3Infos[5][2]);
            using (FileStream fs = File.OpenWrite(path))
            {
                fs.Seek(-info.Length, SeekOrigin.End);
                fs.Write(info, 0, info.Length);
            }
        }
        //---读取mp3信息
        public void GetMp3Info(string path)
        {
            Mp3Infos.Clear();
            FillMp3Infos();
            using (FileStream fs = File.OpenRead(path))
            {
                GetV1TagInfo(fs);
                int _v2TagLength = GetV2TagFrameLength(fs);
                if (_v2TagLength > 0)
                {
                    GetV2TagInfo(fs, _v2TagLength);
                    int[] _index = new int[5];
                    int n = V2TagFrameIds.Length;
                    GetMp3HeaderInfo(_index, fs, _v2TagLength);
                    Mp3Infos[n][1] = Mpeg[_index[0]];
                    Mp3Infos[n+1][1] = Layer[_index[1]];
                    Mp3Infos[n+2][1] = Bitrate[_index[2]];
                    Mp3Infos[n+3][1] = Hz[_index[3]];
                    Mp3Infos[n+4][1] = Channel[_index[4]];
                }
            }
        }
        //---读取mp3 header 信息
        private void GetMp3HeaderInfo(int[] Index, FileStream fs, int Id3v2TagFrameLength)
 

以上是关于MP3 信息读取的主要内容,如果未能解决你的问题,请参考以下文章

是否有用于从 mp3 读取 ID3 信息的非 GPL Python 库? [关闭]

iOS,AVPlayer - 循环播放 MP3 片段

我无法从 android 的片段中读取活动中的数据库信息

Python实现mp3 ID3v2.3信息提取

为啥将拆分为 wav 文件的旋律转换为拆分的 mp3 会在片段边界处产生不好的声音?

调试NAudio MP3读取差异?