(五)用JAVA編寫MP3解碼器——解析文件信息

      前文提到解析MP3標籤,程序源碼中也已經出現了調用解析MP3標籤、打印MP3文件信息的功能,這兒先說說MP3文件信息的解析。html

      解析MP3的文件信息對MP3解碼器來講只是一個附加功能,若是不加入這部分源碼,同時刪除掉前文源碼中的相關調用,不影響解碼播放。若是你想編寫「迷你」型的MP3解碼器,能夠忽略這些附加的功能。java

      MP3的標籤信息位於文件開始處或結尾處,用於表達MP3文件的相關信息,常見的有ID三、APE等。redis

 

      ID3 V1 位於文件最後的128字節,若是讀取的是網絡文件而服務器又不支持隨機讀取的話,意味着不對對其解析這部分信息。這128字節共表示7個信息:服務器

[0..2]       3  bytes: ID3 v1標識 -- 'TAG'
[3..32]     30 bytes: 標題
[33..62]   30 bytes: 藝術家
[63..92]   30 bytes: 專輯名
[93..96]   4  bytes: 發行年份
[97..126] 30 bytes: v1.0 -- 註釋/附加/備註信息
         v1.1 -- 前29 bytes註釋/附加/備註信息,最後1 byte音軌信息
網絡

[127]       1  byte : 流派數據結構

      從「標題」開始,每部份內容之間用'\0'(字符串結束標誌)或'\20'(空格)隔開。dom

 

      ID3 V2 表示的信息更豐富,結構更復雜,位於文件開始處或位於APE標籤以後。ID3 V2的詳細內容請參見http://www.id3.org/id3v2.3.0 this

      APE V1 & V2 位於文件開始處或ID3 V2以後。詳細內容請參見http://cn.bing.com/reference/semhtml/APE_tag (External links下的連接就是APE V2)。有不少MP3的標籤信息很混亂,內容重複。因爲APE標籤出現並在MP3中大量應用得比ID3晚,MP3文件的「有利」位置都被ID3佔用,因此APE標籤位於文件中的位置讓人捉摸不透,狀況很複雜,對網絡文件來講,判斷APE標籤的位置要反覆在文件中定位,何況有的服務器根本就不支持隨機訪問,因此我這兒就放棄了對APE的解析,儘管APE的解析過程並不複雜。spa

 

       本文只解析ID3 V1的具備的那幾項簡單的內容,JAVA的字符集轉換很方便,因此解析ID3 V2的代碼很簡潔。ID3 V2的每一幀都以「Frame ID」開始,例如TT2或TIT2表示「標題」,程序中經過計算ID的哈希值來識別不一樣的幀。須要指出的是,在解碼器讀入文件時自動對標籤信息進行解析,調用IRandomAccess接口的tagAvailable()方法查詢是否已經完成對tag的解析完畢,對網絡文件,是開線程之後臺方式解析。若是對其解析完畢,調用getTitle()等方法就能夠返回其內容,若是MP3文件自己沒有標籤信息,返回值爲null。具體調用方法見http://jmp123.sf.net/ 下的API文檔。.net

 

ID3Tag.java源碼:

/*
* ID3Tag.java -- 解析MP3文件的ID3 v1/v2 tag
* Copyright (C) 2010
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* If you would like to negotiate alternate licensing terms, you may do
* so by contacting the author: <http://jmp123.sourceforge.net/>
*/
package jmp123.tag;

import java.io.UnsupportedEncodingException;

/*
 ID3v1:
 [0-2]    3  bytes: ID3 v1標識 -- 'TAG'
 [3—32]   30 bytes: 標題
 [33—62]  30 bytes: 藝術家
 [63—92]  30 bytes: 專輯名
 [93—96]  4  bytes: 發行年份
 [97—126] 30 bytes: v1.0 -- 註釋/附加/備註信息
 					v1.1 -- 前29 bytes註釋/附加/備註信息,最後1 byte音軌信息
 [127]    1  byte : 流派
*/

public final class ID3Tag {
	// ID3v1 & ID3v2
	private String strTitle;
	private String strArtist;
	private String strAlbum;
	private String strYear;

	// ID3v2
	//private String strLyrics;		// (內嵌)歌詞
	private int intVersion;
	private int intExHeaderSize;
	private boolean boolID3v2Footer;
	//TEXT_ENCODING[0]應由 "ISO-8859-1" 改成 "GBK". ??
	private static String[] TEXT_ENCODING = {"GBK", "UTF-16", "UTF-16BE", "UTF-8"};

	//--------------------------------------------------------------------
	// ID3v1 & ID3v2

	public void printTag() {
		//if (strLyrics != null)
		//	System.out.println("\r" + strLyrics + "\n");
		if (strTitle != null)
			System.out.println("\r        標題: " + strTitle);
		if (strArtist != null)
			System.out.println("\r      藝術家: " + strArtist);
		if (strAlbum != null)
			System.out.println("\r      唱片集: " + strAlbum);
		if (strYear != null)
			System.out.println("\r      發行年: " + strYear);
	}

	public void destroy() {
		strTitle = strArtist = strAlbum = strYear = null;
		//strLyrics = null;
		intVersion = intExHeaderSize = 0;
		boolID3v2Footer = false;
	}

	public String getTitle() {
		return strTitle;
	}

	public String getArtist() {
		return strArtist;
	}

	public String getAlbum() {
		return strAlbum;
	}

	public String getYear() {
		return strYear;
	}

	//--------------------------------------------------------------------
	// ID3v1

