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

(二)用JAVA编写MP3解码器——帧头信息解码

阅读更多

1.解析帧头   帧头共4字节,从高位到低位这32比特的含义如下:

比特数 名称 内容
11 sync 0x7FF
2 version 1=mpeg1.0, 0=mpeg2.0
2 lay 4-lay = layerI, II or III
1 error protection 0=yes, 1=no
4 bitrate_index 见下文
2 sampling_freq 见下文
1 padding 填充位
1 extension 见下文
2 mode 见下文
2 mode_ext 联合立体声(joint stereo)模式
1 copyright 0=no 1=yes
1 original 0=no 1=yes
2 emphasis 预加重

  Header.parseHeader(int)方法中的这几行依次解码上面的各个变量:

intVersionID = (h >> 19) & 3;
intLayer = 4 - (h >> 17) & 3;
intProtectionBit = (h >> 16) & 0x1;
intBitrateIndex = (h >> 12) & 0xF;
intSamplingFrequency = (h >> 10) & 3;
intPaddingBit = (h >> 9) & 0x1;
intMode = (h >> 6) & 3;
intModeExtension = (h >> 4) & 3;

各变量的含义如下:

version  MPEG的版本,本程序支持MPEG 1.0/2.0/2.5,从MPEG 2.0开始支持32Kbps以下的低位率。

 

lay  MPEG Audio的压缩分为I、II、III共3层,Layer III的解码过程最为复杂。

 

error protection  设置为0表示有32位的循环冗余校检(CRC)。

 

bitrate_index   主数据的位率(单位KBits/s),例如对192Kbps的MP3,解码时每秒读取192*1024/8=24576字节的码流,如果你是从网络在线播放要确保每秒下载192/8=24KBytes以上才能流畅播放。

mpeg 1.0
Layer\值 1 2 3 4 5 6 7 8 9 10 11 12 13 14
layer1 32 64 96 128 160 192 224 256 288 320 352 384 416 448
layer2 32 48 56 64 80 96 112 128 160 192 224 256 320 384
layer3 32 40 48 56 64 80 96 112 128 160 192 224 256 320

 

mpeg 2.0
Layer\值 1 2 3 4 5 6 7 8 9 10 11 12 13 14
layer1 32 48 56 64 80 96 112 128 144 160 176 192 224 256
layer2 8 16 24 32 40 48 56 64 80 96 112 128 144 160
layer3 8 16 24 32 40 48 56 64 80 96 112 128 144 160


sampling_freq PCM样本的采样率,用它来初始化音频硬件以播放MP3。
mpeg1.0时其值0,1,2分别对应的采样是44100Hz,48000Hz,32000Hz
mpeg2.0时其值0,1,2分别对应的采样是22050Hz,24000Hz,16000Hz
mpeg2.5时其值0,1,2分别对应的采样是11025Hz,12000Hz,8000Hz


padding  设置为1表示有1字节的填充位,相应帧的长度增加1字节。

 

mode 声道模式,其值表示的含义:
0  立体声(stereo)
1  联合立体声(joint stereo)
2  双声道(dual channel)
3  单声道(single channel)


联合立体声(joint stereo) 采用联合立体声编码方式的两个声道具有关联性。例如MS_stereo将两个声道相加、相差后处理,相减后去掉了左右声道相同的成份,后续的压缩可得到更高的压缩率。


extension 其值表示采用哪种联合立体声方式

extension intensity_stereo ms_stereo
00 off off
01 on of
10 of on
11 on on


  帧头信息解码除解码上述信息外,还要进行帧同步、计算帧长、计算帧边信息长度等供后续解码。

  2. 帧同步   (1)帧头的4字节中高11位全部设置为1(11111111 111xxxxx xxxxxxxx xxxxxxxx),用它作为查找帧的重要依据。(2)考虑到MP3文件可能有的数据帧有损坏,帧同步时还要用version、lay、bitrate_index、sampling_freq的值是否合法去检验;(3)每一帧的 version、lay、sampling_freq保持不变,把已经解码的帧的这些变量保存起来,用以与下一帧这些变量的值比较; (4)根据当前帧的帧长,移到下一帧去解析下一帧的帧头来确定当前的4字节是否是有效的帧头。如源代码Header.syncFrame()方法中的这些行进行帧同步:

