OpenAL 簡易教學

#C

要使用OpenAL,首先要知道裡面的四個東西:

首先是要將一個聲音檔讀入,以下示範wav檔案:

WAV檔案

其實alut裡面有一個函數可以讀取,可是alut需要額外裝,加上我第一次試驗的code前前後後就只有這一個函數用到alut,所以就來研究一下要如何不靠alut把wav檔案讀入.

WAV檔案格式

WAV是binary檔,主要有三個區塊:

RIFF Header

WIKI: RIFF Header

NameSizeType
ChunkID4char * 4
ChunkSize4int * 1
Format4char * 4
1
2
3
4
5
struct RIFF_Header {
    char chunkID[4];
    int chunkSize;//size not including chunkSize or chunkID
    char format[4];
};

WAVE 格式 + 資料

NameSizeType
subChunk1ID4char * 4
subChunk1Size4int * 1
audio Format2short * 1
num Channel2short * 1
Sample Rate4int * 1
Byte Rate4int * 1
Byte Align2short * 1
bits Per Sample2short * 1
subChunk2ID4int * 1
subChunk2Size4int * 1
Dataunsigned char
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
struct WAVE_Format {
    char subChunkID[4];
    int subChunkSize;
    short audioFormat;
    short numChannels;
    int sampleRate;
    int byteRate;
    short blockAlign;
    short bitsPerSample;
};

struct WAVE_Data {
    char subChunkID[4]; //should contain the word data
    int subChunk2Size; //Stores the size of the data block
};

在設定OpenAL的聲音format時會需要考慮num Channel, bits per Sample

以上是WAV格式簡略的介紹,接下來就是OpenAL的部分

OpenAL簡略的播放聲音

首先是初始化,像在前面有提過的,有device和context

1
2
3
ALCdevice* dev = alcOpenDevice(NULL);
ALCcontext* ctx = alcCreateContext(dev, NULL);
alcMakeContextCurrent(ctx);

Listener 的初始化:

1
2
3
4
5
6
ALfloat listenerPos[]={0.0,0.0,4.0};
ALfloat listenerVel[]={0.0,0.0,0.0};
ALfloat listenerOri[]={0.0,0.0,1.0, 0.0,1.0,0.0};
alListenerfv(AL_POSITION,listenerPos);
alListenerfv(AL_VELOCITY,listenerVel);
alListenerfv(AL_ORIENTATION,listenerOri);

準備WavLoader

首先比較特別的是我們需要決定好format

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if (wave_format.numChannels == 1) {
    if (wave_format.bitsPerSample == 8 )
        *format = AL_FORMAT_MONO8;
    else if (wave_format.bitsPerSample == 16)
        *format = AL_FORMAT_MONO16;
} else if (wave_format.numChannels == 2) {
    if (wave_format.bitsPerSample == 8 )
        *format = AL_FORMAT_STEREO8;
    else if (wave_format.bitsPerSample == 16)
        *format = AL_FORMAT_STEREO16;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
bool loadWavFile(const char* filename, 
    ALuint* buffer, ALsizei* size, ALsizei* frequency, ALenum* format) {
    fprintf(stderr, "[1] filename = %s\n", filename);
    FILE* soundFile = NULL;
    WAVE_Format wave_format;
    RIFF_Header riff_header;
    WAVE_Data wave_data;
    unsigned char* data = 0;

    soundFile = fopen(filename, "rb");
    // Read in the first chunk into the struct
    fread(&riff_header, sizeof(RIFF_Header), 1, soundFile);
    
    //Read in the 2nd chunk for the wave info
    fread(&wave_format, sizeof(WAVE_Format), 1, soundFile);
    
    //check for extra parameters;
    if (wave_format.subChunkSize > 16)
        fseek(soundFile, sizeof(short), SEEK_CUR);

    //Read in the the last byte of data before the sound file
    fread(&wave_data, sizeof(WAVE_Data), 1, soundFile);
        
    //Allocate memory for data
    data = new unsigned char[wave_data.subChunk2Size];
    fread(data, wave_data.subChunk2Size, 1, soundFile);
        
    //Now we set the variables that we passed in with the
    //data from the structs
    *size = wave_data.subChunk2Size;
    *frequency = wave_format.sampleRate;
    //The format is worked out by looking at the number of
    //channels and the bits per sample.
    if (wave_format.numChannels == 1) {
        if (wave_format.bitsPerSample == 8 )
            *format = AL_FORMAT_MONO8;
        else if (wave_format.bitsPerSample == 16)
            *format = AL_FORMAT_MONO16;
    } else if (wave_format.numChannels == 2) {
        if (wave_format.bitsPerSample == 8 )
            *format = AL_FORMAT_STEREO8;
        else if (wave_format.bitsPerSample == 16)
            *format = AL_FORMAT_STEREO16;
    }
    
    //create our openAL buffer and check for success
    alGenBuffers(1, buffer);
    alBufferData(*buffer, *format, (void*)data, *size, *frequency);
    //clean up and return true if successful
    fclose(soundFile);
    fprintf(stderr, "load ok\n");
    delete[] data;
    return true;
}

然後就可以播放拉,先把buffer讀取進來,指定給source,然後播放.

1
2
3
4
5
6
7
8
alGenSources(1, &source);
loadWavFile("Under_Water.wav", buffer, &size, &freq, &format);
alSourcef(source, AL_PITCH, 1.0f);
alSourcef(source, AL_GAIN, 1.0f);
alSourcefv(source, AL_POSITION, source0Pos);
alSourcefv(source, AL_VELOCITY, source0Vel);
alSourcei(source, AL_BUFFER, buffer[0]);
alSourcePlay(source);  

完整code(包括多重播放)

Gist:altest1.cpp

參考資料

  1. Tutorial: Loading Wave files in OpenAL without ALUT
  2. Play Compressed Formats With OpenAL
  3. Creative OpenAL Programmer’s Reference
  4. OpenAL Tutorial 1 - Playing WAV files (No ALUT required!)