这篇承接上篇,主要记录的是代码,关于mp3ID3V2的简要介绍可以跳转到上一篇:
前提说明:没有使用任何的外部库,纯代码实现的,根据网上主流的c语言代码提取mp3id3v2标签信息,结合QT修改的
#ifndef MP3TAGLIB_H
#define MP3TAGLIB_H
#include
#include "stdlib.h"
#include "stdio.h"
#include
#include
#include
#include
#include
#include
#include
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;
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 m_IDmap;
};
#endif // MP3TAGLIB_H
这里只展示GetAllInfo函数的实现,所有的功能都在这个函数内实现了,其他函数是我做音乐播放器用到的,这里不重要
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(filePath.utf16());
也可以:
const wchar_t *url = L"C:/Users/Admin/Desktop/song.mp3";
fp = _wfopen(url,L"rb"); 为c语言的文件读取函数,因为url是宽字节,所以要使用_wfopen而不使用fopen,关于这两个函数的有关内容,可以参考博文:
https://blog.csdn.net/zmq5411/article/details/21003831
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、文件指针
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:"<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!"<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!"<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:"<
第一段:
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,可以跳转到上一篇博文参考:
QT 读取mp3ID3V2 获取mp3专辑图片、专辑名称、标题、作者(一)
进入循环内:
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:"<
文件读取帧标识存入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:"<
后来发现,这个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!"<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