好吧,最近帮朋友写毕设时碰到这个的问题的,在网上也是多番查询,都不尽人意,于是自己参考多番之后,写了一个
其实吧,这个读取专辑图片也不是很难得,首先判断MP3文件中是否含有ID3V2的标签,关于ID3V2的格式有一堆的说法
我嘛,不怎么关心,因此只攻专辑图片,也就是判断是否包含APIC这个标识
找到这个标识其实也就是和解析普通文件一样,每个像APIC标识的东西,都有帧标识头和帧标识内容组成,有头中得出帧内容的实际长度即可,然后将数据读出就行啦
但是对于APIC标识,读出了内容,直接保存为文件并不能显示图片,因为这个帧内容的前面一部分用于记录图片的类型,之后还有一部分空数据,这里就需要些判断
我用的mp3文件大部分都是jpeg的图片,在APIC帧内容中开头就是image/jpeg,这种内容,由于JPEG图片以0xFFD8开始,因此只要判断帧内容中以0xFFD8开始即可获取真正的图片数据,当然了,其他的数据格式,我就没有判断了,现在附上C++代码
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <string.h> int ReadAIPCFromMP3(const char* path); int main(int argc, char **argv) { ReadAIPCFromMP3("D:\\KuGou\\金海心 - 悲伤的秋千.mp3"); return 0; } bool isFrameAPIC(const char* cID3V2Fra_head) { // 在win7中vs2012下,没发现strncasecmp函数,不然不需要两次判断 // 当然了,这种判断也可以逐字节判断 if ((strncmp(cID3V2Fra_head, "APIC", 4) == 0) || (strncmp(cID3V2Fra_head, "apic", 4) == 0)) return true; return false; } int calcID3V2Len(const char* cID3V2_head) { int len = (cID3V2_head[6] & 0x7f) << 21 | (cID3V2_head[7] & 0x7f) << 14 | (cID3V2_head[8] & 0x7f) << 7 | (cID3V2_head[9] & 0x7f); return len; } int calclID3V2FraLength(const char* cID3V2Fra_head) { int len = (int)(cID3V2Fra_head[4] * 0x100000000 + cID3V2Fra_head[5] * 0x10000 + cID3V2Fra_head[6] * 0x100 + cID3V2Fra_head[7]); return len; } bool isJPEG(const char* data) { // JPEG格式数据的起始为0xFFD8,当然了,结束也有标志,但是这里不需要了 if (((unsigned char)data[0] == 0xFF) && ((unsigned char)data[1] == 0xD8)) return true; return false; } int ReadAIPCFromMP3(const char* path) { FILE* fp = fopen(path, "rb"); if (!fp) { printf("cannot open this mp3\n"); return -1; } // 这里的ID3V2以及帧标识的大小由标准规定均为10个字节 // 这里其实应该是作为字节存储,用unsigned char更好,这里就简单用char替代吧 char cID3V2_head[10] = {0}, cID3V2Fra_head[10] = {0}; long ID3V2_len = 0, lID3V2Fra_length = 0; char* cID3V2Fra = NULL; // 读取帧头,这里就是为了判断是否是ID3V2的标签头 fread(cID3V2_head, 10, 1, fp); if ((cID3V2_head[0] == 'I' || cID3V2_head[0] == 'i') && (cID3V2_head[1] == 'D' || cID3V2_head[1] == 'd') && cID3V2_head[2] == '3') { // 获取ID3V2标签的长度 ID3V2_len = calcID3V2Len(cID3V2_head); } bool hasAPIC = false; // 这里来逐个读取帧标识,这里的专辑图片即保存在APIC标识里 while ((ftell(fp) + 10) <= ID3V2_len) { // 这里每个帧标识的长度也为10,由于每个帧标识的存储的数据的长度不一 // 每次要读取出来,进行运算获取真正数据长度 memset(cID3V2Fra_head, 0, 10); fread(cID3V2Fra_head, 10, 1, fp); lID3V2Fra_length = (cID3V2Fra_head[4] * 0x100000000 + cID3V2Fra_head[5] * 0x10000 + cID3V2Fra_head[6] * 0x100 + cID3V2Fra_head[7]); // 这里判断是否为专辑图片的帧标识 if (isFrameAPIC(cID3V2Fra_head)) { cID3V2Fra = (char*)calloc(lID3V2Fra_length, 1); if (cID3V2Fra != NULL) { hasAPIC = true; fread(cID3V2Fra, lID3V2Fra_length, 1, fp); } break; } else { // 移动到下一帧标识 fseek(fp, lID3V2Fra_length, SEEK_CUR); } } fclose(fp); // 到这里如果,专辑图片要么读出,要么就不存在 if (hasAPIC) { // 这里整个数据的前面一部分数据是用来记录专辑图片的格式 // 例如 image/jpeg image/png等,这里大部分的专辑图片都是jpeg的 // 因此这里简单的只判断jpeg的格式,除去图片格式,数据前部依然有些是空数据 // 因此以jpeg的标识来定位图片数据的起始 int start = 0; while (start < lID3V2Fra_length) { if (isJPEG(cID3V2Fra + start)) break; ++start; } // 是以JPEG格式存在,则存储为jpeg的文件 if (start != lID3V2Fra_length) { // 这里没有错误处理,从简 FILE* jpegFP = fopen("test.jpeg", "wb"); fwrite(cID3V2Fra + start, lID3V2Fra_length - start, 1, jpegFP); fclose(jpegFP); } } return 0; }