实例解析使用Java实现基本的音频播放器的编写要点
java音频播放,因为必须依赖到本地环境,所以java在音频处理方面优势不大,或者说打从java体系开发时就没太多的考虑音频播放因素,要知道最早的java 1.1版本中,没有后来的javax.sound包,音频只能通过applet包调取……
遗憾的是,在图形程序开发中,我们的程序却又难免要使用到背景音乐、效果音等配合图像操作,哎,这实在是sun大神给我们开的一个不打不小的玩笑。万幸后来sun大神开眼,提供了javax.sound包,才解救我们于水深火热当中~
但是继之而来的问题是,在javax.sound包的使用中,如同java多媒体工具类的通病般,并没有提供十分完善的释放机制。如果我们做windows 开发,调用mediaplayer反复n次可能没也什么大碍,但在java中,如果音频程序反复运行的话,极容易出现内存累计损耗的情况,以至于最后抛出一个java.lang.outofmemoryerror,然后……程序就挂了,用户就傻了,我们就疯了……
这已经是“是可忍孰不可忍 ”的问题了,有鉴于此,所以在本人的loonframework框架开发中,二次整合了sound下的相关方法,力求以最简单的代码,做出最完善的音频控制类。在loonframework-game还没有大成的现在,先摘录一部分方法,以供各位看官——拍砖!
对应网络资源调用,在loonframework中建立了自己的uri用类,基本内容如下:
(其中streamhelper为loonframework自己的流媒体控制类,gethttpstream方法请自行替换。)
package org.loon.framework.game.net; import org.loon.framework.game.helper.streamhelper; /** *//** * <p> * title: loonframework * </p> * <p> * description:loonframework专用uri(统一资源标识符) * </p> * <p> * copyright: copyright (c) 2007 * </p> * <p> * company: loonframework * </p> * * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class uri ...{ //传输协议类型 public static final int _l_uri_http = 1; public static final int _l_uri_udp = 2; private string _uri; private int _type; /** *//** * 析构函数,用于注入uri和type * * @param uri * @param type */ public uri(string uri, int type) ...{ _uri = new string(uri); _type = type; } /** *//** * 析构函数,用于注入uri * * @param uri */ public uri(string uri) ...{ _uri = new string(uri); _type = uri._l_uri_http; } /** *//** * 返回uri所在位置资源的byte数组。 * * @return */ public byte[] getdata() ...{ if (_uri == null) ...{ return null; } return streamhelper.gethttpstream(_uri); } public string geturi() ...{ return _uri; } public int gettype() ...{ return _type; } } 在loonframework框架中,定制了一个基础的sounddata类,用以统一管理音频数据源。 package org.loon.framework.game.sound; import org.loon.framework.game.helper.streamhelper; import org.loon.framework.game.net.uri; /** *//** * <p> * title: loonframework * </p> * <p> * description:用以获得并缓存声音文件数据(更进一步内容操作请见loonframework-game框架) * </p> * <p> * copyright: copyright (c) 2007 * </p> * <p> * company: loonframework * </p> * * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class sounddata ...{ private byte[] _data; private boolean _loop; private int _type; public static final int _l_soundtype_midi = 1; public static final int _l_soundtype_wav = 2; /** *//** * 析构函数,用以注入uri,type,loop * * @param uri * @param type * @param loop */ public sounddata(uri uri, int type, boolean loop) ...{ if (uri != null) ...{ _data = uri.getdata(); } _type = type; _loop = loop; } /** *//** * 析构函数,用以注入data,type,loop * * @param data * @param type * @param loop */ public sounddata(byte[] data, int type, boolean loop) ...{ if (data != null && data.length > 0) ...{ _data = new byte[data.length]; // 直接copy byte数组 system.arraycopy(data, 0, _data, 0, _data.length); } _type = type; _loop = loop; } /** *//** * 析构函数,用以注入限定位置的resname,type,loop * @param resname * @param type * @param loop */ public sounddata(string resname, int type, boolean loop) ...{ this(streamhelper.getdatasource(resname),type,loop); } public byte[] getdata() ...{ return _data; } public boolean getloop() ...{ return _loop; } public void setloop(boolean loop) ...{ _loop = loop; } public int gettype() ...{ return _type; } }
loonframework将音频播放相关方法,封装与soundplay之中,程序员可以不必理会javax.sound内部细节,而直接调用soundplay完成相关操作。
package org.loon.framework.game.sound; import java.io.bytearrayinputstream; import javax.sound.midi.metaeventlistener; import javax.sound.midi.metamessage; import javax.sound.midi.midisystem; import javax.sound.midi.sequence; import javax.sound.midi.sequencer; import javax.sound.sampled.audiofileformat; import javax.sound.sampled.audiosystem; import javax.sound.sampled.clip; import javax.sound.sampled.dataline; import org.loon.framework.game.net.uri; /** *//** * <p> * title: loonframework * </p> * <p> * description:用以进行声音文件操作(仅为loonframework中部分方法,更详细请参见loonframework-game框架) * </p> * <p> * copyright: copyright (c) 2007 * </p> * <p> * company: loonframework * </p> * * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class soundplay implements metaeventlistener, runnable ...{ private int _sleeptime; private clip _audio; private sequencer _midi; private boolean _loop; private int _soundtype; private boolean _playing; private thread _thread = null; private boolean _isrun = false; /** *//** * 析构函数,初始化soundplay * */ public soundplay() ...{ _loop = false; _soundtype = 0; _sleeptime = 1000; _playing = false; } // 载入声音文件 public boolean load(sounddata data) ...{ reset(); if (data == null || data.getdata() == null) ...{ return false; } return init(data.getdata(), data.gettype(), data.getloop()); } /** *//** * 直接播放url文件 * * @param uri * @param ftype * @param loop * @return */ public boolean load(uri uri, int ftype, boolean loop) ...{ // 刷新数据 reset(); if (uri == null) ...{ return false; } // 获得sounddata sounddata data = new sounddata(uri, ftype, loop); if (data == null || data.getdata() == null) ...{ return false; } return init(data.getdata(), data.gettype(), data.getloop()); } /** *//** * 初始化sound相关数据 * * @param data * @param ftype * @param loop * @return */ private boolean init(byte[] data, int ftype, boolean loop) ...{ boolean result = false; bytearrayinputstream bis = null; try ...{ bis = new bytearrayinputstream(data); } catch (exception e) ...{ bis = null; } if (bis == null) ...{ return false; } // 判断类型 switch (ftype) ...{ // midi case sounddata._l_soundtype_midi: // 当midi不存在时 if (_midi == null) ...{ try ...{ // 获得sequencer _midi = midisystem.getsequencer(); _midi.open(); } catch (exception ex) ...{ _midi = null; } if (_midi != null) ...{ _midi.addmetaeventlistener(this); } } // 当midi依旧未获得时 if (_midi != null) ...{ // 重新创建sequence sequence sc = null; try ...{ sc = midisystem.getsequence(bis); } catch (exception e) ...{ sc = null; } if (sc != null) ...{ try ...{ _midi.setsequence(sc); // 获得是否循环播放 _loop = loop; // 获得是否载入 result = true; } catch (exception ee) ...{ } // 获得声音类型 _soundtype = sounddata._l_soundtype_midi; } } try ...{ bis.close(); } catch (exception ee) ...{ } break; // wav case sounddata._l_soundtype_wav: audiofileformat type = null; // 获得audio try ...{ type = audiosystem.getaudiofileformat(bis); } catch (exception e) ...{ type = null; } // 关闭流 try ...{ bis.close(); } catch (exception ex) ...{ } if (type == null) ...{ return false; } // 根据指定信息构造数据行的信息对象 dataline.info di = new dataline.info(clip.class, type.getformat()); // 转为clip try ...{ _audio = (clip) audiosystem.getline(di); } catch (exception e) ...{ } // 播放文件 try ...{ _audio.open(type.getformat(), data, 0, data.length); _loop = loop; result = true; } catch (exception e) ...{ } // 获得文件类型 _soundtype = sounddata._l_soundtype_wav; break; } return result; } public boolean play(sounddata data) ...{ if (!load(data)) ...{ return false; } return play(); } public boolean play() ...{ switch (_soundtype) ...{ case sounddata._l_soundtype_midi: try ...{ _midi.start(); _playing = true; _soundtype = sounddata._l_soundtype_midi; } catch (exception ee) ...{ } break; case sounddata._l_soundtype_wav: if (_audio != null) ...{ if (_loop) ...{ // 设定循环 _audio.setlooppoints(0, -1); _audio.setframeposition(0); _audio.loop(clip.loop_continuously); } else ...{ // 强制设定播放位置至0 _audio.setframeposition(0); _audio.start(); } _playing = true; } break; } return _playing; } /** *//** * 自动播放,循环停止后结束。 * * @param data * @return */ public boolean autoplay(sounddata data) ...{ if (!load(data)) ...{ return false; } return autoplay(); } /** *//** * 自动播放,循环停止后结束。 * * @return */ public boolean autoplay() ...{ _isrun = true; _thread = new thread(this); _thread.start(); return _playing; } /** *//** * 停止播放 */ public void stop() ...{ if (_audio != null && _audio.isactive()) ...{ try ...{ _audio.stop(); } catch (exception e) ...{ } } if (_midi != null) ...{ _midi.stop(); } _playing = false; _isrun = false; } /** *//** * 释放数据 * */ public void reset() ...{ stop(); _loop = false; _soundtype = 0; if (_midi != null) ...{ _midi.close(); _midi = null; } if (_audio != null && _audio.isopen()) ...{ _audio.close(); _audio = null; } _isrun = false; _thread = null; } /** *//** * 设定metamessage */ public void meta(metamessage meta) ...{ // 判断是否循环播放midi if (_loop && _soundtype == sounddata._l_soundtype_midi && meta.gettype() == 47) ...{ if (_midi != null && _midi.isopen()) ...{ _midi.setmicrosecondposition(0); _midi.start(); } } } public void run() ...{ while (_isrun) ...{ play(); // 因为播放类型唯一,所以只会返回一个_playing结果,以此判定。 if (_midi != null) ...{ _playing = _midi.isrunning(); } if (_audio != null) ...{ _playing = _audio.isrunning(); } // 当播放停止 if (!_playing) ...{ // 释放 reset(); } try ...{ thread.sleep(_sleeptime); } catch (interruptedexception e) ...{ e.printstacktrace(); } } } public int getsleeptime() ...{ return _sleeptime; } /** *//** * 设定autoplay线程循环时间。 * * @param time */ public void setsleeptime(int time) ...{ _sleeptime = time; } }
这时我们需要面对的,仅是封装为实体的sounddata数据和soundplay操作,而不必和繁复的javax.sound再打交道。
调用方法如下:
package org.test; import org.loon.framework.game.helper.streamhelper; import org.loon.framework.game.net.uri; import org.loon.framework.game.sound.sounddata; import org.loon.framework.game.sound.soundplay; /** *//** * <p>title: loonframework</p> * <p>description:soundplay播放测试</p> * <p>copyright: copyright (c) 2007</p> * <p>company: loonframework</p> * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class soundplaytest ...{ static void selectplay(int ftype)...{ sounddata data=null; switch(ftype)...{ //通过loonframework下uri从网络播放音乐 case 0: data=new sounddata(new uri("http://looframework.sourceforge.net/midi/谁是大英雄.mid"),sounddata._l_soundtype_midi,false); break; //通过本地资源下音乐文件的byte[]对象播放音乐 case 1: byte[] bytes=streamhelper.getresourcedata("/midi/谁是大英雄.mid"); data=new sounddata(bytes,sounddata._l_soundtype_midi,false); break; //通过音乐文件路径播放音乐 case 2: data=new sounddata("c:/谁是大英雄.mid",sounddata._l_soundtype_midi,false); break; } soundplay play=new soundplay(); //autoplay与play方法的区别在于,autoplay播放完毕会自动停止并释放资源,play需手动中止。 //play.play(data); play.autoplay(data); } public static void main(string[]args)...{ selectplay(2); } }
更详细方法,会待loonframework-game完全公布后,再进行解释。
另:由于streamhelper关联其他loonframework中方法,暂不给出,inputstream转byte[]可用如下写法:
//is为获得的inputstream bytearrayoutputstream bytearrayoutputstream = new bytearrayoutputstream(); //用于承接byte[] byte[] arraybyte = null; try ...{ // 每次传输大小为4096 byte[] bytes = new byte[4096]; bytes = new byte[is.available()]; int read; while ((read = is.read(bytes)) >= 0) ...{ bytearrayoutputstream.write(bytes, 0, read); } arraybyte = bytearrayoutputstream.tobytearray(); } catch (ioexception e) ...{ return null; } finally ...{ try ...{ if (bytearrayoutputstream != null) ...{ bytearrayoutputstream.close(); bytearrayoutputstream = null; } if (is != null) ...{ is.close(); is = null; } } catch (ioexception e) ...{ } }
上一篇: Java 详解垃圾回收与对象生命周期