根据RFC文档上的说法,只要知道文件的长度,播放的比特率(bitRate),采样率(Simpling Frequency Rate)以及 填充位数(Padding bits),以及“恒定每26ms能播放一数据帧” 的约定,就可以计算出播放时长
okay,要做的就是,了解mp3帧格式,获取比特率,采样率 以及 填充位数
在mp3文件的末尾,恒存在一个长度为128字节的ID3 Tag Version 1的标签,用以描述文件。如果愿意,还可以添加一个ID3 Tag Version 2到文件头,长度不固定,不过在其头部,会有10字节的描述头,里头标识出这个TAG结构的总长(含10字节),然后,接下来夹着的一直到TAG V1的部分,就是全部的数据帧。
假定这些帧是采取CBR格式,即固定帧长存储,则每一帧的帧头描述都是一样的,基于此,便可计算出所需要的数据
做一个类,来干这件事情:
using System;
using System.IO;
using System.Text;
namespace IndieCommon
{
public abstract class Mp3Tag
{
// 每秒固定可以播放的帧数
const double playFramesPerSec = 38.461538461538461538461538461538;
/**//// <summary>
/// 获取MP3帧头包含的信息
/// </summary>
/// <param name="FrameHeader">帧头四字节数据</param>
/// <returns>帧信息结构</returns>
static Mp3Frame getFrameInfo(byte[] FrameHeader)#region static Mp3Frame getFrameInfo(byte[] FrameHeader)
internal static Mp3Frame getFrameInfo(byte[] FrameHeader)
{
// 比特率检索字典
int[,] bitrateArray = new int[,] { { 0,0,0,0,0,0},
{32,32,32,32,32,8},
{64,48,40,64,48,16},
{64,48,40,64,48,16},
{96,56,48,96,56,24},
{128,64,56,128,64,32},
{160,80,64,160,80,64},
{192,96,80,192,96,80},
{224,112,96,224,112,52},
{256,128,112,256,128,64},
{288,160,128,288,160,128},
{320,192,160,320,192,160},
{352,320,192,352,320,112},
{384,256,224,384,256,128},
{448,384,320,448,384,320},
{0,0,0,0,0,0} };
// 采样率检索字典
int[,] simpArray = new int[,] { {44100,22050,11025},
{48000,24000,12000},
{32000,16000,8000},
{0,0,0} };
// 取帧信息
Mp3Frame frame = new Mp3Frame();
// MPEG版本
switch ((FrameHeader[1] & 0x18) >> 3)
{
case 3: // MPEG version 1
frame.version = 1;
break;
case 2: // MPEG version 2
frame.version = 2;
break;
case 0: // MPEG version 2.5
frame.version = 3;
break;
case 1: // Reserve
frame.version = 0;
break;
}
// 层级
switch ((FrameHeader[1] & 0x6) >> 1)
{
case 1: // Layer 3
frame.layer = 3;
break;
case 2: // Layer 2
frame.layer = 2;
break;
case 3: // Layer 1
frame.layer = 1;
break;
case 0: // reserve
frame.layer = 0;
break;
}
// 是否有CRC保护
frame.protect = FrameHeader[1] & 0x1;
// 比特率索引
int j = ((FrameHeader[2] & 0xf0) >> 4)+1;
int i = 0;
switch (frame.version)
{
// 通过版本 + 层级 获取 BIT率
case 1:
switch (frame.layer)
{
case 1:
i = 0;
break;
case 2:
i = 1;
break;
case 3:
i = 2;
break;
}
break;
case 2:
case 3:
switch (frame.layer)
{
case 1:
i = 3;
break;
case 2:
i = 4;
break;
case 3:
i = 5;
break;
}
break;
}
frame.bitrate = bitrateArray[j,i];
// 获取采样率
j = ((FrameHeader[2] & 0xc) >> 2);
switch (frame.version)
{
case 1:
i = 0;
break;
case 2:
i = 1;
break;
case 3:
i = 2;
break;
}
frame.simplingRate = simpArray[j, i];
// 填充位
frame.paddingBits = ((FrameHeader[2] & 0x2) >> 1);
// 声道,1为立体声,0为单声道
frame.channel = (((FrameHeader[3] & 0xc0) >> 6) < 3 ? 1: 0 );
return frame;
}
#endregion
/**//// <summary>
/// 由帧头获取Mp3时长信息
/// </summary>
/// <param name="paramMp3">Mp3结构</param>
/// <param name="FrameHeader">帧头</param>
/// <param name="ID3V2_frame_size">ID3 Tag2长度</param>
static void getTimeInfo(ref MP3 paramMP3,byte[] FrameHeader,int ID3V2_frame_size)#region static void getTimeInfo(ref MP3 paramMP3,byte[] FrameHeader,int ID3V2_frame_size)
internal static void getTimeInfo(ref MP3 paramMP3, byte[] FrameHeader, int ID3V2_frame_size)
{
// 文件长度
FileInfo fi = new FileInfo(paramMP3.fileComplete);
paramMP3.FileLength = fi.Length;
// 获取帧信息
Mp3Frame frameInfo = getFrameInfo(FrameHeader);
// 获取帧长(仅对CBR起作用)
int frameSize = frameInfo.CalcFrameSize();
// 帧数
// 帧总长度 = 文件长度 - ID3 TagV2 - ID3 TagV1(固长128)
long frameCount = (paramMP3.FileLength - ID3V2_frame_size - 128) / frameSize;
// 时长
double secs = (double)frameCount / playFramesPerSec;
paramMP3.hours = (int)secs / 3600;
paramMP3.minutes = ((int)secs % 3600) / 60;
paramMP3.seconds = ((int)secs % 3600) % 60;
}
#endregion
/**//// <summary>
/// 读取MP3信息
/// </summary>
/// <param name="paramMP3">Mp3描述结构</param>
static void readMP3Tag(ref MP3 paramMP3)#region static void readMP3Tag(ref MP3 paramMP3)
public static void readMP3Tag(ref MP3 paramMP3)
{
// 文件对象
FileStream oFileStream;
oFileStream = new FileStream(paramMP3.fileComplete, FileMode.Open);
// 是否有ID3 Tag V2
byte[] header = new byte[10];
oFileStream.Seek(0, SeekOrigin.Begin);
oFileStream.Read(header, 0, 10);
// 标识头长度的字串
Encoding instEncoding = new ASCIIEncoding();
string id3Tag = instEncoding.GetString(header);
// 是否有ID3 Tag V2
if (id3Tag.Substring(0, 3) == "ID3")
{
// ID3Tag V2帧长度
int ID3V2_frame_size = (int)(header[6] & 0x7F) * 0x200000
| (int)(header[7] & 0x7F) * 0x400
| (int)(header[8] & 0x7F) * 0x80
| (int)(header[9] & 0x7F);
// 定位到某一帧头
byte[] FrameHeader = new byte[4];
oFileStream.Seek(ID3V2_frame_size + 10, SeekOrigin.Begin);
oFileStream.Read(FrameHeader, 0, 4);
oFileStream.Close();
// 获取时长
getTimeInfo(ref paramMP3, FrameHeader, ID3V2_frame_size);
}
// 直读Frame
// 读取ID3 Tag V1
else
{
// 直接获取帧头
byte[] FrameHeader = new byte[4];
oFileStream.Seek(0, SeekOrigin.Begin);
oFileStream.Read(FrameHeader, 0, 4);
oFileStream.Close();
// 获取时长
getTimeInfo(ref paramMP3, FrameHeader, 0);
}
// Read the 128 byte ID3 tag into a byte array
//byte[] bBuffer = new byte[128];
//oFileStream.Seek(-128, SeekOrigin.End);
//oFileStream.Read(bBuffer, 0, 128);
//oFileStream.Close();
/**///// Convert the Byte Array to a String
//id3Tag = instEncoding.GetString(bBuffer);
/**///// If there is an attched ID3 v1.x TAG then read it
//if (id3Tag.Substring(0, 3) == "TAG")
//{
// paramMP3.id3Title = id3Tag.Substring(3, 30).Trim();
// paramMP3.id3Artist = id3Tag.Substring(33, 30).Trim();
// paramMP3.id3Album = id3Tag.Substring(63, 30).Trim();
// paramMP3.id3Year = id3Tag.Substring(93, 4).Trim();
// paramMP3.id3Comment = id3Tag.Substring(97, 28).Trim();
// // Get the track number if TAG conforms to ID3 v1.1
// if (id3Tag[125] == 0)
// paramMP3.id3TrackNumber = bBuffer[126];
// else
// paramMP3.id3TrackNumber = 0;
// paramMP3.id3Genre = bBuffer[127];
// paramMP3.hasID3Tag = true;
// // ********* IF USED IN ANGER: ENSURE to test for non-numeric year
//}
//else
//{
// // ID3 Tag not found so create an empty TAG in case the user saces later
// paramMP3.id3Title = "";
// paramMP3.id3Artist = "";
// paramMP3.id3Album = "";
// paramMP3.id3Year = "";
// paramMP3.id3Comment = "";
// paramMP3.id3TrackNumber = 0;
// paramMP3.id3Genre = 0;
// paramMP3.hasID3Tag = false;
//}
}
#endregion
/**//// <summary>
/// 更新tag信息
/// </summary>
/// <param name="paramMP3"></param>
static void updateMP3Tag(ref MP3 paramMP3)#region static void updateMP3Tag(ref MP3 paramMP3)
public static void updateMP3Tag(ref MP3 paramMP3)
{
// Trim any whitespace
paramMP3.id3Title = paramMP3.id3Title.Trim();
paramMP3.id3Artist = paramMP3.id3Artist.Trim();
paramMP3.id3Album = paramMP3.id3Album.Trim();
paramMP3.id3Year = paramMP3.id3Year.Trim();
paramMP3.id3Comment = paramMP3.id3Comment.Trim();
// Ensure all properties are correct size
if (paramMP3.id3Title.Length > 30)
paramMP3.id3Title = paramMP3.id3Title.Substring(0, 30);
if (paramMP3.id3Artist.Length > 30)
paramMP3.id3Artist = paramMP3.id3Artist.Substring(0, 30);
if (paramMP3.id3Album.Length > 30)
paramMP3.id3Album = paramMP3.id3Album.Substring(0, 30);
if (paramMP3.id3Year.Length > 4)
paramMP3.id3Year = paramMP3.id3Year.Substring(0, 4);
if (paramMP3.id3Comment.Length > 28)
paramMP3.id3Comment = paramMP3.id3Comment.Substring(0, 28);
// Build a new ID3 Tag (128 Bytes)
byte[] tagByteArray = new byte[128];
for (int i = 0; i < tagByteArray.Length; i++)
tagByteArray[i] = 0; // Initialise array to nulls
// Convert the Byte Array to a String
Encoding instEncoding = new ASCIIEncoding(); // NB: Encoding is an Abstract class // ************ To DO: Make a shared instance of ASCIIEncoding so we don't keep creating/destroying it
// Copy "TAG" to Array
byte[] workingByteArray = instEncoding.GetBytes("TAG");
Array.Copy(workingByteArray, 0, tagByteArray, 0, workingByteArray.Length);
// Copy Title to Array
workingByteArray = instEncoding.GetBytes(paramMP3.id3Title);
Array.Copy(workingByteArray, 0, tagByteArray, 3, workingByteArray.Length);
// Copy Artist to Array
workingByteArray = instEncoding.GetBytes(paramMP3.id3Artist);
Array.Copy(workingByteArray, 0, tagByteArray, 33, workingByteArray.Length);
// Copy Album to Array
workingByteArray = instEncoding.GetBytes(paramMP3.id3Album);
Array.Copy(workingByteArray, 0, tagByteArray, 63, workingByteArray.Length);
// Copy Year to Array
workingByteArray = instEncoding.GetBytes(paramMP3.id3Year);
Array.Copy(workingByteArray, 0, tagByteArray, 93, workingByteArray.Length);
// Copy Comment to Array
workingByteArray = instEncoding.GetBytes(paramMP3.id3Comment);
Array.Copy(workingByteArray, 0, tagByteArray, 97, workingByteArray.Length);
// Copy Track and Genre to Array
tagByteArray[126] = paramMP3.id3TrackNumber;
tagByteArray[127] = paramMP3.id3Genre;
// SAVE TO DISK: Replace the final 128 Bytes with our new ID3 tag
FileStream oFileStream = new FileStream(paramMP3.fileComplete, FileMode.Open);
if (paramMP3.hasID3Tag)
oFileStream.Seek(-128, SeekOrigin.End);
else
oFileStream.Seek(0, SeekOrigin.End);
oFileStream.Write(tagByteArray, 0, 128);
oFileStream.Close();
paramMP3.hasID3Tag = true;
}
#endregion
}
/**//// <summary>
/// Mp3结构
/// </summary>
struct MP3#region struct MP3
public struct MP3
{
public string filePath;
public string fileFileName;
public string fileComplete;
public bool hasID3Tag;
public string id3Title;
public string id3Artist;
public string id3Album;
public string id3Year;
public string id3Comment;
public byte id3TrackNumber;
public byte id3Genre;
public long FileLength;
public int hours;
public int minutes;
public int seconds;
// Required struct constructor
public MP3(string path, string name)
{
this.filePath = path;
this.fileFileName = name;
this.fileComplete = path + "\\" + name;
this.hasID3Tag = false;
this.id3Title = null;
this.id3Artist = null;
this.id3Album = null;
this.id3Year = null;
this.id3Comment = null;
this.id3TrackNumber = 0;
this.FileLength = 0;
this.id3Genre = 0;
this.hours = 0;
this.minutes = 0;
this.seconds = 0;
}
}
#endregion
/**//// <summary>
/// MP3文件帧结构
/// </summary>
struct Mp3Frame#region struct Mp3Frame
internal struct Mp3Frame
{
public int version; // MPEG版本(2+,2,1,0表示保留)
public int layer; // 层级(1,2,3,0表示保留)
public int protect; // 是否受CRC校验保护(1为保护,0为未保护)
public int frameSize; // 帧长度
public int bitrate; // 速率,bps
public int simplingRate; // 采样率
public int paddingBits; // 填充位数
public int channel; // 声道模式(1为立体声,0为单声道)
/**//// <summary>
/// 计算帧长度
/// </summary>
public int CalcFrameSize()
{
// 计算帧长度的公式
this.frameSize = ((this.version == 1 ? 144 : 72) * 1000 * this.bitrate
/ this.simplingRate) + this.paddingBits;
return this.frameSize;
}
}
#endregion
}
很显然,这事儿还没干完,先留着,以后再干~~~