[译文]JOAL教程 第一课 单一固定声源

原文地址: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——译者注],但我们总得从这里开始,随着我们的进一步深入还会介绍更为高级的部分。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结