ID3V2 解析分析

// 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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值