OpenAL

OpenAL是跨平台3D音频库,一般使用完全遵循其API的软件实现OpenAL-Soft。

OpenAL Soft - Software 3D Audio

作者已经提供预编译二进制动态链接库(.dll),但静态库(.lib)仍需要从源代码手动编译。

编译静态链接库

需要在CMake中单独手动添加生成参数LIBTYPE,值为STATIC。

确保生成大小约为30兆的静态库,而不是动态库的导入库。

注意:通过静态链接引入OpenAL soft,需要在include头文件之前添加预处理宏AL_LIBTYPE_STATIC。同时,Windows平台下需要一同链接winmm.lib。

初始化

类似OpenGL,OpenAL需要创建并绑定上下文。

1
2
3
4
5
6
7
8
9
ALCdevice* pDevice = alcOpenDevice(NULL);
ALCcontext* pContext;
if (pDevice){
pContext = alcCreateContext(pDevice, NULL);
alcMakeContextCurrent(pContext);
}
else{
std::cerr << "ERROR: DEVICE OPEN FAILED" << std::endl;
}

创建AL对象

AL对象包括buffer,source,listener。其中每个Context包含一个默认listener,不需要手动创建。

  • Buffer(缓冲区):用于存储音频数据。
  • Source(音源):播放音频的对象,与缓冲区绑定。
  • Listener(监听者):模拟耳朵,决定音频的空间定位。
1
2
3
    ALuint buffer, source;
alGenBuffers(1, &buffer);
alGenSources(1, &source);

函数返回无号整型id,为后续传参函数使用。

参数传递

OpenAL的传参函数类似于OpenGL。函数尾缀f/i代表传入浮点/整型,尾缀v代表传入指针。第一参数为对象id,第二参数为OpenAL内建宏定义,这些宏定义以 AL_ALC_ 开头。

音源的宏用于设置 音量、位置、速度、音调、循环播放 等。

宏定义 作用 默认值 使用的 API
AL_GAIN 设置音量 (0.0 ~ 1.0,>1.0 可增益) 1.0 alSourcef
AL_PITCH 设置音调 (1.0 = 正常, 0.5 = 低八度, 2.0 = 高八度) 1.0 alSourcef
AL_LOOPING 是否循环播放 (AL_TRUE / AL_FALSE) AL_FALSE alSourcei
AL_POSITION 设置音源位置 (x, y, z) {0,0,0} alSourcefv
AL_VELOCITY 设置音源速度 (x, y, z) {0,0,0} alSourcefv
AL_DIRECTION 设置音源方向 (x, y, z) {0,0,0} alSourcefv
AL_SOURCE_RELATIVE 是否相对监听者定位 (AL_TRUE / AL_FALSE) AL_FALSE alSourcei
AL_SOURCE_STATE 获取音源状态 (AL_PLAYING, AL_PAUSED, AL_STOPPED) - alGetSourcei
AL_BUFFER 绑定缓冲区 (Buffer ID) 0 alSourcei
AL_REFERENCE_DISTANCE 3D 音频的衰减起点 1.0 alSourcef
AL_ROLLOFF_FACTOR 3D 音频衰减速率 (0.0 表示无衰减) 1.0 alSourcef

示例:设置音源属性

1
2
3
4
5
alSourcef(source, AL_GAIN, 0.8f);      // 设置音量
alSourcef(source, AL_PITCH, 1.2f); // 提高音调
alSourcei(source, AL_LOOPING, AL_TRUE); // 循环播放
ALfloat pos[] = {0.0f, 0.0f, -3.0f};
alSourcefv(source, AL_POSITION, pos); // 设置音源位置

监听者代表“耳朵”,影响 3D 声音的方向、位置等。

宏定义 作用 默认值 使用 API
AL_POSITION 监听者位置 (x, y, z) {0,0,0} alListenerfv
AL_VELOCITY 监听者速度 (x, y, z) {0,0,0} alListenerfv
AL_ORIENTATION 监听者朝向 (前向向量 + 上向量) {0,0,-1, 0,1,0} alListenerfv
AL_GAIN 监听者整体音量 1.0 alListenerf

示例:设置监听者位置和方向。

1
2
3
4
5
ALfloat listenerPos[] = {0.0f, 0.0f, 0.0f};
ALfloat listenerOri[] = {0.0f, 0.0f, -1.0f, // 前向
0.0f, 1.0f, 0.0f}; // 上向
alListenerfv(AL_POSITION, listenerPos);
alListenerfv(AL_ORIENTATION, listenerOri);

