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

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

阅读更多

      MP3文件按帧(frame)依次存储,解码时也是逐帧解码,所以我们应该首先弄清MP3帧内的数据的封装形式。帧的结构如下图所示:

 

      图中sync表示帧同步字,每一帧以同步字开始;side info表示帧边信息;main_data_end是帧边信息结构中的一个成员。

      每一帧依次存放帧头(header)、帧边信息(side information)、主信息(main information)。其中的主信息由增益因子(scale factor)和主数据(main data)组成。

      每一帧的帧头长4字节,如果帧头信息中的protection_bit为零,帧头之后有2字节的循环冗余校检(CRC)数据。帧头在前面已经讲解过了,不再赘述。

      帧边信息的长度计算方法见《 (二)用JAVA编写MP3解码器——帧头信息解码 》内Header.java第207行。

      主信息的长度计算方法见《 (二)用JAVA编写MP3解码器——帧头信息解码 》内Header.java第218行。

      从上图可以看出,某一帧的主信息可能不在它本身这一帧内,可能有一部分存放在上一帧内,也有可能有一部分数据存放在下一帧内。例如上图中第3帧的主信息就存放在3个相邻的帧内、第1帧的数据包含相邻的三帧的主数据。这样使某些帧主信息太小的帧(如上图中的第1帧)不至于帧的长度太短,提高存储效率。

      对固定码率(CBR)编码的MP3,每一帧的长度几乎相等,由于有的帧有附加位而多一字节;对平均码率(ABR)编码的MP3,每一帧的长度也几乎相等;对变化码率(VBR)编码的MP3来说,每一帧的长度不同,长度相差会比较大,但最长的帧不超过1732字节。

      VBR编码的MP3的第一帧没有存储以哈夫曼编码的主数据,存储的是VBR编码信息,其中TOC域存放的是帧百分比和文件长度的对应关系,我们知道VBR编码的MP3每帧长度不一定相同,但每帧解码成为PCM数据长度是相同的,即每帧的播放时长相同。利用TOC域的数据就使文件按时间轴拖放播放时,定位到正确的文件位置成为可能。

      MPEG 1.0采用标准的立体声编码的帧,每一帧的主信息被划分为两个粒度组(granule),每个粒度组又分为两个声道(channel)。知道了MP3帧数据的封装形式,就不难理解后文中多处出现的for(gr...)和for(ch...)循环语句了。

 

      Layer3.java内的解码一帧MP3(MPEG 1.0 Audio Layer III简称MP3)定义的数据结构和初始化解码器用到的许多常量如下:

/*
* Layer3.java -- 解码Layer3
*
* 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/>.
*/
package decoder;

public final class Layer3 implements ILayer123 {
	private static Header objHeader;
	private static Synthesis objFilter;
	private static BitStream objInBitStream;
	private static int intWhichChannel;
	private static int intMaxGr;
	private static int intChannels;
	private static int intFirstChannel;
	private static int intLastChannel;
	private static int[] intSfbIdxLong;
	private static int[] intSfbIdxShort;
	private static boolean boolIntensityStereo;	//强度立体声:true

