從MP3中讀取專輯圖片

好吧,最近幫朋友寫畢設時碰到這個的問題的,在網上也是多番查詢,都不盡人意,於是自己參考多番之後,寫了一個

其實吧,這個讀取專輯圖片也不是很難得,首先判斷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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章