	public boolean checkID3V1(byte[] b) {
		return b[0] == 'T' && b[1] == 'A' && b[2] == 'G';
	}

	public void parseID3V1(byte[] b) {
		int i;
		if (b.length < 128 || checkID3V1(b) == false)
			return;

		byte[] buf = new byte[125];
		System.arraycopy(b, 3, buf, 0, 125);

		for (i = 0; i < 30 && buf[i] != 0; i++);
		if (strTitle == null)
			strTitle = new String(buf, 0, i).trim();
		if (strTitle.length() == 0)
			strTitle = null;

		for (i = 30; i < 60 && buf[i] != 0; i++);
		if (strArtist == null)
			strArtist = new String(buf, 30, i-30).trim();
		if (strArtist.length() == 0)
			strArtist = null;

		for (i = 60; i < 90 && buf[i] != 0; i++);
		if (strAlbum == null)
			strAlbum = new String(buf, 60, i-60).trim();
		if (strAlbum.length() == 0)
			strAlbum = null;

		for (i = 90; i < 94 && buf[i] != 0; i++);
		if (strYear == null)
			strYear = new String(buf, 90, i-90).trim();
		if (strYear.length() == 0)
			strYear = null;

		buf = null;
	}

	//--------------------------------------------------------------------
	// ID3v2

	public int checkID3V2(byte[] b, int off) {
		if(b.length - off < 10)
			return 0;
		if(b[off] != 'I' || b[off+1] != 'D' || b[off+2] != '3')
			return 0;

		intVersion = b[off+3] & 0xff;

		if(intVersion > 2 && (b[off+5] & 0x40) != 0)
			intExHeaderSize = 1;		//設置爲1表示有擴展頭

		boolID3v2Footer = (b[off+5] & 0x10) != 0;
		int size = synchSafeInt(b, off+6);
		size += 10;					// ID3 header:10bytes 
		return size;
	}

	//b[off..]不含ID3v2 頭(10 bytes)
	public void parseID3V2(byte[] b, int off) {
		int max_size = b.length;
		int pos = off;
		if(intExHeaderSize == 1) {
			intExHeaderSize = synchSafeInt(b, off);
			pos += intExHeaderSize;
		}
		max_size -= 10;		//1 frame header: 10 bytes
		if(boolID3v2Footer)
			max_size -= 10;

		//System.out.println("ID3 v2." + intVersion);
		while(pos < max_size)
			pos += getText(b, pos, max_size);
	}

	public static int synchSafeInt(byte[] b, int off) {
		int i = (b[off] & 0x7f) << 21;
		i |= (b[off+1] & 0x7f) << 14;
		i |= (b[off+2] & 0x7f) << 7;
		i |=  b[off+3] & 0x7f;
		return i;
	}

	private int makeInt(byte[] b, int off, int len) {
		int i, ret = b[off] & 0xff;
		for (i = 1; i < len; i++) {
			ret <<= 8;
			ret |= b[off + i] & 0xff;
		}
		return ret;
	}

	private int getText(byte[] b, int off, int max_size)  {
		int id_part = 4, frame_header = 10;
		if(intVersion == 2) {
			id_part = 3;
			frame_header = 6;
		}
		String id = new String(b, off, id_part);
		off += id_part;

		int fsize, len;
		fsize = len = makeInt(b, off, id_part);
		off += id_part;		// frame size = frame id bytes
		if (intVersion > 2)
			off += 2;		// flag: 2 bytes

		int enc = b[off];
		len--;				// Text encoding: 1 byte
		off++;				// Text encoding: 1 byte
		if (len <= 0 || off + len > max_size || enc < 0 || enc >= TEXT_ENCODING.length)
			return fsize + frame_header;
		//System.out.println(len+" ------------------------------------ off = " + off);
		//System.out.println("ID: " + id + ", id.hashCode()=" + id.hashCode());
		//System.out.println("text encoding: " + TEXT_ENCODING[enc]);
		//System.out.println("frame size: " + fsize);

		try {
			switch(id.hashCode()) {
			case 83378:		//TT2 v2.2
			case 2575251:	//TIT2  標題
				if (strTitle == null)
					strTitle = new String(b, off, len, TEXT_ENCODING[enc]).trim();
				break;
			case 83552:
			case 2590194:	//TYER  發行年
				if (strYear == null)
					strYear = new String(b, off, len, TEXT_ENCODING[enc]).trim();
				break;
			case 2569358:	//TCON  流派
				break;
			case 82815:
			case 2567331:	//TALB  唱片集
				if (strAlbum == null)
					strAlbum = new String(b, off, len, TEXT_ENCODING[enc]).trim();
				break;
			case 83253:
			case 2581512:	//TPE1  藝術家
				if (strArtist == null)
					strArtist = new String(b, off, len, TEXT_ENCODING[enc]).trim();
				break;
			case 2583398:	//TRCK  音軌
				break;
			/*case 2614438:	//USLT  歌詞
				off += 4;	//Languge: 4 bytes
				len -= 4;
				strLyrics = new String(b, off, len, TEXT_ENCODING[enc]);
				break;*/
			}
		} catch (UnsupportedEncodingException e) {
			return fsize + frame_header;
		} finally {
			id = null;
		}
		return fsize + frame_header;
	}
}

 

上一篇:(四)用JAVA編寫MP3解碼器——讀取文件

下一篇:(六)用JAVA編寫MP3解碼器——幀數據結構

 

【本程序下載地址】http://jmp123.sourceforge.net/

相關文章
相關標籤/搜索