前文提到解析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; } }
【本程序下載地址】http://jmp123.sourceforge.net/