1. 程式人生 > >QT 讀取mp3ID3V2 獲取mp3專輯圖片、專輯名稱、標題、作者(二)

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

寫的很菜,謝謝觀看,歡迎批評指正