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
}
之後是一些值得注意的問題
首先,本節課有較多音頻,如果不想自己準備,可以在這裏下載到原版:
http://jogamp.org/git/?p=joal-demos.git;a=tree;f=src/java/demos/data
不知道讀者怎麼想,這次的程序看起來就像某種機翻,如果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
}
這樣程序中的聲源便作爲資源可以重複利用了。