`
lfp001
  • 浏览: 100615 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

阅读更多

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

分享到:
评论
5 楼 wangshengyuan 2016-01-01  
非常感谢博主,真是站在了巨人的肩膀上了
4 楼 lfp001 2010-09-16  
MO_oC 写道
期待加入专辑封面的分析

等到写GUI播放器再说吧,现在的是命令行的呢。
3 楼 MO_oC 2010-09-16  
期待加入专辑封面的分析
2 楼 lfp001 2010-08-24  

WMA是微软公司推出的音频格式,指望MS公开其技术细节不太现实吧。可以用其SDK开发支持WMA格式的播放器。希望从低层实现解码WMA是不现实的。
WMA推出之初,吹得有点过了哈,说是WMA将取代MP3,几年过去了,MP3还是没走向灭亡,至今MP3仍是网络上的主流格式。WMA在低位率压缩时的性能超过MP3的低位率,其它嘛...呵呵。另外,WMA的音乐版权保护功能很完善。
1 楼 chensulong 2010-08-20  
能顺便提一下WMA解析相关的知识吗?非常感激。。。

相关推荐

    java源码包---java 源码 大量 实例

     用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字  Java波浪文字,一个利用...

    基于jmp123解码器的mp3播放器.zip

    这个压缩包文件的标题揭示了它的核心内容——一个使用jmp123解码器的MP3播放器项目。jmp123是一个开源的、用C语言编写的MP3解码库,它能够解析并解码MP3音频文件,将压缩的数字音频数据转化为可以播放的格式。这个...

    Java课程设计——电子音乐盒

    【Java课程设计——电子音乐盒】是一个以Java技术为基础,利用Java Media Framework(JMF)开发的音乐播放软件。这个项目旨在让学生掌握Java编程语言在多媒体应用中的实践,特别是音频处理和用户界面设计方面的能力...

    jmpg123.tar.gz_jmpg1_jmpg123_mp1_mp2_mp3 解码

    综上所述,"jmpg123.tar.gz_jmpg1_jmpg123_mp1_mp2_mp3 解码"涉及到的是用Java编写的MP1、MP2和MP3音频解码器的源代码,其在多媒体处理领域有广泛应用前景,同时也展示了Java在实现跨平台音频处理上的能力。...

    java源码包2

     用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字  Java波浪文字,一个利用...

    java源码包3

     用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字  Java波浪文字,一个利用...

    音乐播放器——湘潭大学JAVA课程设计题目

    开发者需要了解如何加载音频文件,如MP3或WAV,然后使用AudioInputStream和Clip类来解码和播放音频流。 3. **进度条显示**:进度条用于显示当前音乐的播放进度。这涉及到监听音乐播放状态,通过获取音乐总时长和...

    成百上千个Java 源码DEMO 4(1-4是独立压缩包)

    Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...

    java源码包4

     用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字  Java波浪文字,一个利用...

    成百上千个Java 源码DEMO 3(1-4是独立压缩包)

    Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...

    解析XML特殊字符方法

    除了实体引用,XML还提供了一种特殊的结构——CDATA区(Character Data),用于包含一大段不应被解析的文本。在CDATA区内,解析器会忽略所有特殊字符。例如: ```xml &lt;![CDATA[ 这里可以放不会被解析的文本,包括...

    JAVA上百实例源码以及开源项目源代码

    Java编写的显示器显示模式检测程序 2个目标文件 内容索引:JAVA源码,系统相关,系统信息检测 用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作...

    java 解析xml

    除了DOM和SAX,Java还引入了另一种解析方式——StAX(Streaming API for XML),它介于两者之间,提供了更高效的流式处理。StAX允许程序以迭代的方式逐个处理XML事件,既不像DOM那样占用大量内存,也不像SAX那样需要...

    jmp123_400_utf8_mini:JAVA开源程序,包含MP3解码器库和播放器

    今天我们将聚焦于一个名为"jmp123_400_utf8_mini"的JAVA开源项目,它包含了MP3解码器库和播放器,对于那些想要深入理解音频处理和JAVA编程的朋友们来说,这是一个宝贵的资源。 首先,我们来解析一下这个项目的名称...

    korau:Kotlin程序AUdio-纯Kotlin WAV,MP3和OGG vorbis解码器

    5. **解析器与解码器**:korau包含音频文件的解析器,能够读取和验证音频文件的头部信息,然后使用相应的解码器将压缩的数据转换为可播放的音频流。 6. **API友好**:korau的API设计简洁且直观,使得开发者可以轻松...

    基于单片机——PC红外线遥控器上位机及电路图.zip

    3. **上位机软件开发**:PC红外线遥控器的上位机软件可能用C#、Java或Python等语言编写,实现用户界面、信号发送和接收等功能。 4. **硬件设计**:电路图展示了单片机如何连接红外发射和接收模块,电源,以及其他...

    UEditor(五)——解决上传图片时报错:未找到上传数据

    在使用UEditor这款强大的富文本编辑器时,可能会遇到上传图片时报错“未找到上传数据”的问题。这个错误通常是因为服务器端的配置不正确或与客户端的交互存在误解导致的。UEditor是一款由百度开发的JavaScript富文本...

    JAVA二维码jar包与例子

    Java二维码(JAVA二维码)是一种广泛应用于移动设备和网络服务中的数据编码技术,它将大量信息如网址、文本、名片等编码成一个二维图形——二维码,然后通过扫描来快速读取和解析这些信息。在Java中处理二维码,通常...

    一个基于matlab的简单二维码解码程序Decoder.rar

    该项目用Java编写,但通过Java绑定,也可以在其他编程语言,如MATLAB中使用。 MATLAB是一个强大的数学计算和数据分析环境,但其原生功能并不包括二维码的处理。在MATLAB中调用ZXing解码二维码,需要进行一些额外的...

Global site tag (gtag.js) - Google Analytics