	public Layer3(BitStream bs,Header h, Synthesis filter, int wch) {
		objInBitStream = bs;
		objHeader = h;
		intChannels = objHeader.getChannels();
		intWhichChannel = wch;
		intMaxGr = (objHeader.getVersion() == Header.MPEG1) ? 2 : 1;
		objFilter = filter;
		objSI = new SideInfo();
		objHuffBits = new HuffmanBits(objInBitStream);
		objSideBS = new BitStream(36);
		scfL = new int[2][23];
		scfS = new int[2][3][13];
		is = new int[32 * 18 + 4];
		xr = new float[2][32][18];
		intWidthLong = new int[22];
		intWidthShort = new int[13];
		floatRawOut = new float[36];
		floatPrevBlck = new float[2][32][18];
		cs = new float[] { 0.857492925712f, 0.881741997318f, 0.949628649103f,
				0.983314592492f, 0.995517816065f, 0.999160558175f,
				0.999899195243f, 0.999993155067f };
		ca = new float[] { -0.5144957554270f, -0.4717319685650f,
				-0.3133774542040f, -0.1819131996110f, -0.0945741925262f,
				-0.0409655828852f, -0.0141985685725f, -0.00369997467375f };

		int i;
		/*
		 * floatPowIS[]:用于查表求 v^(4/3),v是经哈夫曼解码出的一个(正)值,该值的范围是0..8191
		 */
		floatPowIS = new float[8207];
		for (i = 0; i < 8207; i++)
			floatPowIS[i] = (float) Math.pow(i, 4.0 / 3.0);

		/*
		 * pow_2[] -- 用于查表求 2 ^ exp, exp 是长块(long block)逆量化的指数
		 */
		floatPow2 = new float[256 + 118 + 4];
		for (i = -256; i < 118 + 4; i++)
			floatPow2[i + 256] = (float) Math.pow(2.0, -0.25 * (i + 210));
		//initMDCT();
		if (intChannels == 2)
			switch (intWhichChannel) {
			case Decoder.CH_LEFT:
				intFirstChannel = intLastChannel = 0;
				break;
			case Decoder.CH_RIGHT:
				intFirstChannel = intLastChannel = 1;
				break;
			case Decoder.CH_BOTH:
			default:
				intFirstChannel = 0;
				intLastChannel = 1;
				break;
			}
		else
			intFirstChannel = intLastChannel = 0;

		//---------------------------------------------------------------------
		//待解码文件的不同特征用到不同的变量.初始化:
		//---------------------------------------------------------------------
		int intSfreq = objHeader.getSampleFrequency();
		intSfreq += (objHeader.getVersion() == Header.MPEG1) ? 0 : ((objHeader.getVersion() == Header.MPEG2) ? 3 : 6);
		/*
		 * ANNEX B,Table 3-B.8. Layer III scalefactor bands
		 */
		switch (intSfreq) {
		case 0:
			/* MPEG 1, sampling_frequency=0, 44.1kHz */
			intSfbIdxLong = new int[] { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44,
					52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 16, 22, 30, 40, 52, 66,
					84, 106, 136, 192 };
			break;
		case 1:
			/* MPEG 1, sampling_frequency=1, 48kHz */
			intSfbIdxLong = new int[] { 0, 4, 8, 12, 16, 20, 24, 30, 36, 42,
					50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 16, 22, 28, 38, 50, 64,
					80, 100, 126, 192 };
			break;
		case 2:
			/* MPEG 1, sampling_frequency=2, 32kHz */
			intSfbIdxLong = new int[] { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44,
					54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 16, 22, 30, 42, 58, 78,
					104, 138, 180, 192 };
			break;

		case 3:
			/* MPEG 2, sampling_frequency=0, 22.05kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 24, 32, 42, 56, 74,
					100, 132, 174, 192 };
			break;
		case 4:
			/* MPEG 2, sampling_frequency=1, 24kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 114, 136, 162, 194, 232, 278, 330, 394, 464, 540,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 136, 180, 192 };
			break;
		case 5:
			/* MPEG 2, sampling_frequency=2, 16kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 134, 174, 192 };
			break;
		case 6:
			/* MPEG 2.5, sampling_frequency=0, 11.025kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 134, 174, 192 };
			break;
		case 7:
			/* MPEG 2.5, sampling_frequency=1, 12kHz */
			intSfbIdxLong = new int[] { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66,
					80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522,
					576 };
			intSfbIdxShort = new int[] { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80,
					104, 134, 174, 192 };
			break;
		case 8:
			/* MPEG 2.5, sampling_frequency=2, 8kHz */
			intSfbIdxLong = new int[] { 0, 12, 24, 36, 48, 60, 72, 88, 108,
					132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572,
					574, 576 };
			intSfbIdxShort = new int[] { 0, 8, 16, 24, 36, 52, 72, 96, 124,
					160, 162, 164, 166, 192 };
			break;
		}
		for (i = 0; i < 22; i++)
			intWidthLong[i] = intSfbIdxLong[i + 1] - intSfbIdxLong[i];
		for (i = 0; i < 13; i++)
			intWidthShort[i] = intSfbIdxShort[i + 1] - intSfbIdxShort[i];
		//---------------------------------------------------------------------
		//强度立体声:
		boolIntensityStereo = objHeader.isIStereo();
		if(boolIntensityStereo) {
			if (objHeader.getVersion() == Header.MPEG1)	//MPEG 1.0
				is_coef = new float[] { 0.0f, 0.211324865f, 0.366025404f, 0.5f,
						0.633974596f, 0.788675135f, 1.0f };
			else	//MPEG 2.0/2.5
				lsf_is_coef = new float[][] {
						{ 0.840896415f, 0.707106781f, 0.594603558f, 0.5f,
								0.420448208f, 0.353553391f, 0.297301779f,
								0.25f, 0.210224104f, 0.176776695f,
								0.148650889f, 0.125f, 0.105112052f,
								0.088388348f, 0.074325445f },
						{ 0.707106781f, 0.5f, 0.353553391f, 0.25f,
								0.176776695f, 0.125f, 0.088388348f, 0.0625f,
								0.044194174f, 0.03125f, 0.022097087f,
								0.015625f, 0.011048543f, 0.0078125f,
								0.005524272f } };
		}
		//-----------------------------------------------------------------
		//MPEG 2.0/2.5
		if (objHeader.getVersion() != Header.MPEG1) {
			i_slen2 = new int[256];		 // MPEG 2.0 slen for intensity stereo
			n_slen2 = new int[512];		 // MPEG 2.0 slen for 'normal' mode
			slen_tab2 = new byte[][][] {
					{ { 6, 5, 5, 5 }, { 6, 5, 7, 3 }, { 11, 10, 0, 0 },
							{ 7, 7, 7, 0 }, { 6, 6, 6, 3 }, { 8, 8, 5, 0 } },
					{ { 9, 9, 9, 9 }, { 9, 9, 12, 6 }, { 18, 18, 0, 0 },
							{ 12, 12, 12, 0 }, { 12, 9, 9, 6 },
							{ 15, 12, 9, 0 } },
					{ { 6, 9, 9, 9 }, { 6, 9, 12, 6 }, { 15, 18, 0, 0 },
							{ 6, 15, 12, 0 }, { 6, 12, 9, 6 }, { 6, 18, 9, 0 } } };
			int j, k, l, n;
			for (i = 0; i < 5; i++)
				for (j = 0; j < 6; j++)
					for (k = 0; k < 6; k++) {
						n = k + j * 6 + i * 36;
						i_slen2[n] = i | (j << 3) | (k << 6) | (3 << 12);
					}
			for (i = 0; i < 4; i++)
				for (j = 0; j < 4; j++)
					for (k = 0; k < 4; k++) {
						n = k + j * 4 + i * 16;
						i_slen2[n + 180] = i | (j << 3) | (k << 6) | (4 << 12);
					}
			for (i = 0; i < 4; i++)
				for (j = 0; j < 3; j++) {
					n = j + i * 3;
					i_slen2[n + 244] = i | (j << 3) | (5 << 12);
					n_slen2[n + 500] = i | (j << 3) | (2 << 12) | (1 << 15);
				}
			for (i = 0; i < 5; i++)
				for (j = 0; j < 5; j++)
					for (k = 0; k < 4; k++)
						for (l = 0; l < 4; l++) {
							n = l + k * 4 + j * 16 + i * 80;
							n_slen2[n] = i | (j << 3) | (k << 6) | (l << 9);
						}
			for (i = 0; i < 5; i++)
				for (j = 0; j < 5; j++)
					for (k = 0; k < 4; k++) {
						n = k + j * 4 + i * 20;
						n_slen2[n + 400] = i | (j << 3) | (k << 6) | (1 << 12);
					}
		}
	}