iraInput.read(b4, 0, 4);
h = makeInt32(b4, 0);
while(!bfind) {
	// 1.查找帧同步字
	while((h & intStandardMask) != intStandardMask
		|| ((h >> 19) & 3) == 1		// version ID:  01 - reserved
		|| ((h >> 17) & 3) == 0		// Layer index: 00 - reserved
		|| ((h >> 12) & 0xf) == 0xf	// Bitrate Index: 1111 - reserved
		|| ((h >> 12) & 0xf) == 0	// Bitrate Index: 0000 - free
		|| ((h >> 10) & 3) == 3)	// Sampling Rate Index: 11 - reserved
	{
		//...
	}
//...

// 2.与下一帧的同步头比较
	cur_mask = 0xffe00000;		//syncword
	cur_mask |= h & 0x180000;	//intVersionID
	cur_mask |= h & 0x60000;	//intLayer
	cur_mask |= h & 0x60000;	//intSamplingFrequency

	if(iraInput.dump(intFrameSize-4, b4, 0, 4) < 4)
		return false;
	i = makeInt32(b4, 0);
	bfind = (i & cur_mask) == cur_mask && ((i >> 19) & 3) != 1
		&& ((i >> 17) & 3) != 0 && ((i >> 12) & 15) != 15
		&& ((i >> 12) & 0xf) != 0 && ((i >> 10) & 3) != 3;
//...
 

  3.计算帧长  一帧的长度应该用槽(slot)来描述,MPEG 1.0/2.0/2.5 对声音的3种压缩方式Layer1、Layer2和Layer3,每种压缩方式一帧的槽数是固定的,Layer1 一槽就是4个字节, Layer2和Layer3一槽就是一个字节,据此可以计算出帧的字节数;

 

  4.计算帧边信息长度  根据MP3帧头解码出的表示立体声编码模式(mode)、MPEG的版本(version)、压缩层(lay)套公式计算。

 

  5.解析VBR信息  见Header.parseVBR()方法,其中各个变量在其官方文档中有详细说明。如果你想了解细节,请查阅其官方文档。

 

Header.javar完整的源码如下:

/*
* Header.java -- MPEG 1.0/2.0/2.5 Audio Layer I/II/III 帧同步和帧头信息解码
* 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.decoder;

import jmp123.instream.IRandomAccess;

public final class Header {
	public static final int MPEG1 = 3;
	public static final int MPEG2 = 2;
	public static final int MPEG25 = 0;
	public static final int MAX_FRAMESIZE = 1732;	//MPEG 1.0/2.0/2.5, Lay 1/2/3

	/*
	 * intBitrateTable[intLSF][intLayer-1][intBitrateIndex]
	 */
	private static final int[][][] intBitrateTable = {
		{
			//MPEG 1
			//Layer I
			{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448},
			//Layer II
			{0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384},
			//Layer III
			{0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320}
		},
		{
			//MPEG 2.0/2.5
			//Layer I
			{0,32,48,56,64,80,96,112,128,144,160,176,192,224,256},
			//Layer II
			{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160},
			//Layer III
			{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160}
		}
	};
	/*
	 * intSamplingRateTable[intVersionID][intSamplingFrequency]
	 */
	private static final int[][] intSamplingRateTable = {
		{11025 , 12000 , 8000,0},	//MPEG Version 2.5
		{0,0,0,0,},					//reserved
		{22050, 24000, 16000 ,0},	//MPEG Version 2 (ISO/IEC 13818-3)
		{44100, 48000, 32000,0}		//MPEG Version 1 (ISO/IEC 11172-3)
	};

	/*
	 * intVersionID: 2 bits
	 * "00"  MPEG Version 2.5 (unofficial extension of MPEG 2);
	 * "01"  reserved;
	 * "10"  MPEG Version 2 (ISO/IEC 13818-3);
	 * "11"  MPEG Version 1 (ISO/IEC 11172-3).
	 */
	private static int intVersionID;

	/*
	 * intLayer: 2 bits
	 * "11"	 Layer I
	 * "10"	 Layer II
	 * "01"	 Layer III
	 * "00"	 reserved
	 * 已换算intLayer=4-intLayer: 1-Layer I; 2-Layer II; 3-Layer III; 4-reserved
	 */
	private static int intLayer;

	/*
	 * intProtectionBit: 1 bit
	 * "1"  no CRC;
	 * "0"  protected by 16 bit CRC following header.
	 */
	private static int intProtectionBit;

	/* 
	 * intBitrateIndex: 4 bits
	 */
	private static int intBitrateIndex;

	/*
	 * intSamplingFrequency: 2 bits
	 * '00'	 44.1kHz
	 * '01'	 48kHz
	 * '10'	 32kHz
	 * '11'  reserved
	 */
	private static int intSamplingFrequency;

	private static int intPaddingBit;

	/*
	 * intMode: 2 bits
	 * '00'  Stereo;
	 * '01'  Joint Stereo (Stereo);
	 * '10'  Dual channel (Two mono channels);
	 * '11'  Single channel (Mono).
	 */
	private static int intMode;

	/*
	 * intModeExtension: 2 bits
	 * 		 intensity_stereo	boolMS_Stereo
	 * '00'	 off				off
	 * '01'	 on					off
	 * '10'	 off				on
	 * '11'	 on					on
	 */
	private static int intModeExtension;

	private static int intFrameSize;
	private static int intMainDataBytes;	//main_data length
	private static int intSideInfoSize;		//side_information length
	private static int intLSF;
	private static int intStandardMask = 0xffe00000;
	private static boolean boolMS_Stereo, boolIntensityStereo;
	private static IRandomAccess iraInput;

	public Header(IRandomAccess in_rai) {
		iraInput = in_rai;
	}

	private void parseHeader(int h) {
		intVersionID = (h >> 19) & 3;
		intLayer = 4 - (h >> 17) & 3;
		intProtectionBit = (h >> 16) & 0x1;
		intBitrateIndex = (h >> 12) & 0xF;
		intSamplingFrequency = (h >> 10) & 3;
		intPaddingBit = (h >> 9) & 0x1;
		intMode = (h >> 6) & 3;
		intModeExtension = (h >> 4) & 3;

		boolMS_Stereo = intMode == 1 && (intModeExtension & 2) != 0;
		boolIntensityStereo = intMode == 1 && (intModeExtension & 0x1) != 0;
		intLSF = (intVersionID == MPEG1) ? 0 : 1;

		switch (intLayer) {
		case 1:	
			intFrameSize  = intBitrateTable[intLSF][0][intBitrateIndex] * 12000;
			intFrameSize /= intSamplingRateTable[intVersionID][intSamplingFrequency];
			intFrameSize  = ((intFrameSize+intPaddingBit)<<2);
			break;
		case 2:
			intFrameSize  = intBitrateTable[intLSF][1][intBitrateIndex] * 144000;
			intFrameSize /= intSamplingRateTable[intVersionID][intSamplingFrequency];
			intFrameSize += intPaddingBit;
			break;
		case 3:
			intFrameSize  = intBitrateTable[intLSF][2][intBitrateIndex] * 144000;
			intFrameSize /= intSamplingRateTable[intVersionID][intSamplingFrequency]<<(intLSF);
			intFrameSize += intPaddingBit;
			//计算帧边信息长度
			if(intVersionID == MPEG1)
				intSideInfoSize = (intMode == 3) ? 17 : 32;
			else
				intSideInfoSize = (intMode == 3) ? 9 : 17;
			break;
		}

		//计算主数据长度
		intMainDataBytes = intFrameSize - 4 - intSideInfoSize;
		if(intProtectionBit == 0)
			intMainDataBytes -= 2;
	}

	private static void headerCRC() throws Exception {
		if(iraInput.read() == -1 || iraInput.read() == -1)
			throw new Exception("crc() 文件读完");
	}

	private static int makeInt32(byte[] b, int off) {
		int h = b[off] & 0xff;
		h <<= 8;
		h |= b[off + 1] & 0xff;
		h <<= 8;
		h |= b[off + 2] & 0xff;
		h <<= 8;
		h |= b[off + 3] & 0xff;
		return h;
	}

	private static int intFrameCounter;	//当前帧序号
	private static boolean boolSync;	//true:帧头的特征未改变
	private static final byte[] b4 = new byte[4];
	/*
	 * 帧同步: 查找到帧同步字后与下一帧的intVersionID等比较,确定是否找到有效的同步字.
	 */
	public boolean syncFrame()  throws Exception{
		int h, idx = 0, i, cur_mask = 0;
		iraInput.read(b4, 0, 4);
		h = (b4[0]<<24) | ((b4[1] & 0xff)<<16) | ((b4[2] & 0xff)<<8) | (b4[3] & 0xff);
		while(true) {
			// 1.查找帧同步字
			while((h & intStandardMask) != intStandardMask
				|| ((h >> 19) & 3) == 1		// version ID:  01 - reserved
				|| ((h >> 17) & 3) == 0		// Layer index: 00 - reserved
				|| ((h >> 12) & 0xf) == 0xf	// Bitrate Index: 1111 - reserved
				|| ((h >> 12) & 0xf) == 0	// Bitrate Index: 0000 - free
				|| ((h >> 10) & 3) == 3)	// Sampling Rate Index: 11 - reserved
			{
				if((i = iraInput.read()) == -1)
					return false;
				idx++;
				h = (h << 8) | i;
			}
			if (idx > 0)
				boolSync = false;

			// 2. 解析帧头
			parseHeader(h);

			//若intVersionID等帧的特征未改变(boolSync=true),不用与下一帧的同步头比较.
			if(boolSync)
				break;
			if(idx >= 0xffff) {
				System.out.println("\n搜索 64K 未发现MP3帧后放弃。");
				return false;
			}

			// 3.与下一帧的同步头比较
			cur_mask = 0xffe00000;		//syncword
			cur_mask |= h & 0x180000;	//intVersionID
			cur_mask |= h & 0x60000;	//intLayer
			cur_mask |= h & 0x60000;	//intSamplingFrequency
			//cur_mask |= h & 0xC0;		//intMode
			//intMode,intModeExtension 不是始终不变.

			if(iraInput.dump(intFrameSize-4, b4, 0, 4) < 4)
				return false;
			i = makeInt32(b4, 0);
			if( (i & cur_mask) == cur_mask && ((i >> 19) & 3) != 1
					&& ((i >> 17) & 3) != 0 && ((i >> 12) & 15) != 15
					&& ((i >> 12) & 0xf) != 0 && ((i >> 10) & 3) != 3 )
				break;
			idx++;
			h = (h << 8) | iraInput.read();
		}
		//if(idx > 0)
		//	System.out.println("frs="+intFrameCounter+",skip bytes:" + idx);

		if(boolSync == false) {
			boolSync = true;
			if(intStandardMask == 0xffe00000) {	//是第一帧...
				longFrameOffset = iraInput.getFilePointer();
				longAllFrameSize = iraInput.length() - longFrameOffset;
				longFrames = longAllFrameSize / intFrameSize;
				parseVBR();		//若有VBR tag以上3个变量将被改写
				intStandardMask = cur_mask;
				floatFrameDuration = 1152f / (getFrequency() << intLSF);
			}
		}
		if (intProtectionBit == 0)
			headerCRC();
		intFrameCounter++;
		return true;
	}

	public boolean isMSStereo() {
		return boolMS_Stereo;
	}

	public boolean isIStereo() {
		return boolIntensityStereo;
	}

	public int getBitrate() {
		return intBitrateTable[intLSF][intLayer-1][intBitrateIndex];
	}

	public int getBitrateIndex() {
		return intBitrateIndex;
	}

	public int getChannels() {
		return intMode == 3 ? 1 : 2;
	}

	public int getMode() {
		return intMode;
	}

	public int getModeExtension() {
		return intModeExtension;
	}

	public int getVersion() {
		return intVersionID;
	}

	public int getLayer() {
		return intLayer;
	}

	public int getSampleFrequency() {
		return intSamplingFrequency;
	}

	public int getFrequency() {
		return intSamplingRateTable[intVersionID][intSamplingFrequency];
	}

	public int getMainDataBytes() {
		return intMainDataBytes;
	}

	public int getSideInfoSize() {
		return intSideInfoSize;
	}

	public int getFrameSize() {
		return intFrameSize;
	}

	public int getFrameCounter() {
		return intFrameCounter;
	}

	// MP3 文件帧数等信息
	private static long longAllFrameSize;	//帧长度总和(文件长度减去ID3 tag, APE tag 等长度)
	private static long longFrameOffset;	//第一帧的偏移量
	private static long longFrames;			//帧数
	private static float floatFrameDuration;//一帧时长(秒)
	private static String strDuration;

	public long getTrackFrames() {
		return longFrames;
	}

	/*
	 * 返回MP3文件时长(秒)
	 */
	public float getDuration() {
		return floatFrameDuration * longFrames;
	}

	/*
	* 解码存储在第一帧的VBR信息.若第一帧存储的是VBR信息,帧边信息被填充为零,不解
	* 码VBR tag而把这一帧作为音频帧不影响正常解码.
	*/
	private boolean boolVBRtag;
	private byte[] byteVBRToc;
	private int intTocNumber, intTocPer, intTocFactor;
	private String strBitRate;

	private boolean parseVBR() throws Exception {
		int iTagSize = intFrameSize - intSideInfoSize;
		if (iTagSize < 124)
			return false;
		byte[] b = new byte[iTagSize];
		iraInput.dump(0, b, 0, intSideInfoSize);
		for (int i = 2; i < intSideInfoSize; i++)	//前2字节可能是CRC_word
			if (b[i] != 0) {
				b = null;
				return false;
			}
		iraInput.dump(intSideInfoSize, b, 0, iTagSize);

		//-------------------------------VBR tag------------------------------
		int iOff = 0;
		if ((b[0] == 'X' && b[1] == 'i'	&& b[2] == 'n' && b[3] == 'g') ||
				(b[0] == 'I' && b[1] == 'n' && b[2] == 'f' && b[3] == 'o')) {
			//--------Xing/Info header--------
			boolVBRtag = true;
			longAllFrameSize -= intFrameSize;
			longFrameOffset += intFrameSize;
			int xing_flags = makeInt32(b, 4);
			iOff = 8;
			if ((xing_flags & 1) == 1) { 	// track frames
				longFrames = makeInt32(b, iOff);
				iOff += 4;
				System.out.println("track frames: " + longFrames +
						"  [" + new String(b,0,4) + "]");
			}
			if ((xing_flags & 0x2) != 0) { // track bytes
				longAllFrameSize = makeInt32(b, iOff);
				iOff += 4;
				System.out.println(" track bytes: " + longAllFrameSize);
			}
			if ((xing_flags & 0x4) != 0) { // TOC: 100 bytes.
				byteVBRToc = new byte[100];
				System.arraycopy(b, iOff, byteVBRToc, 0, 100);
				iOff += 100;
				//System.out.println("         TOC: true");
			}
			if ((xing_flags & 0x8) != 0) { // VBR quality
				int xing_quality = makeInt32(b, iOff);
				iOff += 4;
				System.out.println("     quality: " + xing_quality);
			}
			intTocNumber = 100;	//TOC共100个表项
			intTocPer = 1;		//每个表项1字节
			intTocFactor = 1;
		} else if(b[0] == 'V' && b[1] == 'B' && b[2] == 'R' && b[3] == 'I') {
			//--------VBRI header--------
			//version ID: 2 bytes
			//Delay: 2 bytes
			int vbri_quality = (b[8] & 0xff) | (b[9] & 0xff);
			System.out.println("     quality: " + vbri_quality +
					"  [" + new String(b,0,4) + "]");
			longAllFrameSize = makeInt32(b, 10);
			System.out.println(" track bytes: " + longAllFrameSize);
			longFrames = makeInt32(b, 14);
			System.out.println("track frames: " + longFrames);
			intTocNumber = (b[18] & 0xff) | (b[19] & 0xff);
			intTocFactor = (b[20] & 0xff) | (b[21] & 0xff);
			intTocPer = (b[22] & 0xff) | (b[23] & 0xff);
			//int toc_frames = (b[24] & 0xff) | (b[25] & 0xff);	//每个TOC表项的帧数
			int toc_size = intTocNumber * intTocPer;
			iOff = 26 + toc_size;
			System.out.println("         TOC: " + intTocNumber + " * " +
					intTocPer + " = " + toc_size + "factor=" + intTocFactor);
			if (intFrameSize - intSideInfoSize < iOff)
				return false;
			byteVBRToc = new byte[toc_size];
			System.arraycopy(b, 26, byteVBRToc, 0, toc_size);
		} else {
			b = null;
			return false;
		}		

		//-------------------------------LAME tag------------------------------
		//9+1+1+8+1+1+3+1+1+2+4+2+2=36 bytes
		if(iTagSize - iOff < 36 || b[iOff] == 0) {
			strBitRate = "VBR";
			b = null;
			return true;
		}
		//Encoder Version: 9 bytes
		String strEncoder = new String(b, iOff, 9);
		iOff += 9;
		System.out.println("     encoder: " + strEncoder);

		//'Info Tag' revision + VBR method: 1 byte
		//boolean isCBR=false, isABR=false, isVBR=false;
		int revi = (b[iOff] & 0xff) >> 4;	//0:rev0; 1:rev1; 15:reserved
		int lame_vbr = b[iOff++] & 0xf;		//0:unknown

		//Lowpass filter value(低通滤波上限值): 1 byte
		int lowpass = b[iOff++] & 0xff;
		System.out.println("     lowpass: " + (lowpass * 100) + "Hz" +"  [revi "+revi+"]");

		//Replay Gain(回放增益):8 bytes
		float peak = Float.intBitsToFloat(makeInt32(b, iOff));	//Peak signal amplitude
		iOff += 4;
		int radio = ((b[iOff] & 0xff) << 8) | (b[iOff+1] & 0xff);	//Radio Replay Gain
		/*
		* radio:
		* bits 0h-2h: NAME of Gain adjustment:
		*	000 = not set
		*	001 = radio
		*	010 = audiophile
		* bits 3h-5h: ORIGINATOR of Gain adjustment:
		*	000 = not set
		*	001 = set by artist
		*	010 = set by user
		*	011 = set by my model
		*	100 = set by simple RMS average
		* bit 6h: Sign bit
		* bits 7h-Fh: ABSOLUTE GAIN ADJUSTMENT.
		*  storing 10x the adjustment (to give the extra decimal place).
		*/
		iOff += 2;
		int phile = ((b[iOff] & 0xff) << 8) | (b[iOff+1] & 0xff);	//Audiophile Replay Gain
		/*
		* phile各位含义同上(radio)
		*/
		iOff += 2;
		
		//Encoding flags + ATH Type: 1 byte
		/*int enc_flag = (b[iOff] & 0xff) >> 4;
		int ath_type = b[iOff] & 0xf;
		//000?0000: LAME uses "--nspsytune" ?
		boolean nsp = ((enc_flag & 0x1) == 0) ? false : true;
		//00?00000: LAME uses "--nssafejoint" ?
		boolean nsj = ((enc_flag & 0x2) == 0) ? false : true;
		//0?000000: This track is --nogap continued in a next track ?
		//is true for all but the last track in a --nogap album 
		boolean nogap_next = ((enc_flag & 0x4) == 0) ? false : true;
		//?0000000: This track is the --nogap continuation of an earlier one ?
		//is true for all but the first track in a --nogap album
		boolean nogap_cont = ((enc_flag & 0x8) == 0) ? false : true;*/
		iOff++;

		// ABR/CBR位率或VBR的最小位率(0xFF表示位率为255Kbps以上): 1 byte
		int lame_bitrate = b[iOff++] & 0xff;
		switch (lame_vbr) {
		case 1:
		case 8: // CBR
			strBitRate = String.format("CBR %1$dK", getBitrate());
			break;
		case 2:
		case 9: // ABR
			if(lame_bitrate < 0xff)
				strBitRate = String.format("ABR %1$dK", lame_bitrate);
			else
				strBitRate = String.format("ABR %1$dK以上", lame_bitrate);
			break;
		default: // 0: unknown is VBR ?
			if(lame_bitrate == 0)	//unknown
				strBitRate = "VBR";
			else
				strBitRate = String.format("VBR %1$dK以上", lame_bitrate);
		}

		//Encoder delays: 3 bytes
		iOff += 3;

		//Misc: 1 byte
		iOff++;

		//MP3 Gain: 1 byte. 
		//任何MP3能无损放大2^(mp3_gain/4).以1.5dB为步进值改变'Replay Gain'的3个域:
		//	'Peak signal amplitude', 'Radio Replay Gain', 'Audiophile Replay Gain'
		//mp3_gain = -127..+127, 对应的:
		//	分贝值-190.5dB..+190.5dB; mp3_gain增加1, 增加1.5dB
		//	放大倍数0.000000000276883..3611622602.83833951
		int mp3_gain = b[iOff++];	//其缺省值为0
		if(mp3_gain != 0)
			System.out.println("    MP3 Gain: " + mp3_gain);

		//Preset and surround info: 2 bytes
		int preset_surround = ((b[iOff] & 0xff) << 8) | (b[iOff+1] & 0xff);
		int surround_info = (preset_surround >> 11) & 0x7;
		switch(surround_info) {
		case 0:		//no surround info
			break;
		case 1:		//DPL encoding
			System.out.println("    surround: DPL");
			break;
		case 2:		//DPL2 encoding
			System.out.println("    surround: DPL2");
			break;
		case 3:		//Ambisonic encoding
			System.out.println("    surround: Ambisonic");
			break;
		case 7:		// reserved
			System.out.println("    surround: invalid data");
			break;
		}
		preset_surround &= 0x7ff;	//11 bits: 2047 presets
		if(preset_surround != 0)	//0: unknown / no preset used
			System.out.println("    surround: preset " + preset_surround);
		iOff += 2;

		//MusicLength: 4 bytes
		//MP3文件原始的(即除去ID3 tag,APE tag等)'LAME Tag frame'和'音乐数据'的总字节数
		int music_len = makeInt32(b, iOff);
		iOff += 4;
		if(music_len != 0)
			longAllFrameSize = music_len;

		//MusicCRC: 2 bytes
		iOff += 2;

		//CRC-16 of Info Tag: 2 bytes

		b = null;
		return true;
	}

	// -------------------------------------------------------------------
	// 以下是辅助功能,删除掉源码及相关调用不影响正常播放
	// -------------------------------------------------------------------
	// 打印信息
	public void printHeaderInfo() {
		String[] sver = {"MPEG 2.5", "reserved", "MPEG 2.0", "MPEG 1.0"};
		String[] mode_str = {", Stereo",", Joint Stereo",", Dual channel",", Single channel(Mono)"};
		String[] exmode_str = {"","(I/S)","(M/S)","(I/S & M/S)"};
		if(strDuration == null) {
			float duration = getDuration();
			int m = (int)(duration / 60);
			strDuration = String.format("%1$02d:%2$02d", m, (int)(duration - m * 60 + 0.5));
			progress = new StringBuffer(">----------------------------------------");
		}
		if(!boolVBRtag)
			strBitRate = String.format("%1$dK", intBitrateTable[intLSF][intLayer-1][intBitrateIndex]);
		System.out.println("\r" + sver[intVersionID] + ", Layer " + intLayer + 
			", " + getFrequency()+"Hz, " + 
			strBitRate +
			mode_str[intMode] +
			exmode_str[intModeExtension] + ", " +
			strDuration);
	}

	private static StringBuffer progress;
	private static int progress_index = 1;

	public void printState() {
		float t = intFrameCounter * floatFrameDuration;
		int m = (int)(t / 60);
		float s = t - 60 * m;
		float percent;
		if(boolVBRtag)
			percent = (float)intFrameCounter / longFrames * 100;
		else
			percent = (float)iraInput.getFilePointer() / iraInput.length() * 100;
		int i = ((int)(percent + 0.5) << 2) / 10;
		if(i == progress_index) {
			progress.replace(i-1, i+1, "=>");
			progress_index++;
		}
		System.out.printf("\r%1$02d:%2$04.1f [%3$-41s] %4$.1f%%", m, s, progress, percent);
	}
}
 

 

