文档章节

(五)用JAVA编写MP3解码器——解析文件信息

暗之幻影
 暗之幻影
发布于 2015/01/04 15:29
字数 1729
阅读 61
收藏 2

      前文提到解析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;
	}
}

 

上一篇:(四)用JAVA编写MP3解码器——读取文件

下一篇:(六)用JAVA编写MP3解码器——帧数据结构

 

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

本文转载自:http://lfp001.iteye.com/blog/740930

暗之幻影
粉丝 20
博文 377
码字总数 71245
作品 0
南京
高级程序员
私信 提问
疯狂Spring Cloud连载(11)——Feign的编码器与解码器

本文节选自《疯狂Spring Cloud微服务架构实战》 京东购买地址:https://item.jd.com/12256011.html 当当网购买地址:http://product.dangdang.com/25201393.html Spring Cloud教学视频:htt...

杨大仙的程序空间
2017/10/23
3.1K
5
Java虚拟机基础——3类加载机制

Java虚拟机整体篇幅如下: Java虚拟机基础——1Java的内存模型 Java虚拟机基础——2JVM运行时数据区 Java虚拟机基础——3类加载机制 Java虚拟机基础——4内存回收机制 本片文章内容如下: 1、...

隔壁老李头
2018/10/03
0
0
我是SPI,我让框架更加优雅了!

文章首发于【陈树义的博客】,点击跳转到原文《我是 SPI,我让框架更加优雅了!》 自从上次小黑进入公司的架构组之后,小黑就承担起整个公司底层框架的开发工作。就在刚刚,小黑又接到一个任...

陈树义
2018/07/16
0
0
如何用JMF编写MP3音乐播放器2 ——MP3文件工程的建立和源代码

如何用JMF编写MP3音乐播放器2 ——MP3文件工程的建立和源代码 做完上面1的所有步骤之后,说明你已经把JMF的环境配置完毕,接下来我们就可以进行MP3的java文件的编写了。 首先我们先建立一个j...

cui314461408
2013/08/09
3.6K
4
用java获取mp3文件播放时长和tag信息

需要从mp3文件拿到mp3播放时长,折腾了几个小时,绕了弯路,大致记录一下。 先是找到mp3fenge这个分割mp3文件的开源项目 http://code.google.com/p/mp3fenge/ 发现用它的API得到trackLength...

gtd
2012/05/21
4.5K
1

没有更多内容

加载失败,请刷新页面

加载更多

Mybatis Plus删除

/** @author beth @data 2019-10-17 00:30 */ @RunWith(SpringRunner.class) @SpringBootTest public class DeleteTest { @Autowired private UserInfoMapper userInfoMapper; /** 根据id删除......

一个yuanbeth
今天
4
0
总结

一、设计模式 简单工厂:一个简单而且比较杂的工厂,可以创建任何对象给你 复杂工厂:先创建一种基础类型的工厂接口,然后各自集成实现这个接口,但是每个工厂都是这个基础类的扩展分类,spr...

BobwithB
今天
5
0
java内存模型

前言 Java作为一种面向对象的,跨平台语言,其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似,很多人会傻傻分不清楚。比如本文我们要讨论的JVM内存结构、Java内存模...

ls_cherish
今天
4
0
友元函数强制转换

友元函数强制转换 p522

天王盖地虎626
昨天
5
0
js中实现页面跳转(返回前一页、后一页)

本文转载于:专业的前端网站➸js中实现页面跳转(返回前一页、后一页) 一:JS 重载页面,本地刷新,返回上一页 复制代码代码如下: <a href="javascript:history.go(-1)">返回上一页</a> <a h...

前端老手
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部