前文提到解析MP3标签,程序源码中也已经出现了调用解析MP3标签、打印MP3文件信息的功能,这儿先说说MP3文件信息的解析。
解析MP3的文件信息对MP3解码器来说只是一个附加功能,如果不加入这部分源码,同时删除掉前文源码中的相关调用,不影响解码播放。如果你想编写“迷你”型的MP3解码器,可以忽略这些附加的功能。
MP3的标签信息位于文件开始处或结尾处,用于表达MP3文件的相关信息,常见的有ID3、APE等。
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'(空格)隔开。
ID3 V2 表示的信息更丰富,结构更复杂,位于文件开始处或位于APE标签之后。ID3 V2的详细内容请参见http://www.id3.org/id3v2.3.0
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的解析过程并不复杂。
本文只解析ID3 V1的具有的那几项简单的内容,JAVA的字符集转换很方便,所以解析ID3 V2的代码很简洁。ID3 V2的每一帧都以“Frame ID”开始,例如TT2或TIT2表示“标题”,程序中通过计算ID的哈希值来识别不同的帧。需要指出的是,在解码器读入文件时自动对标签信息进行解析,调用IRandomAccess接口的tagAvailable()方法查询是否已经完成对tag的解析完毕,对网络文件,是开线程以后台方式解析。如果对其解析完毕,调用getTitle()等方法就可以返回其内容,如果MP3文件本身没有标签信息,返回值为null。具体调用方法见http://jmp123.sf.net/ 下的API文档。
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/
来源:oschina
链接:https://my.oschina.net/u/113740/blog/363534