上一篇:(一)用JAVA编写MP3解码器——前言

下一篇:(三)用JAVA编写MP3解码器——读取位流

 

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

分享到:
评论
16 楼 y1j2x34 2012-04-08  
如果*.mp3的文件不是标准mp3格式呢
15 楼 lfp001 2012-04-01  
fancycodevip 写道
这个描述是不正确的
官方的syncword 是 1111 1111 1111 而不是 11个 1,versionID只有一位



你看得很仔细。帧头同步字和版本共13位,iso 11172-3规定同步字12位(全为1),版本(MPEG-1或MPEG-2)占一位。为了扩展MPEG-1或MPEG-2音频流在低位率压缩时的性能,有一民间版本MPEG-2.5,为了兼容MPEG-1和MPEG-2那么帧头同步字更改为11位(全为1),用2位表示MPEG的3种版本。
14 楼 fancycodevip 2012-03-13  
xiexifeng113 写道
比如需要左移和右移的原因,最好能全点哦!如果您有时间。我想您的文章没有被评为精华,应该是注释太少吧!实现的原理也大都没讲啊!好期待您的补充!

这已经够充分了,具体还得自己看文档和代码
13 楼 fancycodevip 2012-03-04  
引用
11 sync 0x7FF
2 version 1=mpeg1.0, 0=mpeg2.0

