原文地址:http://jogamp.org/joal-demos/www/devmaster/lesson1.html
原文作者:Athomas Goldberg
譯文:三向板磚
轉載請保留以上信息。
本節課程的學習筆記,記錄了課程中值得注意的問題以及方便複製測試的連續代碼片段:
http://blog.csdn.net/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——譯者注],但我們總得從這裏開始,隨着我們的進一步深入還會介紹更爲高級的部分。