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

来源:互联网 发布:上载数据拓普康全站仪 编辑:程序博客网 时间:2024/04/28 22:12
1.定义解码一帧的接口   ILayer123

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

// ILayer123.javapackage jmp123.decoder;public interface ILayer123 {public void decodeFrame(int intFirstChannel, int intLastChannel) throws Exception;}


 

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

//Decoder.javapackage 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.javapackage 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);elseobjIRA = 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());// Playingwhile (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) {}} elseshowMsg();}//-------------------------------------------------------------------------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文档已经放上去了~

 

【全文完】

 

 

0 0