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>
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); } } }
/****************************************************** * 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 库? [关闭]