原文地址:http://jogamp.org/joal-demos/www/devmaster/lesson1.html
原文作者:Athomas Goldberg
译文:三向板砖
转载请保留以上信息。
本节课程的学习笔记,记录了课程中值得注意的问题以及方便复制测试的连续代码片段:
http://www.jb51.cc/article/p-zvxoaybm-ye.html
第一课 单一固定声源
本文是DevMaster.net(http://devmaster.net/)的OpenAL教程对应的JOAL版本。C语言版原文作者为JesseMaurais
欢迎来到令人激动的OpenAL世界!OpenAL目前仍在不断成长,仍有一大批后续API没有达到它的全部潜能。这其中最主要的原因是由于某些声卡仍然不支持硬件加速。
然而,OpenAL项目的主要贡献者、同时也是最大的声卡生产商之一的Creative Labs公司,已经承诺在不久的将来全面支持声卡的硬件加速。
OpenAL仅有的另一位的主要贡献者Loki已经不知去向,所以OpenAL在Linux平台上的发展前景尚不明朗,但你仍可在一些第三方页面上下载到支持Linux的OpenAL类库
目前,OpenAL仍未在主流商业产品中出现,这也许会妨碍到它的成长。据我所知唯一一款使用了OpenAL的PC游戏是合金装备2(最近我发现虚幻2引擎也使用了它)。流行的建模工具Blender3D同样适用OpenAL作为其音频播放组件。抛开这些,其他使用OpenAL的地方恐怕也只有其SDK中的例子和出现在其它网站上的零散教程了。
但让我们直面现实吧,OpenAL确实很有潜力。有很多其它的音频库都需要与硬件一起工作在底层,但是OpenAL的设计者在其设计中改良了很多部分,使OpenAL成为了一个高级API。
首先,它以之前设计的最棒API之一:OpenGL作为其模板参考,较高的灵活度使不同编程方式和硬件实现变得容易。当然,具有OpenGL学习经验的人会很快学会OpenAL。
其次,OpenAL在创建3D环绕立体声时具有其他音频库无法比拟的优势。
最最给力得一点,加以拓展的OpenAL可以与EAX和AC3完美地融合,据我所知没有任何其它音频库具有这个能力。
如果你还是拿不定注意是否需要它,这里倒是有一个:它很酷,它是一个优雅的API,可以和你的代码完美地组合在一起,你可以使用它做很多音频特效,但在我们开始之前,必须来学习一些基础知识。
不多说了,一起来编码吧!
import com.jogamp.openal.*; import com.jogamp.openal.util.*; import java.io.*; import java.nio.ByteBuffer; public class SingleStaticSource { static AL al = ALFactory.getAL(); //缓冲区储存音频数据 static int[] buffer = new int[1];; //声源播放声音 static int[] source = new int[1];和OpenGL处理程序使用的“纹理对象”(或是纹理名称)时的过程相似,OpenAL使用同样的方法处理音频采样。在OpenAL中有三种基本的对象:储存着播放所需全部信息与声音数据的缓冲区、在空间中发出声音的点声源以及一个听众。
声源本身并不是音频采样,这一点极其重要。声源只是负责读取绑定在它身上的音频数据缓冲区并播放音频,我们可以为声源设置位置和速度来改变声音的特性[这里的速度不是指播放速度,而是声源的物理速度,利用这个特点可以模拟声音的多普勒效应等——译者注]
只有一个听众对象,它代表着用户的位置,听众与声源的属性共同决定了用户实际听到的音频样本,例如其相对位置决定了音频强度。
//声源的位置矢量 static float[] sourcePos = { 0.0f,0.0f,0.0f }; //声源的速度矢量 static float[] sourceVel = { 0.0f,0.0f }; //听众的位置 static float[] listenerPos = { 0.0f,0.0f }; //听众的速度矢量 static float[] listenerVel = { 0.0f,0.0f }; //听众的朝向. (前三个参数表示“脸”的正对方向,后三个参数表示“头顶”方向)[原文为first 3 elements are "at",second 3 are "up"] static float[] listenerOri = { 0.0f,-1.0f,1.0f,0.0f };在上述代码中,我们为声源与听众设置了位置与速度,这些数组是基于直角坐标系的,你也可以使用结构体或类来完成同样的功能,我这里使用数组只是为了方便。
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]; //将Wav文件装入缓冲区 al.alGenBuffers(1,buffer,0); if (al.alGetError() != AL.AL_NO_ERROR) return AL.AL_FALSE; ALut.alutLoadWAVFile("wavdata/FancyPants.wav",format,data,size,freq,loop); al.alBufferData(buffer[0],format[0],data[0],size[0],freq[0]);方法‘alGenBuffers‘将会创建缓冲区对象并将我们传入的值存入其中,错误检测功能极其重要,它确保一切执行顺利进行。某些情况下,由于内存不足,OpenAL无法创建缓冲区对象,此时对其的设置将会出错。Alut工具套件此时显得十分有用,它打开文件并自动装填创建缓冲区的必要信息,而我们所做的,只是调用一个简洁高效的方法。
// 将缓冲区绑定到声源上. al.alGenSources(1,source,0); if (al.alGetError() != AL.AL_NO_ERROR) return AL.AL_FALSE; al.alSourcei (source[0],AL.AL_BUFFER,buffer[0] ); 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,loop[0] );
产生声源对象与产生缓冲区对象所用的方法相似。之后,我们定义声源的各种播放所需属性,这其中最为重要的属性是声源所使用的缓冲区对象,它告诉了声源将要播放哪一个声音样本,在本例中,只有一个音频。当然,我们也会将之前定义好的声源位置与速度告诉它。
与’alGenBuffers’和’alGenSources’有关的另一件事:在一些实例中,我见过这些函数会返回一个整型值来表示创建的缓冲区和声源数量,我想这是一个由早期版本遗留下来的错误检测机制,如果你在其它代码片段中看到了这样的写法也不要仿照去写,如果你想进行此类检查,使用’alGetError’来代替(就像上面做的那样)
//再一次检查并返回结果 if(al.alGetError() == AL.AL_NO_ERROR) return AL.AL_TRUE; return 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() { al.alDeleteBuffers(1,0); al.alDeleteSources(1,0); ALut.alutExit(); }这里是我们的关闭过程,释放程序使用的音频设备与内存资源是十分必要的。
public static void main(String[] args) { //初始化OpenAL并重置错误检测标记 ALut.alutInit(); al.alGetError();alutIniti将会为我们初始化Alc所需的一切。大体上讲,Alut通过Alc创建一个OpenAL上下文并将其置为当前上下文,在Windows平台上,它初始化DirectSound。我们还对错误检测函数进行初始化以清除之前无效的错误信息,每当我们调用glGetError时,它会将内部错误标记变量置为'AL_NO_ERROR'。
//装载Wav数据. if (loadALData() == AL.AL_FALSE) System.exit(-1); setListenerValues(); //设置一个钩子,在系统退出时被执行。 Runtime runtime = Runtime.getRuntime(); runtime.addShutdownHook( new Thread( new Runnable() { public void run() { killALData(); } } ) );我们必须保证wav文件被正确装入,否则系统必须退出。之后是对听众信息以及退出过程的设置。
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: " + "'p' to play,'s' to stop,'h' to pause and 'q' to quit"); buf.read(c); switch(c[0]) { case 'p': //按p键开始播放 al.alSourcePlay(source[0]); break; case 's': //按s键停止播放 al.alSourceStop(source[0]); break; case 'h': //按h键暂停播放 al.alSourcePause(source[0]); break; } } catch (IOException e) { System.exit(1); } } } }//类括号这里是教程最有趣的地方,我们只用一个基本的循环结构便控制了音频播放器的播放、暂停、停止与退出。 好了,这一部分到这里就结束了。这是你第一次进入OpenAL世界,我希望以上教程对你来说是足够简单的,当然,对于黑客而言是在太简单了[原文中作者使用了“1337 h4X0r”,翻译为hacker——译者注],但我们总得从这里开始,随着我们的进一步深入还会介绍更为高级的部分。