MP3文件大体分为三部分:
TAG_V2(ID3V2),
音频数据,
TAG_V1(ID3V1),
其中ID3V2是ID3V1的补充,并不是所有的MP3都有ID3V2补充,即是不是所有的MP3文件都有ID3V2。
如果MP3文件存在ID3V2,则一定在文件的头部,ID3V2结构分为头部(header)和若干标签帧,其中头部长度为10字节,10个字节的结构如表1:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
内容为”ID3” | 版本号 | 副版本号 | 存放标志的字节 | ID3V2总大小(帧头和之后的若干标签帧总和) |
读取MP3文件ID3V2信息的函数可如下:
//定义头部和标签帧
typedefstruct ID3v2Header{
char Identify[3]; // ID3v2固定标志:ID3
char Ver; //主版本号,ID3v2就是3
char Rever; //副版本号,一般都为0
char Flag; //标志位,一般为0,字义为abc00000
char Size[4]; //标签大小,一共四个字节,但每个字节只使用7位,最高位不使用恒为0,所以格式: 0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx
}ID3v2Header;
shell.albert@yantai:~/temporary/build-ZAudioTest-Desktop_Qt_5_3_GCC_64bit-Debug> hexdump -C test.mp3 | head -n 2
00000000 49 44 33 03 00 00 00 00 1f 76 54 50 45 31 00 00 |ID3......vTPE1..|
00000010 00 1d 00 00 01 ff fe 53 00 65 00 63 00 72 00 65 |.......S.e.c.r.e|
typedefstruct ID3v2Frame//标签帧,10个字节
{
char FrameID[4]; //标志对照符,如TEXT,TOPE,TDAT....
char Size[4]; //帧体的大小,按照正常的8位存储的,FSize = Size[0]*0x100000000 + Size[1]*0x10000 + Size[2]*0x100 + Size[3];
char Flag[2]; //存放标志
}ID3v2Frame;
//输出信息并返回ID3V2大小
int ReadID3v2(FILE *pf)
{
ID3v2Headermp3header;
ID3v2Frame mp3Frame;
int FSize = 0;
char str[4096] = {0};
char str2[5] = {0};
int ID3size;
inthead_size = 0;
inti;
if(!pf)
return -1;
fseek(pf,0,SEEK_SET);
读取mp3的头部分信息
fread(&mp3header,sizeof(mp3header),1,pf);
if (mp3header.Identify[0]!='I' || mp3header.Identify[1]!='D' || mp3header.Identify[2]!='3' ){
printf("此歌曲不支持ID3v2标准!\n");
//文件复位
rewind(pf);
return -2;
}
printf("ID3v2标志:%.3s\n",mp3header.Identify);
printf("ID3v2版本:%d\n", mp3header.Ver);
ID3size= (mp3header.Size[0]& 0x7F)<< 21|(mp3header.Size[1]& 0x7F)<< 14|(mp3header.Size[2] & 0x7F) << 7|(mp3header.Size[3] & 0x7F);
printf("标签大小:%d\n***********\n",ID3size);
for (i=0;i<ID3size;i=i+11+FSize){
memset(&mp3Frame,0,sizeof(mp3Frame));
memset(&str,0,sizeof(str));
fseek(pf,10+i,SEEK_SET); //移动到标签帧头
fread(&mp3Frame,sizeof(mp3Frame),1,pf);
//原则上是不用-1的,但是实际发现,总有一个字节的差距,为了计算方便-1,所以出现-1时标明此区块无内容
FSize = (int)(mp3Frame.Size[0]*0x100000000 + mp3Frame.Size[1]*0x10000+ mp3Frame.Size[2]*0x100 + mp3Frame.Size[3]-1);
if (FSize>0) {
fseek(pf,10+11+i,SEEK_SET);//移动到内容区
fread(str,FSize,1,pf);
GetStr(mp3Frame.FrameID,str2);
printf("%s-%s:\t%s\n",str2,mp3Frame.FrameID,str);
head_size+=11;
}else{
return ID3size+10;
}
}
return ID3size+10;
}
//通过FrameID获取对应的中文名
void GetStr(char* oldstr,char* str)
{
if (0==memcmp((LPCTSTR)"TIT2",oldstr,4))
{
memcpy(str,"标题",4);
}elseif(0==memcmp((LPCTSTR)"TPE1",oldstr,4)){
memcpy(str,"作者",4);
}elseif(0==memcmp((LPCTSTR)"TALB",oldstr,4)){
memcpy(str,"专辑",4);
}elseif(0==memcmp((LPCTSTR)"TRCK",oldstr,4)){
memcpy(str,"音轨",4);
}elseif(0==memcmp((LPCTSTR)"TYER",oldstr,4)){
memcpy(str,"年代",4);
}elseif(0==memcmp((LPCTSTR)"COMM",oldstr,4)){
memcpy(str,"备注",4);
}elseif(0==memcmp((LPCTSTR)"TCON",oldstr,4)){
memcpy(str,"类型",4);
}else{
memcpy(str,"未知",4); //其他的不是很重要,所以省略了
}
}