参数查询

这些宏用于查询 音源、缓冲区、监听者 的当前状态。

宏定义 作用 返回值 使用 API
AL_SOURCE_STATE 获取音源当前状态 AL_PLAYING / AL_STOPPED / AL_PAUSED alGetSourcei
AL_PLAYING 表示音源正在播放 0x1012 alGetSourcei
AL_STOPPED 表示音源已停止 0x1014 alGetSourcei
AL_PAUSED 表示音源已暂停 0x1013 alGetSourcei
AL_INITIAL 表示音源未播放过 0x1011 alGetSourcei
1
2
3
4
5
ALint state;
alGetSourcei(source, AL_SOURCE_STATE, &state);
if (state == AL_PLAYING) {
std::cout << "音源正在播放\n";
}

音频播放

缓冲区 (Buffer) 存储音频数据,需要先填充缓冲区才能播放音频。

1
alBufferData(buffer, AL_FORMAT_MONO16, pcmData, dataSize, sampleRate);

参数解析:

  • buffer:缓冲区 ID
  • AL_FORMAT_MONO16:音频格式(单声道 16-bit)
  • pcmData:音频数据指针
  • dataSize:数据大小(字节)
  • sampleRate:采样率,一般为44100Hz

此处提供加载音频数据指针的函数。

1
2
3
4
5
6
7
8
9
10
11
void loadPCM(std::string audioPath, unsigned char** audioBuffer,int* audioSize) {
std::ifstream audioFile(audioPath, std::ios::binary | std::ios::in);
audioFile.seekg(0, std::ios::beg);
std::ifstream file(audioPath, std::ios::binary | std::ios::ate);
std::streampos endPos = file.tellg();
*audioSize = int(endPos);
file.close();
*audioBuffer = new unsigned char[audioSize];
audioFile.read((char*)audioBuffer, audioSize);
audioFile.close();
}

绑定缓冲区至声源,并播放音频。

1
2
3
4
//绑定
alSourcei(source, AL_BUFFER, buffer);
// 播放音频
alSourcePlay(source);

销毁

运行结束前,需要手动释放每一个创建过的AL对象,销毁上下文和设备。

1
2
3
4
5
alDeleteSources(1, &source);
alDeleteBuffers(1, &buffer);
alcMakeContextCurrent(NULL);
alcDestroyContext(context);
alcCloseDevice(device);

示例代码

此处提供音频加载,循环播放的完整实现代码。

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
55
56
57
58
59
60
#define AL_LIBTYPE_STATIC
#include <stdlib.h>
#include <AL/al.h>
#include <AL/alc.h>
#include <iostream>
#include <fstream>
#include <string>

void loadAudioSource(ALuint* m_source, ALuint* m_buffer, std::string audioPath) {
std::ifstream audioFile(audioPath, std::ios::binary | std::ios::in);
audioFile.seekg(100, std::ios::beg);
std::ifstream file(audioPath, std::ios::binary | std::ios::ate);
std::streampos endPos = file.tellg();
int audioSize = int(endPos);
file.close();
unsigned char* audioBuffer = new unsigned char[audioSize];
audioFile.read((char*)audioBuffer, audioSize);
audioFile.close();

ALuint buffer, source;
alGenBuffers(1, &buffer);
alBufferData(buffer, AL_FORMAT_STEREO16, audioBuffer, audioSize, 44100);
alGenSources(1, &source);
alSourcei(source, AL_BUFFER, buffer);
*m_source = source;
*m_buffer = buffer;
delete[] audioBuffer;
}

int main()
{
ALCdevice* device = alcOpenDevice(NULL);
ALCcontext* context;
if (device)
{
context = alcCreateContext(device, NULL);
alcMakeContextCurrent(context);
}
else
{
std::cout << "device open error" << std::endl;
}

ALuint source, buffer;
loadAudioSource(&source, &buffer, "../audio/test_audio.wav");
alSourcei(source, AL_LOOPING, AL_TRUE);
alSourcePlay(source);

while (1) {

}

alDeleteSources(1, &source);
alDeleteBuffers(1, &buffer);
alcMakeContextCurrent(NULL);
alcDestroyContext(context);
alcCloseDevice(device);

return 0;
}