这个描述是不正确的
官方的syncword 是 1111 1111 1111 而不是 11个 1,versionID只有一位

12 楼 poko123 2010-09-18  
几年前弄过,那时候只能检测版本1的好像,高版本就不行了。当时没你的文章,苦啊。

后来就把这事给忘记了。
11 楼 lfp001 2010-09-17  
MO_oC 写道
扫描每一帧得到最大位率应该会影响到解码效率吧,现在没有高效的办法读取VBR和ABR的位率吗?

VBR和ABR每一帧的位率不一定相同,不能一次计算出所有帧的位率。
10 楼 MO_oC 2010-09-16  
扫描每一帧得到最大位率应该会影响到解码效率吧,现在没有高效的办法读取VBR和ABR的位率吗?
9 楼 lfp001 2010-09-16  
MO_oC 写道
楼主,我看到有些程序是先解析VBR信息,如果存在VBR信息就这样
return (int)((medFrameSize * (double)getFrequency()) / (1000.0 * ((getLayerIndex()==3) ? 12.0 : 144.0)));

不存在的话就查表

这种方法计算当前帧的位率是正确的,但VBR和ABR每一帧的位率不一定相同,如果是用LAME压缩的MP3的话,通过读LAME tag可以知道其最小的位率是多大,最高位率无法知道,除非去扫描每一帧得到最大位率。
8 楼 MO_oC 2010-09-16  
楼主,我看到有些程序是先解析VBR信息,如果存在VBR信息就这样
return (int)((medFrameSize * (double)getFrequency()) / (1000.0 * ((getLayerIndex()==3) ? 12.0 : 144.0)));

