1. 程式人生 > >[譯文]JOAL教程 第五課 多聲源共享緩衝區

[譯文]JOAL教程 第五課 多聲源共享緩衝區

[譯文]JOAL教程

原文作者:Athomas Goldberg

譯文:三向板磚

轉載請保留以上資訊。

第五課 多聲源共享緩衝區

本文是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程式,它已經超出了本次教程的範圍。能夠做出一個全功能的“天氣引擎”就已經足夠漂亮了;)