	// 解码帧边信息等代码...
}

 

      需要根据MP3编码的不同特征而初始化为不同常量的部分在构造方法内初始化,这些常量值及含义在手册内都查得到。用到的常量、变量很多,看了让人挠头。构造方法中的形参中有3个类class BitStream、class Header、class Synthesis变量,这三个类的实例先于class Layer的对象实例被初始化。解码的输入是位流对象(BitStream),解码输到PCM缓冲区(在class Synthesis内定义);形参int wch表示输出哪一个声道。

      解码的过程就是由经过编码的MP3文件还原成PCM数据,再将PCM数据送入音频硬件播放。我把解码一帧MP3划分为10个步骤,这些步骤在下文中陆续写出来,各个步骤内也将谈到class Layer3内定义的变量,将下文解码帧边信息等各步骤的代码放在class Layer3内就封装好了解码一帧MP3的一个类了。

 

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

下一篇:(七)用JAVA编写MP3解码器——解码帧边信息

 

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

  • 大小: 51.9 KB
分享到:
评论
10 楼 lfp001 2010-08-24  
you 写道
不错,希望楼主有空写下编码转换之类的哦

从底层编写代码实现音视频的编码转换的话,非几个人可以完成,格式太多。
如果你想了解音视频的编解,MPlayer的官方网站有一些源码:
http://www.mplayerhq.hu/
9 楼 0604hx 2010-08-22  
不错的文章,顶起
8 楼 笑我痴狂 2010-08-21  
厉害  我都没接受过这玩意 
7 楼 ztcwh 2010-08-21  
真是厉害。。。该收藏下。
6 楼 you 2010-08-21  
不错,希望楼主有空写下编码转换之类的哦
5 楼 nakupanda 2010-08-20  
sunway00 写道
论坛里很少这种针对底层、编码等方面的贴在,好像Java的人都在研究业务、框架、性能、数据库呢。楼主这个是javaEye的另类亮点!


