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

(十八)用JAVA编写MP3解码器——迷你播放器

阅读更多

  1.定义解码一帧的接口   ILayer123

  Layer1、Layer2和Layer3这三个类都实现了ILayer123的decodeFrame方法。

// ILayer123.java
package jmp123.decoder;

public interface ILayer123 {
	public void decodeFrame(int intFirstChannel, int intLastChannel) throws Exception;
}

 

  2.封装解码器 对帧头解码之后可以知道当前待解码的文件是采用MPEG Audio的哪一层压缩方式,根据压缩层的 不同,解码器自动初始化Layer1、Layer2和Layer3这三个类中的某一个实例。你也就明白了为什么我们把这三 个类的大部分初始化放在其构造方法内的道理了。class Decoder的decodeFrame方法完成解码和播放一帧的任务。  

//Decoder.java
package jmp123.decoder;

import jmp123.output.Audio;

public final class Decoder {
	public final static int CH_LEFT = 0;
	public final static int CH_RIGHT = 1;
	public final static int CH_BOTH = 2;
	private static int intFirstChannel, intLastChannel;
	private static int intChannels;
	private static int intOutputChannel;
	private static int intForwardMultiple;
	private ILayer123 layer123;

	public Decoder(BitStream objBS, Header objHeader) {
		intChannels = objHeader.getChannels();
		switch(objHeader.getLayer()) {
		case 1:
			layer123 = new Layer1(objBS, objHeader);
			break;
		case 2:
			layer123 = new Layer2(objBS, objHeader);
			break;
		case 3:
			layer123 = new Layer3(objBS, objHeader);
			break;
		}

		// 设置参数缺省值
		intForwardMultiple = 1;
		setOutputChannel(CH_BOTH);
	}

	public void decodeFrame() throws Exception {
		layer123.decodeFrame(intFirstChannel, intLastChannel);

		if(intChannels == 1 && intOutputChannel == CH_BOTH) {
			int i;
			byte[] buf = Synthesis.bytePCMBuf;
			for(i = 0; i < 4608; i += 4) {
				buf[i+2] = buf[i];
				buf[i+3] = buf[i+1];
			}
		}

		if(intForwardMultiple == 1)
			Audio.write(Synthesis.bytePCMBuf, 4608);
		else {
			//变速变调?
			int i, k = 0, i0, i1, N = intForwardMultiple;
			byte[] buf = Synthesis.bytePCMBuf;
			for(i = 0; i < 4608; i += N << 2) {
				i0 = (buf[1+i] << 8) | (buf[2+i] & 0xff);
				i1 = (buf[3+i] << 8) | (buf[4+i] & 0xff);
				buf[k++] = (byte)i0; buf[k++] = (byte)(i0 >>> 8);
				buf[k++] = (byte)i1; buf[k++] = (byte)(i1 >>> 8);
			}
			Audio.write(buf, k);
		}
	}

	/*
	* intWhichChannel - 输出的声道: CH_LEFT/CH_RIGHT/CH_BOTH
	*/
	public static void setOutputChannel(int intWhichChannel) {
		intOutputChannel = intWhichChannel;
		switch (intWhichChannel) {
		case CH_LEFT:
			intFirstChannel = intLastChannel = 0;
			break;
		case CH_RIGHT:
			intFirstChannel = intLastChannel = 1;
			break;
		case CH_BOTH:
		default:
			if(intChannels == 1)
				intFirstChannel = intLastChannel = 0;
			else {
				intFirstChannel = 0;
				intLastChannel = 1;
			}
			break;
		}
	}

	public static void setForwardMultiple(int intMultiple) {
		if(intMultiple < 1 || intMultiple > 8)
			intMultiple = 1;
		intForwardMultiple = intMultiple;
	}
}

 

   解码、播放一个文件的类class PlayingThread:

// PlayingThread.java
package jmp123.player;

import jmp123.decoder.BitStream;
import jmp123.decoder.Decoder;
import jmp123.decoder.Header;
import jmp123.instream.IRandomAccess;
import jmp123.instream.BuffRandAcceFile;
import jmp123.instream.BuffRandAcceURL;
import jmp123.output.Audio;

