// ID3Paser.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <malloc.h>
#include <string.h>
typedef struct _stID3V2Head
{
char head[3]; // 标签头 ID3
char mainVer; // 主版本号 ID3V2.3 就是 3
char subVer; // 副版本号
char flag; // 标签为,这个版本只定义了3位
char size[4]; // 标签大小,包括标签头的这10个字节
}ID3V2Head;
typedef struct _stID3V2Frame
{
char frameID[4]; // 帧ID,用4个字符标识
char size[4]; // 帧大小
char flags[2]; // 标志位
}ID3V2Frame;
#define MAX_ID3TAG_LENGTH (3*1024*1024)
/*
其实如果简单点也可以只解析下面这几种帧,不必解析完整的大帧表(下面的 szTable[][5])
//标题
#define FRAME_TITLE_ID "TIT2"
//作者
#define FRAME_AUTHOR_ID "TPE1"
//专辑
#define FRAME_SPECIAL_ID "TALB"
//音轨 格式
#define FRAME_FORMAT_ID "TRCK"
//年代
#define FRAME_YEAR_ID "TYER"
//类型
#define FRAME_TYPE_ID "TCON"
//备注
#define FRAME_COMM_ID "COMM"
//封面
#define FRAME_APIC_ID "APIC"
*/
static char szTable[][5] = {
"AENC", //Audio encryption
"APIC", //Attached picture
"COMM", //Comments
"COMR", //Commercial frame
"ENCR", //Encryption method registration
"EQUA", //Equalization
"ETCO", //Event timing codes
"GEOB", //General encapsulated object
"GRID", //Group identification registration
"IPLS", //Involved people list
"LINK", //Linked information
"MCDI", //Music CD identifier
"MLLT", //MPEG location lookup table
"OWNE", //Ownership frame
"PRIV", //Private frame
"PCNT", //Play counter
"POPM", //Popularimeter
"POSS", //Position synchronisation frame
"RBUF", //Recommended buffer size
"RVAD", //Relative volume adjustment
"RVRB", //Reverb
"SYLT", //Synchronized lyric/text
"SYTC", //Synchronized tempo codes
"TALB", //Album/Movie/Show title
"TBPM", //BPM (beats per minute)
"TCOM", //Composer
"TCON", //Content type
"TCOP", //Copyright message
"TDAT", //Date
"TDLY", //Playlist delay
"TENC", //Encoded by
"TEXT", //Lyricist/Text writer
"TFLT", //File type
"TIME", //Time
"TIT1", //Content group description
"TIT2", //Title/songname/content description
"TIT3", //Subtitle/Description refinement
"TKEY", //Initial key
"TLAN", //Language(s)
"TLEN", //Length
"TMED", //Media type
"TOAL", //Original album/movie/show title
"TOFN", //Original filename
"TOLY", //Original lyricist(s)/text writer(s)
"TOPE", //Original artist(s)/performer(s)
"TORY", //Original release year
"TOWN", //File owner/licensee
"TPE1", //Lead performer(s)/Soloist(s)
"TPE2", //Band/orchestra/accompaniment
"TPE3", //Conductor/performer refinement
"TPE4", //Interpreted, remixed, or otherwise modified by
"TPOS", //Part of a set
"TPUB", //Publisher
"TRCK", //Track number/Position in set
"TRDA", //Recording dates
"TRSN", //Internet radio station name
"TRSO", //Internet radio station owner
"TSIZ", //Size
"TSRC", //ISRC (international standard recording code)
"TSSE", //Software/Hardware and settings used for encoding
"TYER", //Year
"TXXX", //User defined text information frame
"UFID", //Unique file identifier
"USER", //Terms of use
"USLT", //Unsychronized lyric/text transcription
"WCOM", //Commercial information
"WCOP", //Copyright/Legal information
"WOAF", //Official audio file webpage
"WOAR", //Official artist/performer webpage
"WOAS", //Official audio source webpage
"WORS", //Official internet radio station homepage
"WPAY", //Payment
"WPUB", //Publishers official webpage
"WXXX" //User defined URL link frame
};
typedef struct _stID3V2Paser
{
FILE *fe;
int size;
}ID3V2Paser;
int getID3V2FrameSize(unsigned char szBuf[4])
{
int Len = (szBuf[0]&0x7f) << 21 | (szBuf[1]&0x7f) << 14
| (szBuf[2]&0x7f) << 7 | (szBuf[3]&0x7f);
return Len;
}
// 创建ID3V2解析器,如果创建成功返回文件句柄
ID3V2Paser* createID3V2Paser(const char *szFile)
{
ID3V2Paser *paser = NULL;
bool ret = true;
do
{
if (szFile == NULL)
{
ret = false;
break;
}
paser = (ID3V2Paser*)malloc(sizeof(ID3V2Paser));
if (paser == NULL)
{
// 内存分配失败
ret = false;
break;
}
//memset(&paser, 0, sizeof(ID3V2Paser));
paser->fe = fopen(szFile, "rb");
if (paser->fe == NULL)
{
// 文件打开失败
ret = false;
break;
}
ID3V2Head head = {0};
int size = fread(&head, 1, sizeof(ID3V2Head), paser->fe);
if (size != sizeof(ID3V2Head))
{
// 没有读取到文字的 ID3V2 头
ret = false;
break;
}
if (0 != strncmp(head.head, "ID3",3))
{
// 关键字不满足要求
ret = false;
break;
}
paser->size = getID3V2FrameSize((unsigned char *)head.size);
// 这个判断条件是自己加的,规范中没有说明
if(paser->size > MAX_ID3TAG_LENGTH)
{
// 呵呵,我觉得这个ID3信息有点过大了。
ret = false;
break;
}
} while (0);
if (!ret)
{
if (paser != NULL && paser->fe !=NULL)
{
fclose(paser->fe);
free(paser);
}else if (paser != NULL)
{
free(paser);
}else
{
// do nothing
}
}
return paser;
}
bool isValiedFrameID(char *szFrameID)
{
bool ret = false;
if (szFrameID != NULL)
{
int i = 0;
int size = sizeof(szTable) / sizeof(szTable[0]);
for (i = 0; i < size; i++)
{
// 只需要比较4个字节就可以了
if (0 == strncmp(szFrameID, szTable[i], 4))
{
ret = true;
}
}
}
return ret;
}
void printHexBuf(unsigned char *pBuf, int iLen)
{
if (pBuf)
{
int printLen = iLen > 128 ? 128 : iLen;
int newline = 0;
for (int i = 0; i < printLen; i++)
{
printf("0x%02X ", pBuf[i]);
newline++;
if (newline == 8)
{
newline = 0;
printf("\n"); // 16个换一行
}
}
}
}
int parseID3V2Frame(ID3V2Paser *paser)
{
int count = 0;
if(paser)
{
int readsize = paser->size - sizeof(ID3V2Head); //之前已经读取了帧头
fseek(paser->fe, sizeof(ID3V2Head), SEEK_SET); //怕该接口被多次调用,所以每次固定走到指定位置
unsigned char *pBuf = NULL;
char szPrintBuf[255] = {0}; //打印用的辅助变量
do
{
pBuf = (unsigned char*)malloc(readsize);
if (pBuf == NULL)
{
break;
}
memset(pBuf, 0, readsize);
int len = (int)fread(pBuf, 1, readsize, paser->fe);
int offset = 0;
int frame_len = 0; //每一个具体帧的大小
ID3V2Frame *pFrame = (ID3V2Frame*)pBuf;
// 开始循环解析每一帧
while (offset < readsize)
{
pFrame = (ID3V2Frame*)((char*)pBuf + offset); // 这里要注意,先按照 char* 进行跳转,跳转到合适的位置后,格式化成ID3V2Frame指针
// 判断是不是合法帧
if (isValiedFrameID(pFrame->frameID))
{
count++;
//跳转帧大小,到下一帧
frame_len = pFrame->size[0]*0x100000000 \
+ pFrame->size[1]*0x10000 \
+ pFrame->size[2]*0x100 \
+ pFrame->size[3];
//准备打印帧数据,这里把 APIC 图片数据只打印4个字节
printf("[%s] \n", pFrame->frameID); //统一打印帧头信息(这里偷懒了,枕头其实没有'\0'的,嘿嘿!实际使用别学我)
if (0 == strncmp(pFrame->frameID, "APIC",4))
{
/*
他的实际数据跳转格式:
1. 第一个字节标识后面信息的编码格式。
2. 按照标识的编码格式解析接下来的字符串。
3. 紧接着下一个字节标识后面信息的编码格式。
4. 按照标识的编码格式解析下面的字符串。
5. 两个字符串解析完成后,就是原始的图片数据信息。
*/
}
else
{
/*
1. 第一个字节标识文本信息的编码格式。
2. 根据编码格式解析字符信息。
*/
char *print = (char*)pFrame;
print += 11; //帧头的10 个字节,和一个字节的信息编码
memcpy(szPrintBuf, print, frame_len -1);
szPrintBuf[frame_len] = '\0';
printf("%s\n", szPrintBuf);
}
offset += frame_len + 10; //每次偏移的时候需要跳过帧头和有效负载
}
else
{
// 虽然数据还没有解析完毕但是这里的帧标识不合法,不和你玩了
break;
}
}
} while (0);
if (pBuf)
{
free(pBuf);
}
}
return count;
}
void destoryID3V2Paser(ID3V2Paser *paser)
{
if (paser)
{
/*
我认为,只要外面能有paser句柄,文件一定是打开成功的。
如果怕调用者乱操作,可以把 ID3V2Paser 封装一下,不暴露细节
*/
fclose(paser->fe);
free(paser);
}
paser = NULL;
}
int _tmain(int argc, _TCHAR* argv[])
{
/*
int i = 0;
for (i = 0; i < sizeof(szTable) / sizeof(szTable[0]); i++)
{
printf("\n%s",szTable[i]);
}
*/
ID3V2Paser *paser = createID3V2Paser("D:\\TTPmusic\\彭佳慧 - 残酷的温柔.mp3");
if (paser)
{
parseID3V2Frame(paser);
destoryID3V2Paser(paser);
}
return 0;
}