不存在的话就查表
7 楼 MO_oC 2010-09-16  
<p>这是文件属性的<br><img src="http://dl.iteye.com/upload/attachment/311754/2486fc8a-be5b-3be2-88c0-4ab19269062c.png" alt=""></p>
<p>这是酷狗的<br> <br><img src="http://dl.iteye.com/upload/attachment/311756/ccb52994-4594-3d93-8aed-dd9b84a95e8d.png" alt=""></p>
<p> </p>
<p>这是你的程序的<br> <br><img src="http://dl.iteye.com/upload/attachment/311758/5e8a3f3e-ee12-32e6-b522-d662cb277fa8.png" alt=""></p>
<p> </p>
<p> </p>
<p>这是那首歌曲的下载地址:<a href="http://upwap.ru/1035149">http://upwap.ru/1035149</a><br> </p>
6 楼 lfp001 2010-09-16  
MO_oC 写道
楼主,我用你的代码测试读取一首位速为320kbps的歌曲的位速,读取出来的数据是128kbps哦

通过查表得到位率。
你用其它的播放器播放试试看,是否真的是320Kbps。
5 楼 MO_oC 2010-09-13  
楼主,我用你的代码测试读取一首位速为320kbps的歌曲的位速,读取出来的数据是128kbps哦
4 楼 sdh5724 2010-08-27  
xiexifeng113 写道
比如需要左移和右移的原因,最好能全点哦!如果您有时间。我想您的文章没有被评为精华,应该是注释太少吧!实现的原理也大都没讲啊!好期待您的补充!