public final class PlayingThread implements Runnable {
	private Decoder objDec123;
	private Header objHeader;
	private IRandomAccess objIRA;

	public PlayingThread(String strFileName) throws Exception {
		strFileName.toLowerCase();
		if (strFileName.startsWith("http://"))
			objIRA = new BuffRandAcceURL(strFileName);
		else
			objIRA = new BuffRandAcceFile(strFileName);
		objHeader = new Header(objIRA);
	}

	public void run() {
		int frame_count = 0;
		Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
		try {
			if (objHeader.syncFrame() == false)
				return;
			objHeader.printHeaderInfo();
			objDec123 = new Decoder(new BitStream(objIRA), objHeader);
			Audio.open(objHeader.getFrequency());

			// Playing
			while (true) {
				objDec123.decodeFrame();
				if (objHeader.syncFrame() == false)
					break;
				if ((++frame_count & 0x7) == 0x7)	//每8帧(44.1kHz,时长约0.2s)更新一次
					objHeader.printState();
			}

			objHeader.printState();
			Audio.close();
		} catch (Exception e) {
			//e.printStackTrace();
		}
		objIRA.close();
	}
}

 

   MP3解码引擎(engine)的编写过程就讲解完了,作为一个完整的程序,应该把播放器代码也出来,我只 写了一个简单的命令行播放器意思意思。MP3解码的关键技术方面的资料不太容易查阅到,其中泛泛而谈的比较 多,网上盛行互相抄袭。开放源代码的程序是我们获得这些细节的一种途径。

mpg123的README文件 写道
mpg123 is fast. Any faster software player is probably based on some hacked mpg123;-)

  这话说得狠牛,MPG123是优秀的MP3解码器,它的算法的确优秀。但他们不提供技术方面的其 它文档,只从源码码琢磨解码细节是一件比较痛苦的事。我花时间把最关键的“多相合成滤波”算法 整理出来,就是前面的贴子《(十四)用JAVA编写MP3解码器——多相合成滤波》和《MP3解码之DCT(32→64)快 速算法的展开》。如果你想了解MP3解码技术细节的话,强烈建议你看看,一是向别人的堪称经典的优秀算法学 习,二是希望有一天能有这方面的爱好者提出自己的更为优秀的算法。

 

  3.迷你播放器   经过上述封装之后,写一个基于命令行的迷你播放器就是很简单的事情了。播放器通常的做法 是将解码、播放放入一个线程,把播放控制放在另一个线程。对基于命令行的播放器来说,播放时不交互, 开不开线程都可以。

 

  PlayingThread类完成了对播放一个文件的封装,实现了Runnable接口是为了便于实现以“后台”方式解码、播 放。

  class PlayingThread中的这一行:

Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);

为解码播放线程设置了更高的优先级,这对流畅解码播放是很有必要的,防止你在运行其它稍 复杂的任务时播放时断时续。

 

  命令行播放器class Player

/*
* Player.java -- JAVA MPEG 1.0/2.0/2.5 Layer I/II/III mini Player
*                简称:jmp123
* 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.player;

import jmp123.player.PlayingThread;

/*
 * class Play -- 简单的命令行播放器示例
 */
public class Player {
	public static void main(String args[]) {
		if (args.length > 0) {
			try {
				new Thread(new PlayingThread(args[0])).start();
				//new PlayingThread(args[0]).run();
			} catch (Exception e) {}
		} else
			showMsg();
	}

