實例解析使用Java實現(xiàn)基本的音頻播放器的編寫要點
Java音頻播放,因為必須依賴到本地環(huán)境,所以JAVA在音頻處理方面優(yōu)勢不大,或者說打從Java體系開發(fā)時就沒太多的考慮音頻播放因素,要知道最早的Java 1.1版本中,沒有后來的javax.sound包,音頻只能通過Applet包調(diào)取……
遺憾的是,在圖形程序開發(fā)中,我們的程序卻又難免要使用到背景音樂、效果音等配合圖像操作,哎,這實在是Sun大神給我們開的一個不打不小的玩笑。萬幸后來Sun大神開眼,提供了javax.sound包,才解救我們于水深火熱當中~
但是繼之而來的問題是,在javax.sound包的使用中,如同Java多媒體工具類的通病般,并沒有提供十分完善的釋放機制。如果我們做Windows 開發(fā),調(diào)用MediaPlayer反復(fù)N次可能沒也什么大礙,但在Java中,如果音頻程序反復(fù)運行的話,極容易出現(xiàn)內(nèi)存累計損耗的情況,以至于最后拋出一個java.lang.OutOfMemoryError,然后……程序就掛了,用戶就傻了,我們就瘋了……
這已經(jīng)是“是可忍孰不可忍 ”的問題了,有鑒于此,所以在本人的Loonframework框架開發(fā)中,二次整合了sound下的相關(guān)方法,力求以最簡單的代碼,做出最完善的音頻控制類。在Loonframework-game還沒有大成的現(xiàn)在,先摘錄一部分方法,以供各位看官——拍磚!
對應(yīng)網(wǎng)絡(luò)資源調(diào)用,在Loonframework中建立了自己的uri用類,基本內(nèi)容如下:
(其中StreamHelper為Loonframework自己的流媒體控制類,getHttpStream方法請自行替換。)
package org.loon.framework.game.net; import org.loon.framework.game.helper.StreamHelper; /** *//** * <p> * Title: LoonFramework * </p> * <p> * Description:Loonframework專用uri(統(tǒng)一資源標識符) * </p> * <p> * Copyright: Copyright (c) 2007 * </p> * <p> * Company: LoonFramework * </p> * * @author chenpeng * @email:ceponline@yahoo.com.cn * @version 0.1 */ public class URI ...{ //傳輸協(xié)議類型 public static final int _L_URI_HTTP = 1; public static final int _L_URI_UDP = 2; private String _uri; private int _type; /** *//** * 析構(gòu)函數(shù),用于注入uri和type * * @param uri * @param type */ public URI(String uri, int type) ...{ _uri = new String(uri); _type = type; } /** *//** * 析構(gòu)函數(shù),用于注入uri * * @param uri */ public URI(String uri) ...{ _uri = new String(uri); _type = URI._L_URI_HTTP; } /** *//** * 返回uri所在位置資源的byte數(shù)組。 * * @return */ public byte[] getData() ...{ if (_uri == null) ...{ return null; } return StreamHelper.getHttpStream(_uri); } public String getURI() ...{ return _uri; } public int getType() ...{ return _type; } } 在Loonframework框架中,定制了一個基礎(chǔ)的SoundData類,用以統(tǒng)一管理音頻數(shù)據(jù)源。 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:用以獲得并緩存聲音文件數(shù)據(jù)(更進一步內(nèi)容操作請見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; /** *//** * 析構(gòu)函數(shù),用以注入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; } /** *//** * 析構(gòu)函數(shù),用以注入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數(shù)組 System.arraycopy(data, 0, _data, 0, _data.length); } _type = type; _loop = loop; } /** *//** * 析構(gòu)函數(shù),用以注入限定位置的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將音頻播放相關(guān)方法,封裝與SoundPlay之中,程序員可以不必理會javax.sound內(nèi)部細節(jié),而直接調(diào)用SoundPlay完成相關(guān)操作。
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; /** *//** * 析構(gòu)函數(shù),初始化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) ...{ // 刷新數(shù)據(jù) 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相關(guān)數(shù)據(jù) * * @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) ...{ // 重新創(chuàng)建Sequence Sequence sc = null; try ...{ sc = MidiSystem.getSequence(bis); } catch (Exception e) ...{ sc = null; } if (sc != null) ...{ try ...{ _midi.setSequence(sc); // 獲得是否循環(huán)播放 _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; } // 關(guān)閉流 try ...{ bis.close(); } catch (Exception ex) ...{ } if (type == null) ...{ return false; } // 根據(jù)指定信息構(gòu)造數(shù)據(jù)行的信息對象 DataLine.Info di = new DataLine.Info(Clip.class, type.getFormat()); // 轉(zhuǎn)為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) ...{ // 設(shè)定循環(huán) _audio.setLoopPoints(0, -1); _audio.setFramePosition(0); _audio.loop(Clip.LOOP_CONTINUOUSLY); } else ...{ // 強制設(shè)定播放位置至0 _audio.setFramePosition(0); _audio.start(); } _playing = true; } break; } return _playing; } /** *//** * 自動播放,循環(huán)停止后結(jié)束。 * * @param data * @return */ public boolean AutoPlay(SoundData data) ...{ if (!load(data)) ...{ return false; } return AutoPlay(); } /** *//** * 自動播放,循環(huán)停止后結(jié)束。 * * @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; } /** *//** * 釋放數(shù)據(jù) * */ 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; } /** *//** * 設(shè)定MetaMessage */ public void meta(MetaMessage meta) ...{ // 判斷是否循環(huán)播放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結(jié)果,以此判定。 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; } /** *//** * 設(shè)定AutoPlay線程循環(huán)時間。 * * @param time */ public void setSleepTime(int time) ...{ _sleepTime = time; } }
這時我們需要面對的,僅是封裝為實體的SoundData數(shù)據(jù)和SoundPlay操作,而不必和繁復(fù)的javax.sound再打交道。
調(diào)用方法如下:
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從網(wǎng)絡(luò)播放音樂 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方法的區(qū)別在于,AutoPlay播放完畢會自動停止并釋放資源,play需手動中止。 //play.play(data); play.AutoPlay(data); } public static void main(String[]args)...{ selectPlay(2); } }
更詳細方法,會待Loonframework-game完全公布后,再進行解釋。
另:由于StreamHelper關(guān)聯(lián)其他Loonframework中方法,暫不給出,inputStream轉(zhuǎn)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) ...{ } }
相關(guān)文章
java將一個目錄下的所有數(shù)據(jù)復(fù)制到另一個目錄下
這篇文章主要為大家詳細介紹了java將一個目錄下的所有數(shù)據(jù)復(fù)制到另一個目錄下,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08基于JTable的列寬與內(nèi)容自適應(yīng)的實現(xiàn)方法
本篇文章是對JTable的列寬與內(nèi)容自適應(yīng)的實現(xiàn)方法進行了詳細的分析介紹,需要的朋友參考下2013-05-05Java NIO三大組件與ByteBuffer深入理解及使用
這篇文章主要介紹了Java NIO三大組件與ByteBuffer,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-01-01IDEA的默認快捷鍵設(shè)置與Eclipse的常用快捷鍵的設(shè)置方法
這篇文章主要介紹了IDEA的默認快捷鍵設(shè)置與Eclipse的常用快捷鍵的設(shè)置方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01