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

本教程介绍了如何使用OpenAL创建一个简单的3D音频播放器。详细讲解了声源、缓冲区和听众的概念,并提供了完整的代码示例。

原文地址:http://jogamp.org/joal-demos/www/devmaster/lesson1.html

原文作者:Athomas Goldberg

译文:三向板砖

转载请保留以上信息。

 

本节课程的学习笔记,记录了课程中值得注意的问题以及方便复制测试的连续代码片段:

http://blog.youkuaiyun.com/shuzhe66/article/details/40213193

 

第一课 单一固定声源

 

本文是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, 0.0f };
    //听众的位置
    static float[] listenerPos = { 0.0f, 0.0f, 0.0f };
    //听众的速度矢量
    static float[] listenerVel = { 0.0f, 0.0f, 0.0f };
    //听众的朝向. (前三个参数表示“脸”的正对方向,后三个参数表示“头顶”方向)[原文为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 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, buffer, 0);
        	al.alDeleteSources(1, source, 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——译者注],但我们总得从这里开始,随着我们的进一步深入还会介绍更为高级的部分。



基于实时迭代的数值鲁棒NMPC双模稳定预测模型(Matlab代码实现)内容概要:本文介绍了基于实时迭代的数值鲁棒非线性模型预测控制(NMPC)双模稳定预测模型的研究与Matlab代码实现,重点在于通过数值方法提升NMPC在动态系统中的鲁棒性与稳定性。文中结合实时迭代机制,构建了能够应对系统不确定性与外部扰动的双模预测控制框架,并利用Matlab进行仿真验证,展示了该模型在复杂非线性系统控制中的有效性与实用性。同时,文档列举了大量相关的科研方向与技术应用案例,涵盖优化调度、路径规划、电力系统管理、信号处理等多个领域,体现了该方法的广泛适用性。; 适合人群:具备一定控制理论基础和Matlab编程能力,从事自动化、电气工程、智能制造等领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①用于解决非线性动态系统的实时控制问题,如机器人控制、无人机路径跟踪、微电网能量管理等;②帮助科研人员复现论文算法,开展NMPC相关创新研究;③为复杂系统提供高精度、强鲁棒性的预测控制解决方案。; 阅读建议:建议读者结合提供的Matlab代码进行仿真实践,重点关注NMPC的实时迭代机制与双模稳定设计原理,并参考文档中列出的相关案例拓展应用场景,同时可借助网盘资源获取完整代码与数据支持。
UWB-IMU、UWB定位对比研究(Matlab代码实现)内容概要:本文介绍了名为《UWB-IMU、UWB定位对比研究(Matlab代码实现)》的技术文档,重点围绕超宽带(UWB)与惯性测量单元(IMU)融合定位技术展开,通过Matlab代码实现对两种定位方式的性能进行对比分析。文中详细阐述了UWB单独定位与UWB-IMU融合定位的原理、算法设计及仿真实现过程,利用多传感器数据融合策略提升定位精度与稳定性,尤其在复杂环境中减少信号遮挡和漂移误差的影响。研究内容包括系统建模、数据预处理、滤波算法(如扩展卡尔曼滤波EKF)的应用以及定位结果的可视化与误差分析。; 适合人群:具备一定信号处理、导航定位或传感器融合基础知识的研究生、科研人员及从事物联网、无人驾驶、机器人等领域的工程技术人员。; 使用场景及目标:①用于高精度室内定位系统的设计与优化,如智能仓储、无人机导航、工业巡检等;②帮助理解多源传感器融合的基本原理与实现方法,掌握UWB与IMU互补优势的技术路径;③为相关科研项目或毕业设计提供可复现的Matlab代码参考与实验验证平台。; 阅读建议:建议读者结合Matlab代码逐段理解算法实现细节,重点关注数据融合策略与滤波算法部分,同时可通过修改参数或引入实际采集数据进行扩展实验,以加深对定位系统性能影响因素的理解。
本系统基于MATLAB平台开发,适用于2014a、2019b及2024b等多个软件版本,并提供了可直接执行的示例数据集。代码采用模块化设计,关键参数均可灵活调整,程序结构逻辑分明且附有详细说明注释。主要面向计算机科学、电子信息工程、数学等相关专业的高校学生,适用于课程实验、综合作业及学位论文等教学与科研场景。 水声通信是一种借助水下声波实现信息传输的技术。近年来,多输入多输出(MIMO)结构与正交频分复用(OFDM)机制被逐步整合到水声通信体系中,显著增强了水下信息传输的容量与稳健性。MIMO配置通过多天线收发实现空间维度上的信号复用,从而提升频谱使用效率;OFDM方案则能够有效克服水下信道中的频率选择性衰减问题,保障信号在复杂传播环境中的可靠送达。 本系统以MATLAB为仿真环境,该工具在工程计算、信号分析与通信模拟等领域具备广泛的应用基础。用户可根据自身安装的MATLAB版本选择相应程序文件。随附的案例数据便于快速验证系统功能与性能表现。代码设计注重可读性与可修改性,采用参数驱动方式,重要变量均设有明确注释,便于理解与后续调整。因此,该系统特别适合高等院校相关专业学生用于课程实践、专题研究或毕业设计等学术训练环节。 借助该仿真平台,学习者可深入探究水声通信的基础理论及其关键技术,具体掌握MIMO与OFDM技术在水声环境中的协同工作机制。同时,系统具备良好的交互界面与可扩展架构,用户可在现有框架基础上进行功能拓展或算法改进,以适应更复杂的科研课题或工程应用需求。整体而言,该系统为一套功能完整、操作友好、适应面广的水声通信教学与科研辅助工具。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值