	//-------------------------------------------------------------------------
	private static void showMsg() {
		System.out.println(
		"jmp123 - JAVA mini MPEG 1.0/2.0/2.5 Layer I/II/III Decoder and Player\n"
		+"Copyright (C) 2010 LeiW  (Email:ly2697@sina.com)\n\n"
		+"This program is free software: you can redistribute it and/or modify\n"
		+"it under the terms of the GNU General Public License as published by\n"
		+"the Free Software Foundation, either version 3 of the License, or\n"
		+"(at your option) any later version.\n\n"
		+"This program is distributed in the hope that it will be useful,\n"
		+"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
		+"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
		+"GNU General Public License for more details.\n\n"
		+"Usage: \njava -jar jmp123.jar <mp3 FileName or URL>");
	}
}

  其main方法内的 new Thread(new PlayingThread(args[0])).start();以新开一个线程以“后台”方式解码、播放;

  其main方法内的 //new PlayingThread(args[0]).run();在主程序中解码播放。

  你可以在这两行中任选一行来实现你的解码、播放方式。如果你打算写调用本解码库的GUI播放器,需要 注意两点:(1)必须开线程以“后台”方式解码播放;(2)控制“播放/暂停”调用静态方法Audio.(true)暂停音 频输出并阻塞解码过程,调用Audio(false)继续音频输出和解码。  

  播放器中只需用一行代码就可以完成对解码、播放一个文件的调用,是不是很方便呢?

 

  需要指出的是,播放时快进的同时不回放监听的话就超简单,快进同时能够监听是一件很难实现的事,涉及这 方面技术的有许多专利算法,包括大名鼎鼎的杜比司的算法。他们的算法不得而知。由于要改变抽样的样本数 使噪声急剧增加,我尝试过几种方法消噪,结果都不理想,难道非得将时域信号转换为频域,经过滤波后再转 换回来?有简单点的折衷办法把噪声降低到可接受的范围吗?也希望能有这方面的爱好者研究研究有所发现。

 

  附件里jmp123_xxx(xxx表示日期)内包含:本程序JAR包、两个批命令播放示例、一个自述文件Readme.txt、 一个许可协议文本。如果你在试用本程序时发现bug,楼下有请。

 

  文件下载   在jmp123主页 http://jmp123.sourceforge.net/ (或 ttp://jmp123.sf.net )下载最新的程序和源 码。源码和程序的API文档已经放上去了~

 

【全文完】

 
上一篇:(十七)用JAVA编写MP3解码器——解码Layer1