同感
4 楼 dopic 2010-08-20  
我刚要做个java的网络MP3的上位机,
3 楼 form_rr 2010-08-20  
这个不错,有完整的讲解!
底层的东西论坛上真的少有。
如果不是因为jre的关系,java的桌面应用应该大规模展开了。
2 楼 sunway00 2010-08-20  
论坛里很少这种针对底层、编码等方面的贴在,好像Java的人都在研究业务、框架、性能、数据库呢。楼主这个是javaEye的另类亮点!
1 楼 mark.chinama 2010-08-20  
这等好东西一定要顶,请作者一定把整个过程都写完

相关推荐

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

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

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

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

    jmpg123.tar.gz_jmpg1_jmpg123_mp1_mp2_mp3 解码

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

    JAVA读取文件——以行为单位读取

    在Java编程语言中,读取文件是常见的任务之一,尤其对于处理文本文件,如TXT文件,以行为单位...在实际项目中,你可以根据需要扩展这个基础示例,例如添加异常处理,或者将读取的内容存储到数据结构中以便进一步处理。

    java源码包3

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

    java源码包2

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

    JAVA基于纠错码的冗余技术的研究——EVENODD码的设计与实现(源代码+LW).zip

    此时,解码器可以尝试反转出现错误的那一位,以恢复原始数据。 3. **源代码实现**:JAVA作为面向对象的编程语言,可以利用类和方法来组织EVENODD码的编码和解码逻辑。一个简单的实现可能包括两个类:`...

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

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

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

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

    java源码包4

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

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

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

    数据结构课程设计哈夫曼编码[1].doc

    数据结构课程设计中,哈夫曼编码是一种优化的前缀编码方法,用于无损数据压缩。哈夫曼编码的关键在于构建一棵特殊的二叉树——哈夫曼树(Huffman Tree),其中每个叶子节点代表一个输入符号,而节点的权重与输入符号...

    毕业设计——流媒体视频直播服务器(Java + MySQL + FFmpeg + RTSP + RTP).zip

    **Java**: Java是一种广泛使用的面向对象的编程语言,以其“一次编写,到处运行”的特性闻名。在这个项目中,Java被用来作为服务器端的主要开发语言,处理客户端请求,实现直播流的接收、处理和分发。Java的多线程...

    编码——隐匿在计算机软硬件背后的语言上.pdf

    理解数据结构是编写高效算法的关键。 在软硬件交互层面,输入输出(I/O)编码同样重要。比如,键盘和显示器的编码协议,决定了我们如何通过键盘输入信息,以及计算机如何将处理结果呈现在屏幕上。此外,网络通信中的...

    安卓Android源码——FFMpeg.rar

    使用硬件加速可以提升解码速度,但需要检查设备是否支持相应的编码器或解码器。 这个压缩包中的源码示例可能涵盖了如何在 Android 上构建 FFmpeg,如何初始化和使用 FFmpeg API,以及如何处理 Android 中特有的问题...

    安卓Andriod源码——基于SDL、FFmpeg的播放器源码.zip

    1. **项目结构**:通常会有一个Android Studio项目结构,包括AndroidManifest.xml文件,以及src目录下的Java代码和资源文件。 2. **SDL集成**:源码会包含如何在Android项目中集成SDL库,可能有jniLibs目录存放SDL...

    安卓Android源码——手机实时视频监控项目.zip

    2. **PC端源码**:这表明项目不仅限于Android设备,还可能有一个配套的PC客户端,可能是用Java或其他语言编写的,用于视频流的发送和控制。这样的系统通常需要实现网络通信协议,如TCP/IP或UDP,以实现实时视频传输...

    <<项目架构那点儿事>>——工具类,你喜欢你就拿去

    类似于`Pagenation`,`PageUtil`也可能是用于处理分页的工具类,但可能会提供更通用的解决方案,如处理各种类型的分页数据结构。 10. **Query.java**: `Query`可能是用于构建数据库查询的工具类,包含构建SQL...

    2010-2014java上机真题 北大计算机应用专业(自考

    4. 数据结构与算法:2010年11月的试题要求考生编写一个找出小于给定数的所有质数的程序,这涉及到质数判断算法。2014年6月的试题要求实现3进制大整数加法,需要掌握数组表示大数以及自定义数据类型的加法运算。 5. ...

Global site tag (gtag.js) - Google Analytics