QT 讀取mp3ID3V2 獲取mp3專輯圖片、專輯名稱、標題、作者(二)
這篇承接上篇,主要記錄的是程式碼,關於mp3ID3V2的簡要介紹可以跳轉到上一篇:
前提說明:沒有使用任何的外部庫,純程式碼實現的,根據網上主流的c語言程式碼提取mp3id3v2標籤資訊,結合QT修改的
一、標頭檔案
1.1、申明兩個結構體,存放相關資訊
#ifndef MP3TAGLIB_H #define MP3TAGLIB_H #include <QMainWindow> #include "stdlib.h" #include "stdio.h" #include <QDebug> #include <QImage> #include <QMap> #include <QTextCodec> #include <QString> #include <QFile> #include <QUrl> using namespace std; class MP3Header; typedef struct ID3V2FrameH { char frameID[4]; //存放標籤格式,是否為id3v2 unsigned char size[4]; //存放標籤資料的大小 char flags[2]; }ID3V2FH; typedef struct MP3INFO { QString Url; //存放這首歌的詳細地址 QString Name; //歌名 TIT2 QString Album; //專輯 TALB QString Singer; //歌手 TPE1 QString Picture_url; //歌曲圖片存放路徑 QString Picture_type; //圖片型別 jpg,png int number; //歌曲編號 int beginNum; //圖片起始位置 int lenth; //圖片資料長度 bool pic_flag; //是否有圖片 }MP3INFO; typedef struct frameIDStruct { int beginNum; int endNum; QString FrameId; }frameIDStruct;
1.2、MP3Header類
class MP3Header { public: MP3Header(); FILE *fp; QString m_url; unsigned char Header[3]; unsigned char FrameId[4]; //存放幀標識 unsigned char Header_size[4]; unsigned int mp3_TagSize; unsigned char frameSize[4]; //存放該幀內容的大小 unsigned int framecount; //計算出幀內容的大小 void GetMp3IDV2(const wchar_t *url); MP3INFO GetAllInfo(const wchar_t *url, int songNumber); void GetPicture(MP3INFO *mp3info); void GetFrameId(); QString GetInfo(QString fId); QMap<QString,frameIDStruct> m_IDmap; }; #endif // MP3TAGLIB_H
二、原始檔
這裡只展示GetAllInfo函式的實現,所有的功能都在這個函式內實現了,其他函式是我做音樂播放器用到的,這裡不重要
2.1、開啟mp3檔案,得到標籤的型別
MP3INFO MP3Header::GetAllInfo(const wchar_t *url,int songNumber) { m_url = QString::fromWCharArray(url); fp = _wfopen(url,L"rb"); if (NULL==fp) { printf("open read file error!!"); MP3INFO falseInfo; falseInfo.pic_flag = false; return falseInfo; } fseek(fp,0,SEEK_SET); fread(&Header,1,3,fp); if(Header[0]=='I'&&Header[1]=='D'&&Header[2]=='3') { qDebug()<<"open ID3 correct!"; }
說明:
由於歌曲名字有中文也有英文,因此使用wchar_t型別儲存,wchar_t寬字元型別一般為16位或32位,在申明一個wchar_t型別的常量時可以使用一下語句:
QString filePath = "C:/Users/Admin/Desktop/song.mp3"
const wchar_t * url = reinterpret_cast<const wchar_t *>(filePath.utf16());
也可以:
const wchar_t *url = L"C:/Users/Admin/Desktop/song.mp3";
fp = _wfopen(url,L"rb"); 為c語言的檔案讀取函式,因為url是寬位元組,所以要使用_wfopen而不使用fopen,關於這兩個函式的有關內容,可以參考博文:
if (NULL==fp)
{
printf("open read file error!!");
MP3INFO falseInfo;
falseInfo.pic_flag = false;
return falseInfo;
}
這一小部分的意思是,在函式呼叫時,可以根據返回的MP3INFO結構體的pic_flag引數判斷mp3檔案是否被開啟成
其中fint fseek(FILE * stream, long offset, int whence);用於移動檔案流讀取的位置
引數1、FILE * stream為檔案的指標;
引數2、long offset為以起始位置為基準向前移動的位元組數,這裡設定為0,為從第0個位元組開始讀;
引數3、SEEK_SET表示設定起始位置為檔案開頭
SEEK_COR表示檔案當前位置
SEEK_END表示檔案末尾
其中size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );用於讀取二進位制資料
引數1、讀取的資料在記憶體中存放的位置,這裡是結構體的指標
引數2、每次要讀取的位元組數
引數3、讀取的此時
引數4、檔案指標
2.2、解析並且獲取mp3檔案資訊
unsigned int i = 10;
MP3INFO mp3info_struct;
mp3info_struct.Url = m_url;
mp3info_struct.number = songNumber;
while(i<(mp3_TagSize-10))
{
frameIDStruct m_struct;
fseek(fp,i,SEEK_SET);
fread(&FrameId,1,4,fp);
fseek(fp,4+i,SEEK_SET);
fread(&frameSize,1,4,fp);
framecount = frameSize[0]*0x1000000+frameSize[1]*0x10000+frameSize[2]*0x100+frameSize[3];
//qDebug()<<"framecount:"<<framecount;
QString aa;
aa = FrameId[0];
aa+=FrameId[1];
aa+=FrameId[2];
aa+=FrameId[3];
//qDebug()<<"aa:"<<aa;
m_struct.beginNum = i+10;
m_struct.FrameId = aa;
i =10+ i+framecount;
m_struct.endNum = i;
m_IDmap.insert(aa,m_struct);
int lenth = m_struct.endNum-m_struct.beginNum;
if(m_struct.FrameId=="APIC")
{
unsigned char temp[20] = {0};
fseek(fp,m_struct.beginNum,SEEK_SET);
fread(&temp,1,20,fp);
int tank=0;
int j = 0;
int pictureFlag=0;
while(1)
{
if((temp[j] == 0xff)&&(temp[j+1] == 0xd8)) //jpeg
{
tank = j;
pictureFlag=1;
mp3info_struct.Picture_type = ".jpg";
qDebug()<<"jpeg";
qDebug()<<"j:"<<j;
break;
}else if((temp[j] == 0x89)&&(temp[j+1] == 0x50)) //png
{
tank = j;
pictureFlag=2;
mp3info_struct.Picture_type = ".png";
qDebug()<<"png";
qDebug()<<"j:"<<j;
break;
}
j++;
}
//qDebug()<<"frameSize:"<<i; //10+i為frameid自己的首地址
unsigned char t[lenth] = {0};
fseek(fp,m_struct.beginNum+tank,SEEK_SET);
mp3info_struct.beginNum = m_struct.beginNum+tank;
mp3info_struct.lenth = lenth;
fread(&t,1,lenth,fp);
if(pictureFlag==1) //jpeg
{
QString temp_1 = "C:/Users/Admin/Desktop/new_text";
QString temp_2 = QString::number(songNumber,10);
temp_1+=temp_2;
temp_1+=".jpg";
mp3info_struct.Picture_url = temp_1;
if(songNumber<3)
{
std::string str_temp = temp_1.toStdString();
const char *ch = str_temp.c_str();
FILE *fpw = fopen( ch, "wb" );
fwrite(&t,lenth,1,fpw);
fclose(fpw); //是否也需要關掉fp
}
}else if(pictureFlag==2) //png
{
QString temp_1 = "C:/Users/Admin/Desktop/new_text";
QString temp_2 = QString::number(songNumber,10);
temp_1+=temp_2;
temp_1+=".png";
mp3info_struct.Picture_url = temp_1;
if(songNumber<4)
{
std::string str_temp = temp_1.toStdString();
const char *ch = str_temp.c_str();
FILE *fpw = fopen( ch, "wb" );
fwrite(&t,lenth,1,fpw);
fclose(fpw); //是否也需要關掉fp
fclose(fp);
}
}
}else if(m_struct.FrameId=="TIT2") //標題
{
QFile file(m_url);
if(!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
qDebug()<<"Can't open the file!"<<endl;
MP3INFO falseInfo;
falseInfo.pic_flag = false;
return falseInfo;
}
QTextStream stream(&file);
stream.seek(m_struct.beginNum+1);
QString all= stream.readLine(lenth-1);
QTextCodec *codec = QTextCodec::codecForName("GBK");
QString ua = codec->toUnicode(all.toLocal8Bit().data());
QString unser = ua.mid(0,(int)(lenth/2-1));
mp3info_struct.Name = unser;
//mp3info_struct.beginNum = m_struct.beginNum;
//mp3info_struct.lenth = lenth;
file.close();
}else if(m_struct.FrameId=="TPE1") //歌手
{
QFile file(m_url);
if(!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
qDebug()<<"Can't open the file!"<<endl;
MP3INFO falseInfo;
falseInfo.pic_flag = false;
return falseInfo;
}
QTextStream stream(&file);
stream.seek(m_struct.beginNum+1);
QString all= stream.readLine(lenth-1);
QTextCodec *codec = QTextCodec::codecForName("GBK");
QString ua = codec->toUnicode(all.toLocal8Bit().data());
QString unser = ua.mid(0,(int)(lenth/2-1));
mp3info_struct.Singer = unser;
//mp3info_struct.beginNum = m_struct.beginNum;
//mp3info_struct.lenth = lenth;
file.close();
}else if(m_struct.FrameId=="TALB") //專輯
{
QFile file(m_url);
if(!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
qDebug()<<"Can't open the file!"<<endl;
MP3INFO falseInfo;
falseInfo.pic_flag = false;
return falseInfo;
}
QTextStream stream(&file);
stream.seek(m_struct.beginNum+1);
QString all= stream.readLine(lenth-1);
QTextCodec *codec = QTextCodec::codecForName("GBK");
QString ua = codec->toUnicode(all.toLocal8Bit().data());
QString unser = ua.mid(0,(int)(lenth/2-1));
mp3info_struct.Album = unser;
//mp3info_struct.beginNum = m_struct.beginNum;
//mp3info_struct.lenth = lenth;
file.close();
}
//qDebug()<<"frameSize:"<<i; //10+i為frameid對應資料的首地址
if(aa=="APIC")
{
break;
}
}
mp3info_struct.pic_flag = true;
return mp3info_struct;
程式碼解釋說明:
第一段:
unsigned int i = 10;
MP3INFO mp3info_struct;
mp3info_struct.Url = m_url;
mp3info_struct.number = songNumber;
變數i用於表示將要在檔案的第11個位元組開始讀取資料(前10個位元組為id3v2的標籤頭)
申明一個結構體 mp3info_struct
第二段:
進入while迴圈 while(i<(mp3_TagSize-10)) mp3_tagsize記錄的是整個標籤的位元組大小,因此需要將前10個位元組跳過,即-10,可以跳轉到上一篇博文參考:
進入迴圈內:
frameIDStruct m_struct;
fseek(fp,i,SEEK_SET);
fread(&FrameId,1,4,fp);
fseek(fp,4+i,SEEK_SET);
fread(&frameSize,1,4,fp);
framecount = frameSize[0]*0x1000000+frameSize[1]*0x10000+frameSize[2]*0x100+frameSize[3];
//qDebug()<<"framecount:"<<framecount;
QString aa;
aa = FrameId[0];
aa+=FrameId[1];
aa+=FrameId[2];
aa+=FrameId[3];
//qDebug()<<"aa:"<<aa;
m_struct.beginNum = i+10;
m_struct.FrameId = aa;
i =10+ i+framecount;
m_struct.endNum = i;
m_IDmap.insert(aa,m_struct);
int lenth = m_struct.endNum-m_struct.beginNum;
檔案讀取幀標識存入FrameId,將其轉化為QString格式存入aa;
讀取幀內容大小存入framesize;
計算得到幀內容的位元組數framecount;
得到幀資料的起始位元組位置 m_struct.beginNum = i+10;
得到幀資料結束位元組位置,並且i的值變為這個幀資料的結束位置,也就是下一個標籤幀的起始位置,那麼到下一個迴圈時,就從下一個標籤幀開始讀取了
i =10+ i+framecount;
m_struct.endNum = i;
得到資料長度lenth
第三段:判斷圖片檔案型別
如果 :if(m_struct.FrameId=="APIC") 幀標識為APIC則為圖片
進入條件語句內部:
unsigned char temp[20] = {0};
fseek(fp,m_struct.beginNum,SEEK_SET);
fread(&temp,1,20,fp);
int tank=0;
int j = 0;
int pictureFlag=0;
讀取圖片資料時要跳過前方14個位元組,然後判斷圖片型別的標誌位才會出現,由於當時我不知道,因此採用了迴圈知道出現標誌位才跳出迴圈的辦法,即:
while(1)
{
if((temp[j] == 0xff)&&(temp[j+1] == 0xd8)) //jpeg
{
tank = j;
pictureFlag=1;
mp3info_struct.Picture_type = ".jpg";
qDebug()<<"jpeg";
qDebug()<<"j:"<<j;
break;
}else if((temp[j] == 0x89)&&(temp[j+1] == 0x50)) //png
{
tank = j;
pictureFlag=2;
mp3info_struct.Picture_type = ".png";
qDebug()<<"png";
qDebug()<<"j:"<<j;
break;
}
j++;
}
後來發現,這個tank的輸出值永遠都是14,因此這一步就顯得沒有必要了
第四段:讀取圖片資料
相關解釋請看註釋
fseek(fp,m_struct.beginNum+tank,SEEK_SET);
mp3info_struct.beginNum = m_struct.beginNum+tank;
mp3info_struct.lenth = lenth;
fread(&t,1,lenth,fp);
if(pictureFlag==1) //jpeg
{
QString temp_1 = "C:/Users/Admin/Desktop/new_text";
QString temp_2 = QString::number(songNumber,10);
temp_1+=temp_2;
temp_1+=".jpg"; //以上為生成圖片名稱
mp3info_struct.Picture_url = temp_1;
std::string str_temp = temp_1.toStdString(); //將QString轉為string型別
const char *ch = str_temp.c_str(); //再轉為靜態
FILE *fpw = fopen( ch, "wb" );
fwrite(&t,lenth,1,fpw); //生成圖片
fclose(fpw); //是否也需要關掉fp
fclose(fp);
}else if(pictureFlag==2) //png
{
QString temp_1 = "C:/Users/Admin/Desktop/new_text";
QString temp_2 = QString::number(songNumber,10);
temp_1+=temp_2;
temp_1+=".png";
mp3info_struct.Picture_url = temp_1;
std::string str_temp = temp_1.toStdString();
const char *ch = str_temp.c_str();
FILE *fpw = fopen( ch, "wb" );
fwrite(&t,lenth,1,fpw);
fclose(fpw); //是否也需要關掉fp
fclose(fp);
}
第五段:讀取標題,歌手等其他資料
else if(m_struct.FrameId=="TIT2") //標題
{
QFile file(m_url);
if(!file.open(QIODevice::ReadWrite | QIODevice::Text)) { //如果開啟檔案失敗,處理
qDebug()<<"Can't open the file!"<<endl;
MP3INFO falseInfo;
falseInfo.pic_flag = false;
return falseInfo;
}
QTextStream stream(&file);
stream.seek(m_struct.beginNum+1);
QString all= stream.readLine(lenth-1);
QTextCodec *codec = QTextCodec::codecForName("GBK");
QString ua = codec->toUnicode(all.toLocal8Bit().data());
QString unser = ua.mid(0,(int)(lenth/2-1));
mp3info_struct.Name = unser;
//mp3info_struct.beginNum = m_struct.beginNum;
//mp3info_struct.lenth = lenth;
file.close();
}
其中:
QTextCodec *codec = QTextCodec::codecForName("GBK");
QString ua = codec->toUnicode(all.toLocal8Bit().data());
兩行程式碼是將編碼格式轉換為GBK
由於GBK格式是兩個位元組表示一個數據(要表示中文),因此轉換之後,後面會出現多餘資料,我們只取前面的資料,即:
QString unser = ua.mid(0,(int)(lenth/2-1));
之後的其他資料,如歌手,專輯名稱也是同樣的方法
end