分享到:
评论
27 楼 大柳树 2011-10-17  
不错的源码!顶下!
26 楼 i2534 2010-10-08  
我今天在调试gui时候发现一个bug,可能是由于Header类中的静态变量引起的.
测试代码如下:
	public static void main(String[] args) {
		try {
			new Thread(new PlayingThread("F:\\music\\Dragon Dance.mp3"))
					.start();
			Thread.sleep(10000);
			new Thread(new PlayingThread("F:\\music\\你不知道的事.mp3")).start();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

会出现第二首mp3的帧数和长度不改变,仍旧保持为第一个mp3.并且printHeaderInfo只会被调用一次.
另,请在Audio加入
public static Control getControl(Type type) {
if (objSDL != null && objSDL.isActive() && objSDL.isControlSupported(type)) {
return objSDL.getControl(type);
}
return null;
}
方法.以方便获取当前DataLine的Control
25 楼 i2534 2010-09-29  
<div class="quote_title">lfp001 写道</div>
<div class="quote_div">
<div class="quote_title">i2534 写道</div>
<div class="quote_div">我刚把播放进度显示和播放顺序的功能完成,正在做测试.<br>快进的问题确实正在考虑,打算和google播放器一样,拖动或者点击进度条快进.这样需要你那边加个从某帧开始播放的方法.当然了,如果有别的可替代方案也可以.<br>另:建议加入对网络文件的缓存.否则每次听都要重新下载一次.并开放缓存目录的配置接口.方便我这里设置.</div>
<br>随机播放定位今后在class Header中加入;<br>网络文件磁盘缓存容易让人觉得是产生的垃圾文件,可能增加一个后台下载的功能比较好。等有空的时候完善这些功能吧。这段时间比较忙,还要把有些代码检查修改一下。</div>
<p><br>加入后台下载时,请给我一个添加进度监听的接口.<br>另:mp3 tag的读取是在每个文件读取header的时候,这时候最好能在此header中保留此tag,并且tag开放get方法.<br>另:为了加入音量控制或其他控制,我需要在Audio中添加:</p>
<pre name="code" class="java">public static Control getControl(Control.Type type) {
if (objSDL != null &amp;&amp; objSDL.isActive()) {
return objSDL.getControl(type);
}
return null;
}</pre>
 
24 楼 enefry 2010-09-27  
lfp001 写道
enefry 写道
楼主..
在 http://jmp123.sourceforge.net/ 找不到源代码...
只有jar文件.
是不是需要自己反编译啊.

http://jmp123.sourceforge.net/点击“Download”有源文件下载,是以前的版本,最新的源文件还没传上去。

感谢楼主分享源代码..
不过前几天确实只下载到 jar文件...
点download就直接弹出下载.
现在可以下到源代码了
23 楼 lfp001 2010-09-27  
enefry 写道
楼主..
在 http://jmp123.sourceforge.net/ 找不到源代码...
只有jar文件.
是不是需要自己反编译啊.

http://jmp123.sourceforge.net/点击“Download”有源文件下载,是以前的版本,最新的源文件还没传上去。
22 楼 lfp001 2010-09-27  
i2534 写道
我刚把播放进度显示和播放顺序的功能完成,正在做测试.
快进的问题确实正在考虑,打算和google播放器一样,拖动或者点击进度条快进.这样需要你那边加个从某帧开始播放的方法.当然了,如果有别的可替代方案也可以.
另:建议加入对网络文件的缓存.否则每次听都要重新下载一次.并开放缓存目录的配置接口.方便我这里设置.

随机播放定位今后在class Header中加入;
网络文件磁盘缓存容易让人觉得是产生的垃圾文件,可能增加一个后台下载的功能比较好。等有空的时候完善这些功能吧。这段时间比较忙,还要把有些代码检查修改一下。
21 楼 enefry 2010-09-22  
楼主..
在 http://jmp123.sourceforge.net/ 找不到源代码...
只有jar文件.
是不是需要自己反编译啊.
20 楼 i2534 2010-09-17  
我刚把播放进度显示和播放顺序的功能完成,正在做测试.
快进的问题确实正在考虑,打算和google播放器一样,拖动或者点击进度条快进.这样需要你那边加个从某帧开始播放的方法.当然了,如果有别的可替代方案也可以.
另:建议加入对网络文件的缓存.否则每次听都要重新下载一次.并开放缓存目录的配置接口.方便我这里设置.
19 楼 i2534 2010-09-10  
lfp001 写道
i2534 写道
楼主,我今天在写外壳的时候,发现自然播放完毕会出现.以下错误:
java.lang.ArrayIndexOutOfBoundsException: 2048
at jmp123.instream.BuffRandAcceFile.read(BuffRandAcceFile.java:103)
at jmp123.decoder.Header.syncWord(Header.java:320)
at jmp123.decoder.Header.syncSearch(Header.java:267)
at jmp123.decoder.Header.syncFrame(Header.java:249)
at jmp123.shell.ShellPlayer$1.run(ShellPlayer.java:71)
网络文件的播放我还没有测试.

是这样的,是文件读完时抛出的异常。很多方法没有返回值,在开始编程的阶段,主要精力放在解码器代码的优化上了,程序的流程是用抛出异常去处理的。
等有空的时候我把代码整理下,让一些方法带回返回值,根据返回值确定程序流程。

另外,你不需要写一个处理解码播放的线程,暂停时调用Audio(true)解码线程被阻塞;调用Audio(false)该线程继续执行。

貌似我刚开始没写,直接用Audio(true)这种,但是这样只是阻塞了声音,while(true)循环会快速循环完毕.即线程没有阻塞.
本来我今天打算完成进度条控制和自动顺序播放的,结果下午和房东吵了一架.完全静不下心来写程序.
现在这个可以保存和读取播放列表.
18 楼 lfp001 2010-09-10  
i2534 写道
楼主,我今天在写外壳的时候,发现自然播放完毕会出现.以下错误:
java.lang.ArrayIndexOutOfBoundsException: 2048
at jmp123.instream.BuffRandAcceFile.read(BuffRandAcceFile.java:103)
at jmp123.decoder.Header.syncWord(Header.java:320)
at jmp123.decoder.Header.syncSearch(Header.java:267)
at jmp123.decoder.Header.syncFrame(Header.java:249)
at jmp123.shell.ShellPlayer$1.run(ShellPlayer.java:71)
网络文件的播放我还没有测试.

(1).是这样的,是文件读完时抛出的异常。很多方法没有返回值,在开始编程的阶段,主要精力放在解码器代码的优化上了,程序的流程是用抛出异常去处理的。
等有空的时候我把代码整理下,让一些方法带回返回值,根据返回值确定程序流程。

(2).GUI应该加入:快退、1--8倍速快进(可以选择是否回放监听)。功能调用很简单,直接调用静态方法。完成这些功能的方法正在编写代码和测试中,妥了就放出来~~

(3).不用写处理解码播放的线程,需要暂停时调用Audio(true)解码线程被阻塞;调用Audio(false)该线程继续执行。
17 楼 i2534 2010-09-10  
楼主,我今天在写外壳的时候,发现自然播放完毕会出现.以下错误:
java.lang.ArrayIndexOutOfBoundsException: 2048
at jmp123.instream.BuffRandAcceFile.read(BuffRandAcceFile.java:103)
at jmp123.decoder.Header.syncWord(Header.java:320)
at jmp123.decoder.Header.syncSearch(Header.java:267)
at jmp123.decoder.Header.syncFrame(Header.java:249)
at jmp123.shell.ShellPlayer$1.run(ShellPlayer.java:71)
网络文件的播放我还没有测试.
16 楼 i2534 2010-09-04  
对播放线程做了封装,感觉比较满意。发上来让大家拍砖。
思路就是对线程的阻塞和中断。在jmp那个播放线程了修改。
改了多次,实验了多次,终于有了这个版本。

线程真是不好玩,还是出现了问题。

终于遇到一次死锁了,哈哈。

解决很简单,再加一个标志死亡的锁存器,在彻底死亡之前一直等待。

这里不贴代码了,代码见压缩包吧.
15 楼 i2534 2010-09-04  
今天我又稍微改了下。现在可以添加删除分组,给分组添加删除歌曲,选择歌曲播放。但是播放顺序,保存读取分组信息这些基本功能还没有完成。我周一到周四比较忙。周五回公司全天有空。然后业余时间在找房子。住的房子马上到期,找了一个星期新房子,真TM贵。。。明抢啊。
14 楼 lfp001 2010-09-04  
i2534 写道
楼主,现在获得tag需要修改源代码了.

随便DIY 94 了,Header.java中通过解析VBR能得到VBR/ABR压缩编码的MP3文件的总帧数,CBR的还没写上去呢,自己添上去吧。
13 楼 lfp001 2010-09-04  
i2534 写道
楼主,现在获得tag需要修改源代码了.

已经修改了,应该再没的啥问题了吧。修改了几处,新的jar包已经上传到http://jmp123.sf.net/。
12 楼 glamey 2010-09-04  
东西挺好的,希望坚持下去。别中断了。
11 楼 i2534 2010-09-03  
<p>整理了下现在的源代码.先传上来,做个备份<br>本来想传到google code上的,结果被墙了.<br>运行需要导入jmp123.jar<br>果然,我们的网络是最目田的.</p>
<p> </p>
<p>new:</p>
<p>加入了添加mp3的功能</p>
<p>加入了配置文件的读取</p>
<p>现在可以播放上一个,下一个了,并且不会因此而死锁了</p>
<p> </p>
<p>todo:</p>
<p>暂时自己写了个tag读取方法显示mp3部分信息。以后会改成使用jmp中的ID3Tag显示详细信息。</p>
<p>写入分组和歌曲信息到配置文件中</p>
<p>播放顺序:循环,随机和重复</p>
<p>歌曲进度信息</p>
<p>音量管理?</p>
<p>同步歌词??</p>
<p>and so on...</p>
<p> </p>
<p> </p>
<p> </p>
10 楼 i2534 2010-09-03  
楼主,现在大概可以播放和暂停了.其他功能正在添加.我可能需要修改你的包中的代码.
界面是仿的google音乐的播放器.

如下图:


9 楼 i2534 2010-09-03  
楼主,现在获得tag需要修改源代码了.
8 楼 i2534 2010-09-03  
今天回公司了,终于有空了,我先做个简单的GUI来

相关推荐

Global site tag (gtag.js) - Google Analytics