實(shí)例解析使用Java實(shí)現(xiàn)基本的音頻播放器的編寫要點(diǎn)
Java音頻播放,因?yàn)楸仨氁蕾嚨奖镜丨h(huán)境,所以JAVA在音頻處理方面優(yōu)勢(shì)不大,或者說打從Java體系開發(fā)時(shí)就沒太多的考慮音頻播放因素,要知道最早的Java 1.1版本中,沒有后來的javax.sound包,音頻只能通過Applet包調(diào)取……
遺憾的是,在圖形程序開發(fā)中,我們的程序卻又難免要使用到背景音樂、效果音等配合圖像操作,哎,這實(shí)在是Sun大神給我們開的一個(gè)不打不小的玩笑。萬幸后來Sun大神開眼,提供了javax.sound包,才解救我們于水深火熱當(dāng)中~
但是繼之而來的問題是,在javax.sound包的使用中,如同Java多媒體工具類的通病般,并沒有提供十分完善的釋放機(jī)制。如果我們做Windows 開發(fā),調(diào)用MediaPlayer反復(fù)N次可能沒也什么大礙,但在Java中,如果音頻程序反復(fù)運(yùn)行的話,極容易出現(xiàn)內(nèi)存累計(jì)損耗的情況,以至于最后拋出一個(gè)java.lang.OutOfMemoryError,然后……程序就掛了,用戶就傻了,我們就瘋了……
這已經(jīng)是“是可忍孰不可忍 ”的問題了,有鑒于此,所以在本人的Loonframework框架開發(fā)中,二次整合了sound下的相關(guān)方法,力求以最簡單的代碼,做出最完善的音頻控制類。在Loonframework-game還沒有大成的現(xiàn)在,先摘錄一部分方法,以供各位看官——拍磚!
對(duì)應(yīng)網(wǎng)絡(luò)資源調(diào)用,在Loonframework中建立了自己的uri用類,基本內(nèi)容如下:
(其中StreamHelper為Loonframework自己的流媒體控制類,getHttpStream方法請(qǐng)自行替換。)
package org.loon.framework.game.net;
import org.loon.framework.game.helper.StreamHelper;
/** *//**
* <p>
* Title: LoonFramework
* </p>
* <p>
* Description:Loonframework專用uri(統(tǒng)一資源標(biāo)識(shí)符)
* </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框架中,定制了一個(gè)基礎(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ù)(更進(jìn)一步內(nèi)容操作請(qǐng)見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之中,程序員可以不必理會(huì)javax.sound內(nèi)部細(xì)節(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:用以進(jìn)行聲音文件操作(僅為Loonframework中部分方法,更詳細(xì)請(qǐng)參見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:
// 當(dāng)MIDI不存在時(shí)
if (_midi == null) ...{
try ...{
// 獲得Sequencer
_midi = MidiSystem.getSequencer();
_midi.open();
} catch (Exception ex) ...{
_midi = null;
}
if (_midi != null) ...{
_midi.addMetaEventListener(this);
}
}
// 當(dāng)MIDI依舊未獲得時(shí)
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ù)行的信息對(duì)象
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 ...{
// 強(qiáng)制設(shè)定播放位置至0
_audio.setFramePosition(0);
_audio.start();
}
_playing = true;
}
break;
}
return _playing;
}
/** *//**
* 自動(dòng)播放,循環(huán)停止后結(jié)束。
*
* @param data
* @return
*/
public boolean AutoPlay(SoundData data) ...{
if (!load(data)) ...{
return false;
}
return AutoPlay();
}
/** *//**
* 自動(dòng)播放,循環(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();
// 因?yàn)椴シ蓬愋臀ㄒ唬灾粫?huì)返回一個(gè)_playing結(jié)果,以此判定。
if (_midi != null) ...{
_playing = _midi.isRunning();
}
if (_audio != null) ...{
_playing = _audio.isRunning();
}
// 當(dāng)播放停止
if (!_playing) ...{
// 釋放
reset();
}
try ...{
Thread.sleep(_sleepTime);
} catch (InterruptedException e) ...{
e.printStackTrace();
}
}
}
public int getSleepTime() ...{
return _sleepTime;
}
/** *//**
* 設(shè)定AutoPlay線程循環(huán)時(shí)間。
*
* @param time
*/
public void setSleepTime(int time) ...{
_sleepTime = time;
}
}
這時(shí)我們需要面對(duì)的,僅是封裝為實(shí)體的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[]對(duì)象播放音樂
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播放完畢會(huì)自動(dòng)停止并釋放資源,play需手動(dòng)中止。
//play.play(data);
play.AutoPlay(data);
}
public static void main(String[]args)...{
selectPlay(2);
}
}
更詳細(xì)方法,會(huì)待Loonframework-game完全公布后,再進(jìn)行解釋。
另:由于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將一個(gè)目錄下的所有數(shù)據(jù)復(fù)制到另一個(gè)目錄下
這篇文章主要為大家詳細(xì)介紹了java將一個(gè)目錄下的所有數(shù)據(jù)復(fù)制到另一個(gè)目錄下,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
SpringBoot實(shí)現(xiàn)接口防刷的兩種方法
接口被刷指的是同一接口被頻繁調(diào)用,可能是由于以下原因?qū)е拢簮阂夤艉驼`操作或程序錯(cuò)誤,本文給大家介紹了SpringBoot實(shí)現(xiàn)接口防刷的兩種方法,并有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2024-06-06
基于JTable的列寬與內(nèi)容自適應(yīng)的實(shí)現(xiàn)方法
本篇文章是對(duì)JTable的列寬與內(nèi)容自適應(yīng)的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Java NIO三大組件與ByteBuffer深入理解及使用
這篇文章主要介紹了Java NIO三大組件與ByteBuffer,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01
IDEA的默認(rèn)快捷鍵設(shè)置與Eclipse的常用快捷鍵的設(shè)置方法
這篇文章主要介紹了IDEA的默認(rèn)快捷鍵設(shè)置與Eclipse的常用快捷鍵的設(shè)置方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
Java實(shí)現(xiàn)直接插入排序和折半插入排序算法示例
這篇文章主要介紹了Java實(shí)現(xiàn)直接插入排序和折半插入排序算法示例,文中對(duì)算法的思想和時(shí)間復(fù)雜度都有簡單的講解,需要的朋友可以參考下2016-04-04

