[译文]JOAL教程
原文地址:http://jogamp.org/joal-demos/www/devmaster/lesson5.html
原文作者:Athomas Goldberg
译文:三向板砖
转载请保留以上信息。
本节对应的学习笔记:http://blog.csdn.net/shuzhe66/article/details/40295895
第五课 多声源共享缓冲区
本文是DevMaster.net(http://devmaster.net/)的OpenAL教程对应的JOAL版本。C语言版原文作者为JesseMaurais
本次将会向大家展示在多个声源间共享缓冲区的方法。这个过程整体下来显得非常自然且合乎逻辑,它是如此地简单以至于你们当中的有些人已经自己掌握了。如果你是其中一位,那么你可以跳过本次教程了。但对于那些特别渴望着获得全部信息的人来讲,我当然会把这部分知识毫无保留的交给你,你会发现其中还是有很多乐趣的。顺带一提,这次我们会使用第四节课程中的内容直接实现Alc层,总之,你今后可能用得着本次实例程序。
好的,我们开始。
import java.io.*;
import java.nio.*;
import java.util.*;
import com.jogamp.openal.*;
import com.jogamp.openal.util.*;
public class SourceSharingBuffers {
static ALC alc;
static AL al;
// 缓冲区索引标记
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;
//装载声音数据的缓冲区
static int[] buffers = new int[NUM_BUFFERS];
//声源列表
static List sources = new ArrayList();
//声源位置
static float[] sourcePos = { 0.0f, 0.0f, 0.0f };
//声源速度
static float[] sourceVel = { 0.0f, 0.0f, 0.0f };
//听众位置
static float[] listenerPos = { 0.0f, 0.0f, 0.0f };
//听众速度
static float[] listenerVel = { 0.0f, 0.0f, 0.0f };
//听众朝向
static float[] listenerOri = { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f };
首先我们使用一系列静态常量来标记缓冲区对应在数组中的位置。我们使用了各种wav文件源,所以这里有多个缓冲区。我们使用一个线性表来代替数组储存声源,因为这样支持动态改变声源的数量。我们可以不断把声源加入到场景当中直到OpenAL将它们用完,这是教程中首次把声源作为一种可被用完的资源,确实,它们是有限的。
static int initOpenAL() {
al = ALFactory.getAL();
alc = ALFactory.getALC();
ALCdevice device;
ALCcontext context;
String deviceSpecifier;
String deviceName = "DirectSound3D"; //你可以指定一个其它的设备
deviceName = null; //使用null可以获得系统默认的设备
//得到设备句柄
device = alc.alcOpenDevice(deviceName);
//获得设备标识符
deviceSpecifier = alc.alcGetString(device, ALC.ALC_DEVICE_SPECIFIER);
System.out.println("Using device " + deviceSpecifier);
//创建音频上下文
context = alc.alcCreateContext(device, null);
//将其设置为当前上下文
alc.alcMakeContextCurrent(context);
//检测错误
if (alc.alcGetError(device) != ALC.ALC_NO_ERROR)
return AL.AL_FALSE;
return AL.AL_TRUE;
}
以上的代码来自于上一个教程,我们获得了DirectSound3D的设备句柄,并为我们的程序创建一个渲染上下文,之后将其设置为当前上下文并在返回成功前检测错误以保证一切都顺利完成。
static void exitOpenAL() {
ALCcontext curContext;
ALCdevice curDevice;
//获得当前上下文
curContext = alc.alcGetCurrentContext();
//由上下文获得设备
curDevice = alc.alcGetContextsDevice(curContext);
//重置当前上下文为null
alc.alcMakeContextCurrent(null);
//释放上下文及设备
alc.alcDestroyContext(curContext);
alc.alcCloseDevice(curDevice);
}
以上方法将会作出与上一个代码片段相反的行为,它重新获得程序中使用的上下文及设备并释放它们,之后将当前上下文设置为null(默认值)以停止所有OpenAL对声音数据的处理过程。将当前上下文置为null极其重要,否则你可能会使用一个无效的上下文来处理音频数据,这么做的结果是不可预料的。
如果你的程序使用多个上下文,那么你可能需要一个更高级的方法来初始化与关闭,我建议使用全局变量保存所有的上下文及设备并在程序结束时逐一关闭而非只处理当前上下文。
static int loadALData() {
//需要填装的变量。
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];
//将数据放入缓冲区
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]);
//进行错误检测并返回
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);
}
这个函数用来为我们创建声源,它将为每一个我们在之前代码中装载的缓冲区创建一个单独的声源。将之前代码中定义的静态缓冲区索引作为type参数传入,并在最终进行一次错误检测以保证确实创建了一个有效的声源(如我所说,它们是有限的)。如果声源无法分配,程序将会退出。
static void killALData() {
Iterator 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
} // class
这里是程序的主循环。大体上讲它等待一个键盘输入某一特定按键并依据按键的不同向场景中加入不同类型的声源。本质上将,这里做的内容非常像现实中人们听录音带休闲的情景。我们的程序可以让用户来选择当前播放的背景音乐,而且代码干净利落不是么?我在编码时就听着它,多么富有禅意啊(现在我还在听着)。
这个程序可以拓展到处理更多种类的wav文件,并添加在任意位置加入声源的功能,你也可以为加入的声源设置播放频率而不是单一循环。然而,这可能需要设计一个GUI程序,它已经超出了本次教程的范围。能够做出一个全功能的“天气引擎”就已经足够漂亮了;)