解码类的代码, 基本不看编码/解码规范  注释啊, 原理啊基本是没有大的作用的。 看明白规范, 基本代码也就出来了。
3 楼 lfp001 2010-08-26  
xiexifeng113 写道
看不懂呢,大师,这么多的左移,右移等各种运算,看得人眼都花了,能不能讲以下解码和编码的原理啊!

表示帧头信息的几个变量的含义、占几比特都作了注解。
移位操作就是从帧头的4字节中取相关的那几比特出来。第一个表格解释了帧头4字节中哪几位表示什么含义。
原理后文陆续作简介。
2 楼 xiexifeng113 2010-08-25  
比如需要左移和右移的原因,最好能全点哦!如果您有时间。我想您的文章没有被评为精华,应该是注释太少吧!实现的原理也大都没讲啊!好期待您的补充!
1 楼 xiexifeng113 2010-08-25  
看不懂呢,大师,这么多的左移,右移等各种运算,看得人眼都花了,能不能讲以下解码和编码的原理啊!

相关推荐

    jmpg123.tar.gz_jmpg1_jmpg123_mp1_mp2_mp3 解码

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

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

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

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

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

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

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

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

    本篇将深入探讨如何使用Java进行逐行读取TXT文件,并提供相关示例代码。 首先,我们需要了解Java中的几个关键类,它们在文件读取过程中扮演着重要角色: 1. `File` 类:代表文件或目录的路径名。可以创建、重命名...

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

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

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

    【音乐播放器——湘潭大学JAVA课程设计题目】是湘潭大学一门JAVA课程的期末项目,旨在让学生运用所学的JAVA编程知识开发一个功能丰富的音乐播放器。这个播放器不仅包含了基本的音乐播放功能,还增加了多项高级特性,...

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

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

    java源码包3

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

    java源码包2

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

    用Java语言写的JPEG图象生成器程序

    本话题聚焦于一个特定的应用——"用Java语言写的JPEG图象生成器程序",这是一个适用于课程设计和学习管理系统开发的宝贵资源。 首先,我们要理解JPEG(Joint Photographic Experts Group)是一种广泛使用的有损图像...

    Android 使用 FFmpeg (一)——编译.so文件Demo

    - 考虑到性能和内存消耗,合理选择FFmpeg的编译选项,如是否包含所有解码器和编码器。 - 处理好异步操作,避免在主线程中执行耗时的原生代码,以免引起UI卡顿。 这个"Android 使用 FFmpeg (一)——编译.so文件...

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

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

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

    FFmpeg的JNI接口会被用来在Java层调用C/C++编写的解码器,这样可以充分利用CPU的性能并降低内存消耗。 4. **Android多媒体框架**: Android系统本身提供了一个多媒体框架,包括MediaPlayer类,它可以播放本地和网络...

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

    3. **FFmpeg**:FFmpeg是一个开源的跨平台多媒体处理工具,它包含了各种音频和视频编解码器,可以进行音视频的编码、解码、转换、封装等操作。在直播系统中,FFmpeg用于处理视频源,将其转化为适合网络传输的格式,...

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

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

    JAVA二维码jar包与例子

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

    java源码包4

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

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

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

Global site tag (gtag.js) - Google Analytics