JOAL學習筆記 第五課 多聲源共享緩衝區
阿新 • • 發佈:2019-01-06
JOAL學習筆記
先是例行的連續內碼表
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.jogamp.openal.AL; import com.jogamp.openal.ALC; import com.jogamp.openal.ALCcontext; import com.jogamp.openal.ALCdevice; import com.jogamp.openal.ALException; import com.jogamp.openal.ALFactory; import com.jogamp.openal.util.ALut; public class SourceSharingBuffers { static ALC alc; static AL al; // These index the buffers. public static final int THUNDER = 0; public static final int WATERDROP = 1; public static final int STREAM = 2; public static final int RAIN = 3; public static final int CHIMES = 4; public static final int OCEAN = 5; public static final int NUM_BUFFERS = 6; // Buffers hold sound data. static int[] buffers = new int[NUM_BUFFERS]; // A list of sources for multiple emissions. static List<Integer> sources = new ArrayList<>(); // Position of the source sounds. static float[] sourcePos = { 0.0f, 0.0f, 0.0f }; // Velocity of the source sounds. static float[] sourceVel = { 0.0f, 0.0f, 0.0f }; // Position of the listener. static float[] listenerPos = { 0.0f, 0.0f, 0.0f }; // Velocity of the listener. static float[] listenerVel = { 0.0f, 0.0f, 0.0f }; // Orientation of the listener. (first 3 elements are "at", second 3 are // "up") static float[] listenerOri = { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f }; static int initOpenAL() { al = ALFactory.getAL(); alc = ALFactory.getALC(); ALCdevice device; ALCcontext context; String deviceSpecifier; String deviceName = "DirectSound3D"; // You may choose to open a // specific OpenAL device if you // know its name. deviceName = null; // Passing a null String to alcOpenDevice will open // the default device on your system! // Get handle to device. device = alc.alcOpenDevice(deviceName); // Get the device specifier. deviceSpecifier = alc.alcGetString(device, ALC.ALC_DEVICE_SPECIFIER); System.out.println("Using device " + deviceSpecifier); // Create audio context. context = alc.alcCreateContext(device, null); // Set active context. alc.alcMakeContextCurrent(context); // Check for an error. if (alc.alcGetError(device) != ALC.ALC_NO_ERROR) return AL.AL_FALSE; return AL.AL_TRUE; } static void exitOpenAL() { ALCcontext curContext; ALCdevice curDevice; // Get the current context. curContext = alc.alcGetCurrentContext(); // Get the device used by that context. curDevice = alc.alcGetContextsDevice(curContext); // Reset the current context to NULL. alc.alcMakeContextCurrent(null); // Release the context and the device. alc.alcDestroyContext(curContext); alc.alcCloseDevice(curDevice); } static int loadALData() { // Variables to load into. int[] format = new int[1]; int[] size = new int[1]; ByteBuffer[] data = new ByteBuffer[1]; int[] freq = new int[1]; int[] loop = new int[1]; // Load wav data into buffers. al.alGenBuffers(NUM_BUFFERS, buffers, 0); if (al.alGetError() != AL.AL_NO_ERROR) return AL.AL_FALSE; ALut.alutLoadWAVFile("wavdata/thunder.wav", format, data, size, freq, loop); al.alBufferData(buffers[THUNDER], format[0], data[0], size[0], freq[0]); ALut.alutLoadWAVFile("wavdata/waterdrop.wav", format, data, size, freq, loop); al.alBufferData(buffers[WATERDROP], format[0], data[0], size[0], freq[0]); ALut.alutLoadWAVFile("wavdata/stream.wav", format, data, size, freq, loop); al.alBufferData(buffers[STREAM], format[0], data[0], size[0], freq[0]); ALut.alutLoadWAVFile("wavdata/rain.wav", format, data, size, freq, loop); al.alBufferData(buffers[RAIN], format[0], data[0], size[0], freq[0]); ALut.alutLoadWAVFile("wavdata/ocean.wav", format, data, size, freq, loop); al.alBufferData(buffers[OCEAN], format[0], data[0], size[0], freq[0]); ALut.alutLoadWAVFile("wavdata/chimes.wav", format, data, size, freq, loop); al.alBufferData(buffers[CHIMES], format[0], data[0], size[0], freq[0]); // Do another error check and return. if (al.alGetError() != AL.AL_NO_ERROR) return AL.AL_FALSE; return AL.AL_TRUE; } static void addSource(int type) { int[] source = new int[1]; al.alGenSources(1, source, 0); if (al.alGetError() != AL.AL_NO_ERROR) { System.err.println("Error generating audio source."); System.exit(1); } al.alSourcei(source[0], AL.AL_BUFFER, buffers[type]); al.alSourcef(source[0], AL.AL_PITCH, 1.0f); al.alSourcef(source[0], AL.AL_GAIN, 1.0f); al.alSourcefv(source[0], AL.AL_POSITION, sourcePos, 0); al.alSourcefv(source[0], AL.AL_VELOCITY, sourceVel, 0); al.alSourcei(source[0], AL.AL_LOOPING, AL.AL_TRUE); al.alSourcePlay(source[0]); sources.add(new Integer(source[0])); } static void setListenerValues() { al.alListenerfv(AL.AL_POSITION, listenerPos, 0); al.alListenerfv(AL.AL_VELOCITY, listenerVel, 0); al.alListenerfv(AL.AL_ORIENTATION, listenerOri, 0); } static void killALData() { Iterator<Integer> iter = sources.iterator(); while (iter.hasNext()) { al.alDeleteSources(1, new int[] { ((Integer) iter.next()).intValue() }, 0); } sources.clear(); al.alDeleteBuffers(NUM_BUFFERS, buffers, 0); exitOpenAL(); } public static void main(String[] args) { try { initOpenAL(); } catch (ALException e) { e.printStackTrace(); System.exit(1); } if (loadALData() == AL.AL_FALSE) System.exit(1); setListenerValues(); char[] c = new char[1]; while (c[0] != 'q') { try { BufferedReader buf = new BufferedReader(new InputStreamReader( System.in)); System.out.println("Press a key and hit ENTER: \n" + "\t'w' for Water Drop\n" + "\t't' for Thunder\n" + "\t's' for Stream\n" + "\t'r' for Rain\n" + "\t'o' for Ocean\n" + "\t'c' for Chimes\n" + "\n'q' to Quit\n"); buf.read(c); switch (c[0]) { case 'w': addSource(WATERDROP); break; case 't': addSource(THUNDER); break; case 's': addSource(STREAM); break; case 'r': addSource(RAIN); break; case 'o': addSource(OCEAN); break; case 'c': addSource(CHIMES); break; } } catch (IOException e) { System.exit(1); } } killALData(); } // main }
之後是一些值得注意的問題
首先,本節課有較多音訊,如果不想自己準備,可以在這裡下載到原版:
不知道讀者怎麼想,這次的程式看起來就像某種機翻,如果C語言轉Java能機翻的話……,可能使用的JDK非常老吧(不支援自動裝拆箱、泛型類、foreach等),現在值得改進的地方很多,例如載入過程可以抽象為一個函式,我們的聲源與緩衝區可以抽象為Java類等等,這類簡單的不再細說。
源程式的大致思想是這樣的,首先建立了有限個緩衝區,之後封裝了一個addSource函式,當音訊播放請求到來時,建立一個聲源,把它加入線性表並讓之播放。
當程式退出時,終止上下文並刪除表中的全部聲源。
假如我們的音訊不需要迴圈(遊戲中大部分音效都是這樣的),播放完一次的聲源就不再利用了,每次對播放請求建立新的聲源的確有點浪費。
這裡考慮一種多例模式的改進,我們把聲源作為一種資源,建立一個固定數量的聲源集合,當播放請求到來時,從該集合中找出一個當前可用的聲源來為播放提供服務,這與之實現粒子特效的思想相同(見http://blog.csdn.net/shuzhe66/article/details/39523555)。
首先給出管理集合的工具類:
它容納所有的個體,並提供一個方法返回一個集合內可用的個體。import java.util.HashSet; import java.util.Set; import java.util.concurrent.Semaphore; public class CrowdController<T extends Individual> { Semaphore ListSph = new Semaphore(1); Semaphore runSph = new Semaphore(1); Set<T> indList = new HashSet<T>(); Set<T> runSet = new HashSet<T>(); public int countAvailible() { int res = 0; ListSph.acquireUninterruptibly(); for (Individual ind : indList) { if (ind.isAvalible()) { res++; } } ListSph.release(); return res; } public int countUnAvailible() { int res = 0; ListSph.acquireUninterruptibly(); for (Individual ind : indList) { if (!ind.isAvalible()) { res++; } } ListSph.release(); return res; } public T getUnAvailible() { T result = null; ListSph.acquireUninterruptibly(); for (T ind : indList) { if (!ind.isAvalible()) { result = ind; break; } } ListSph.release(); if (result == null) { // System.out.println(this.toString()+" Report: not enough individuals!"+" TYPES:"+indList.iterator().next().getClass().toString()); } return result; } public T getAvailible() { T result = null; ListSph.acquireUninterruptibly(); for (T ind : indList) { if (ind.isAvalible()) { result = ind; break; } } ListSph.release(); if (result == null) { // System.out.println(this.toString()+" Report: not enough individuals!"); } return result; } public void addIndividual(T ind) { ListSph.acquireUninterruptibly(); indList.add(ind); ListSph.release(); } public void destroyAllInd() { ListSph.acquireUninterruptibly(); for (Individual ind : indList) { ind.destroy(); } ListSph.release(); } public void finishAllInd(int src) { ListSph.acquireUninterruptibly(); for (Individual ind : indList) { ind.finish(src); } ListSph.release(); } public void finishAllInd() { ListSph.acquireUninterruptibly(); for (Individual ind : indList) { ind.finish(0); } ListSph.release(); } }
之後是個體這個資源的介面:
public interface Individual
{
public boolean isAvalible();
public void getUse(Object[] ARGS,float... FARGS);
public void finish(int src);
public void destroy();
}
任何作為資源的個體必須實現該介面,它包含了一個返回資源是否可用的方法。接著是作為資源出現的聲源Java類,對聲源控制代碼包裝一下,並實現Individual介面。
public class SoundSource implements Individual{
private int sourceId = -1;
private int[] contenier = new int[1];
private int[] state = new int[1];
AL al;
public SoundSource(AL al){
this.al = al;
al.alGenSources(1, contenier,0);
sourceId = contenier[0];
al.alSourcef(sourceId, AL.AL_PITCH, 1.0f);
al.alSourcef(sourceId, AL.AL_GAIN, 1.0f);
}
@Override
public boolean isAvalible() {
al.alGetSourcei(sourceId, AL.AL_SOURCE_STATE, state, 0);//這裡使用了第三課中的內容,它返回一個聲源的播放狀態
return state[0] != AL.AL_PLAYING;
}
@Override
public void getUse(Object[] ARGS, float... FARGS) {
if(checkArg(ARGS)){
int bufferId = (Integer)(ARGS[0]);
float[] position = (float[])ARGS[1];
float[] velocity = (float[])ARGS[2];
int loop = (Integer)ARGS[3];
al.alSourceStop(sourceId);
al.alSourcei(sourceId, AL.AL_BUFFER, bufferId);
al.alSourcefv(sourceId, AL.AL_POSITION, position, 0);
al.alSourcefv(sourceId, AL.AL_VELOCITY, velocity, 0);
al.alSourcei(sourceId, AL.AL_LOOPING, loop);
al.alSourcePlay(sourceId);
}
}
private boolean checkArg(Object[] ARGS){
if(ARGS[0] instanceof Integer){
if(ARGS[1] instanceof float[]){
if(ARGS[2] instanceof float[]){
if(ARGS[3] instanceof Integer){
return true;
}
}
}
}
return false;
}
@Override
public void finish(int src) {
al.alSourceStop(sourceId);
}
@Override
public void destroy() {
finish(0);
al.alDeleteSources(1,contenier,0);
}
}
之後是一個可用於播放的包裝類,它將CrowdController作為其成員。
public class SourceCrowd {
CrowdController<SoundSource> crowdCt;
public SourceCrowd(AL al,int maxSoundSource){
crowdCt = new CrowdController<>();
for(int i = 0;i < maxSoundSource;i++){
crowdCt.addIndividual(new SoundSource(al));
}
}
public synchronized boolean playSound(int bufferId,float[] posi,float velo[],int loop){
SoundSource ss = crowdCt.getAvailible();
if(ss != null){
ss.getUse(new Object[]{bufferId,posi,velo,loop});
return true;
}
else{
return false;
}
}
public void destroyAll(){
crowdCt.destroyAllInd();
}
}
其構造方法指明瞭集合數量,本例中是指最大的音訊併發數量。而且提供了播放與銷燬的方法。
最後是對例項程式的改進:
public class SourceSharingBuffersChanged {
static ALC alc;
static AL al;
// These index the buffers.
public static final int THUNDER = 0;
public static final int WATERDROP = 1;
public static final int STREAM = 2;
public static final int RAIN = 3;
public static final int CHIMES = 4;
public static final int OCEAN = 5;
public static final int NUM_BUFFERS = 6;
// Buffers hold sound data.
static int[] buffers = new int[NUM_BUFFERS];
// A list of sources for multiple emissions.
static List<Integer> sources = new ArrayList<>();
// Position of the source sounds.
static float[] sourcePos = { 0.0f, 0.0f, 0.0f };
// Velocity of the source sounds.
static float[] sourceVel = { 0.0f, 0.0f, 0.0f };
// Position of the listener.
static float[] listenerPos = { 0.0f, 0.0f, 0.0f };
// Velocity of the listener.
static float[] listenerVel = { 0.0f, 0.0f, 0.0f };
// Orientation of the listener. (first 3 elements are "at", second 3 are
// "up")
static float[] listenerOri = { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f };
static SourceCrowd cc;
static int initOpenAL() {
al = ALFactory.getAL();
alc = ALFactory.getALC();
ALCdevice device;
ALCcontext context;
String deviceSpecifier;
String deviceName = "DirectSound3D"; // You may choose to open a
// specific OpenAL device if you
// know its name.
deviceName = null; // Passing a null String to alcOpenDevice will open
// the default device on your system!
// Get handle to device.
device = alc.alcOpenDevice(deviceName);
// Get the device specifier.
deviceSpecifier = alc.alcGetString(device, ALC.ALC_DEVICE_SPECIFIER);
System.out.println("Using device " + deviceSpecifier);
// Create audio context.
context = alc.alcCreateContext(device, null);
// Set active context.
alc.alcMakeContextCurrent(context);
cc = new SourceCrowd(al,10);//注意這裡!我們建立了一個擁有10個聲源資源群體
// Check for an error.
if (alc.alcGetError(device) != ALC.ALC_NO_ERROR)
return AL.AL_FALSE;
return AL.AL_TRUE;
}
static void exitOpenAL() {
ALCcontext curContext;
ALCdevice curDevice;
// Get the current context.
curContext = alc.alcGetCurrentContext();
// Get the device used by that context.
curDevice = alc.alcGetContextsDevice(curContext);
// Reset the current context to NULL.
alc.alcMakeContextCurrent(null);
// Release the context and the device.
alc.alcDestroyContext(curContext);
alc.alcCloseDevice(curDevice);
}
static int loadALData() {
// Variables to load into.
int[] format = new int[1];
int[] size = new int[1];
ByteBuffer[] data = new ByteBuffer[1];
int[] freq = new int[1];
int[] loop = new int[1];
// Load wav data into buffers.
al.alGenBuffers(NUM_BUFFERS, buffers, 0);
if (al.alGetError() != AL.AL_NO_ERROR)
return AL.AL_FALSE;
ALut.alutLoadWAVFile("wavdata/thunder.wav", format, data, size, freq,
loop);
al.alBufferData(buffers[THUNDER], format[0], data[0], size[0], freq[0]);
ALut.alutLoadWAVFile("wavdata/waterdrop.wav", format, data, size, freq,
loop);
al.alBufferData(buffers[WATERDROP], format[0], data[0], size[0],
freq[0]);
ALut.alutLoadWAVFile("wavdata/stream.wav", format, data, size, freq,
loop);
al.alBufferData(buffers[STREAM], format[0], data[0], size[0], freq[0]);
ALut.alutLoadWAVFile("wavdata/rain.wav", format, data, size, freq, loop);
al.alBufferData(buffers[RAIN], format[0], data[0], size[0], freq[0]);
ALut.alutLoadWAVFile("wavdata/war.wav", format, data, size, freq,
loop);
al.alBufferData(buffers[OCEAN], format[0], data[0], size[0], freq[0]);
ALut.alutLoadWAVFile("wavdata/chimes.wav", format, data, size, freq,
loop);
al.alBufferData(buffers[CHIMES], format[0], data[0], size[0], freq[0]);
// Do another error check and return.
if (al.alGetError() != AL.AL_NO_ERROR)
return AL.AL_FALSE;
return AL.AL_TRUE;
}
static void addSource(int type){
cc.playSound(buffers[type], sourcePos,sourceVel,AL.AL_FALSE);//這裡是改動最大的地方
}
static void setListenerValues() {
al.alListenerfv(AL.AL_POSITION, listenerPos, 0);
al.alListenerfv(AL.AL_VELOCITY, listenerVel, 0);
al.alListenerfv(AL.AL_ORIENTATION, listenerOri, 0);
}
static void killALData() {
cc.destroyAll();//不要忘記呼叫銷燬方法
al.alDeleteBuffers(NUM_BUFFERS, buffers, 0);
exitOpenAL();
}
public static void main(String[] args) {
try {
initOpenAL();
} catch (ALException e) {
e.printStackTrace();
System.exit(1);
}
if (loadALData() == AL.AL_FALSE)
System.exit(1);
setListenerValues();
char[] c = new char[1];
while (c[0] != 'q') {
try {
BufferedReader buf = new BufferedReader(new InputStreamReader(
System.in));
System.out.println("Press a key and hit ENTER: \n"
+ "\t'w' for Water Drop\n" + "\t't' for Thunder\n"
+ "\t's' for Stream\n" + "\t'r' for Rain\n"
+ "\t'o' for Ocean\n" + "\t'c' for Chimes\n"
+ "\n'q' to Quit\n");
buf.read(c);
switch (c[0]) {
case 'w':
addSource(WATERDROP);
break;
case 't':
addSource(THUNDER);
break;
case 's':
addSource(STREAM);
break;
case 'r':
addSource(RAIN);
break;
case 'o':
addSource(OCEAN);
break;
case 'c':
addSource(CHIMES);
break;
}
} catch (IOException e) {
System.exit(1);
}
}
killALData();
} // main
}
這樣程式中的聲源便作為資源可以重複利用了。