JOAL學習筆記 第五課 多聲源共享緩衝區

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
}
這樣程序中的聲源便作爲資源可以重複利用了。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章