OpenALPlayer

WAVE 文件

WAVE 文件格式是微软用来存储多媒体文件的一个子集;遵循 RIFF资源交换档案格式

一个 RIFF 文件从一个文件头开始,然后是一个数据块的序列。WAVE文件通常只是一个重复的文件,其中包含一个WAVE块,它由两个子块组成——一个fmt块指定数据格式和一个包含实际示例数据的data块。

标准的 WAVE 文件格式(图片来源于这里

wav-sound-format.gif

其文件头结构体如下:

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
typedef unsigned int uInt32;
typedef unsigned short uInt16;
// 除说明外,其他都是 小端模式
typedef struct __RIFF {
char riffChunkID[4]; // 固定标识 'RIFF' (大端模式)
uInt32 fileSize; // 文件大小(除掉标志'RIFF' + 这个整数本身)
char format[4]; // 固定标识 'WAVE' (大端模式)
}RIFF;
typedef struct __FMT {
char fmtChunkID[4]; // 固定标识 'fmt ' (大端模式)
uInt32 fmtChunkSize; // fmt 区块大小(除掉标志'fmt ' + 这个整数本身)
uInt16 audioFormat; // 格式类型(pcm = 1)
uInt16 numChannels; // 声道数(Mono = 1, Stereo = 2)
uInt32 sampleRate; // 采样率(48000, 44100)
uInt32 byteRate; // 每秒播放字节数(码率/8) = sampleRate * numChannels * bitsPerSample/8
uInt16 blockAlign; // DATA数据块单位(单位采样)长度 = numChannels * bitsPerSample/8
uInt16 bitsPerSample; // PCM位深 - 用来存储采样点y值所使用的二进制位个数(8 bits, 16 bits, 24 bits)
}FMTChunk;
typedef struct __DATA {
char dataChunkID[4]; // 固定标识'data'(大端模式)
uInt32 dataChunkSize; // 数据部分总长度 区块大小(除掉标志'data' + 这个整数本身)
}DATAChunk;
typedef struct __WAVE {
RIFF riff; // riff 块
FMTChunk fmt; // fmt 块
DATAChunk data; // data 块
}WAVHead;

OpenAL

OpenAL(Open Audio Library)是自由软件界的跨平台音效API。它设计给多通道三维位置音效的特效表现。其API风格模仿自 OpenGL

API 结构和功能

  1. OpenAL 主要的功能是在来源物体音效缓冲收听者中编码。
    • 来源物体:包含一个指向缓冲器的指针、声音的速度、位置和方向,以及声音强度。
    • 音效缓冲:包含8或16位、单声道或立体声 PCM 格式的音效数据,表现引擎进行所有必要的计算,如距离衰减、多普勒效应等。
    • 收听者:包含收听者的速度、位置和方向,以及全部声音的整体增益。
  2. 不同于OpenGL规格,OpenAL规格包含两个API分支:
    • 以实际 OpenAL 函数组成的核心。
    • ALC:用于管理表现内容、资源使用情况,并将跨平台风格封在其中。还有ALUT程序库,提供高级“易用”的函数,其定位相当于OpenGL的GLUT。

实现的基本步骤:

  1. 得到设备信息
  2. 将环境与设备关联
  3. 在缓存中加入声音数据
  4. 在声源中加入缓存数据
  5. 播放声源

实现代码

SYOpenALPlayer.hpp

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#ifndef SYOpenALPlayer_hpp
#define SYOpenALPlayer_hpp
#include <iostream>
#include <string>
#include <OpenAL/OpenAL.h>
#define BUFF_NUM 3
#define BUFF_SIZE 4096
typedef enum {
SourceInitial = 0x00, // 初始
SourcePlaying = 0x01, // 播放
SourcePaused = 0x02, // 暂停
SourceStopped = 0x03, // 停止
}SourceState;
class SYOpenALPlayer
{
private:
ALCdevice* m_device; // 硬件,获取设备音频硬件资源
ALCcontext* m_context; // 内容,给播放器提供上下文环境描述
ALuint m_sourceID; // 音源,标识每一个音源
ALuint m_buffers[BUFF_NUM]; // 缓存
ALenum m_format; // 格式
ALsizei m_frequency; // 采样频率
ALubyte* m_initBuf; // 缓冲区
ALint m_fillBufLen; // 初始化缓冲区长度
ALint m_totalBufLen; // 缓冲区总长度
/**
更新 OpenAL 缓存
*/
void refreshQueueBuffer();
/**
检出 OpenAL 状态
@return OpenAL 状态值
*/
ALenum checkALError();
public:
SYOpenALPlayer();
~SYOpenALPlayer();
/**
初始化 OpenAL
@return 是否初始化成功(true:成功; false:失败)
*/
bool initOpenAL();
/**
配置 OpenAL
@param channels 声道数
@param bits 位深
@param frequency 采样频率
@return 是否配置成功(true:成功; false:失败)
*/
bool configOpenAL(ALuint channels, ALuint bits, ALuint frequency);
/**
设置是否循环播放
@param isLoop 是否循环(1:循环;0:不循环)
*/
void setLoop(ALint isLoop);
/**
设置音量大小
@param volume 音量值(0.0 —— 1.0)
*/
void setVolume(ALfloat volume);
/**
设置播放速度
@param speed 播放速度(1.0:正常速度)
*/
void setSpeed(ALfloat speed);
/**
获取 OpenAL 已经处理(播放)完毕的缓冲个数
@return 缓冲个数
*/
ALint numOfBuffProcessed();
/**
获取音源状态
@return 音源状态值,参见‘SourceState’
*/
SourceState sourceState();
/**
打开音频缓存进行播放
@param buffer 需要播放的音频缓存数据
@param length 缓存长度
*/
void openAudio(ALubyte* buffer, ALuint length);
/**
开启播放
*/
void playSound();
/**
暂停播放
*/
void pauseSound();
/**
停止播放
*/
void stopSound();
/**
清理 OpenAL
*/
void clearOpenAL();
};
#endif /* SYOpenALPlayer_hpp */

SYOpenALPlayer.cpp

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#include "SYOpenALPlayer.hpp"
#pragma mark - Private
#pragma mark -- 更新 OpenAL 缓存
void SYOpenALPlayer::refreshQueueBuffer()
{
ALint processedNum = numOfBuffProcessed();
while (processedNum--)
{
ALuint buffID;
alSourceUnqueueBuffers(m_sourceID, 1, &buffID);
alDeleteBuffers(1, &buffID);
}
}
#pragma mark -- 检出 OpenAL 状态
ALenum SYOpenALPlayer::checkALError()
{
ALenum errState = AL_NO_ERROR;
errState = alGetError();
switch (errState)
{
case AL_NO_ERROR:
std::cout << "AL_NO_ERROR" << std::endl;
break;
case AL_INVALID_NAME:
std::cout << "AL_INVALID_NAME : Invalid Name paramater passed to AL call" << std::endl;
break;
case AL_INVALID_ENUM:
std::cout << "AL_INVALID_ENUM : Invalid parameter passed to AL call" << std::endl;
break;
case AL_INVALID_VALUE:
std::cout << "AL_INVALID_VALUE : Invalid enum parameter value" << std::endl;
break;
case AL_INVALID_OPERATION:
std::cout << "AL_INVALID_OPERATION : Illegal call" << std::endl;
break;
case AL_OUT_OF_MEMORY:
std::cout << "AL_OUT_OF_MEMORY : No mojo" << std::endl;
break;
default:
std::cout << "Unknown error code" << std::endl;
break;
}
return errState;
}
#pragma mark - Public
#pragma mark -- 构造函数
SYOpenALPlayer::SYOpenALPlayer()
{
m_fillBufLen = 0;
m_totalBufLen = BUFF_NUM * BUFF_SIZE;
m_initBuf = (ALubyte*)malloc(m_totalBufLen);
}
#pragma mark -- 析构函数
SYOpenALPlayer::~SYOpenALPlayer()
{
clearOpenAL();
}
#pragma mark -- 初始化 OpenAL
bool SYOpenALPlayer::initOpenAL()
{
m_device = alcOpenDevice(NULL); // 参数为 NULL, 让 ALC 使用默认设备
m_context = alcCreateContext(m_device, NULL);
alcMakeContextCurrent(m_context);
alGenSources(1, &m_sourceID); // 初始化音源 ID
alGenBuffers(BUFF_NUM, m_buffers);
return true;
}
#pragma mark -- 配置 OpenAL
bool SYOpenALPlayer::configOpenAL(ALuint channels, ALuint bits, ALuint frequency)
{
ALenum format;
if (8 == bits)
{
if (1 == channels)
{
format = AL_FORMAT_MONO8;
}
else if (2 == channels)
{
format = AL_FORMAT_STEREO8;
}
else
{
format = 0;
}
}
else if (16 == bits)
{
if (1 == channels)
{
format = AL_FORMAT_MONO16;
}
else if (2 == channels)
{
format = AL_FORMAT_STEREO16;
}
else
{
format = 0;
}
}
else
{
format = 0;
}
if (0 == format)
{
std::cout << "Incompatible format : channels = " << channels << "bits = " << bits << std::endl;
return false;
}
m_format = format;
m_frequency = frequency;
return true;
}
#pragma mark -- 设置是否循环播放
void SYOpenALPlayer::setLoop(ALint isLoop)
{
alSourcei(m_sourceID, AL_LOOPING, isLoop);
}
#pragma mark -- 设置音量大小
void SYOpenALPlayer::setVolume(ALfloat volume)
{
alSourcef(m_sourceID, AL_GAIN, volume);
}
#pragma mark -- 设置播放速度
void SYOpenALPlayer::setSpeed(ALfloat speed)
{
alSpeedOfSound(1.0f);
}
#pragma mark -- 获取 OpenAL 已经处理(播放)完毕的缓冲个数
ALint SYOpenALPlayer::numOfBuffProcessed()
{
ALint bufNum;
alGetSourcei(m_sourceID, AL_BUFFERS_PROCESSED, &bufNum);
return bufNum;
}
#pragma mark -- 获取音源状态
SourceState SYOpenALPlayer::sourceState()
{
ALint val;
SourceState state;
alGetSourcei(m_sourceID, AL_SOURCE_STATE, &val);
switch (val)
{
case AL_INITIAL:
state = SourceInitial;
break;
case AL_PLAYING:
state = SourcePlaying;
break;
case AL_PAUSED:
state = SourcePaused;
break;
case AL_STOPPED:
state = SourceStopped;
break;
default:
state = SourceInitial;
break;
}
return state;
}
#pragma mark -- 打开音频缓存进行播放
void SYOpenALPlayer::openAudio(ALubyte* buffer, ALuint length)
{
if (NULL == buffer || 0 == length)
{
std::cout << "Can not open audio !" << std::endl;
return;
}
if (0 == m_fillBufLen
|| m_totalBufLen > m_fillBufLen) // 先初始化完所有预备缓冲区
{
ALint needLen = m_totalBufLen - m_fillBufLen;
ALint cpyLen = length > needLen ? needLen : length;
ALint remainLen = length - cpyLen;
memcpy(m_initBuf, buffer, cpyLen);
m_fillBufLen += cpyLen;
if (m_totalBufLen == m_fillBufLen) // 缓冲已满,可以开始播放
{
for (ALint i = 0; i < BUFF_NUM; i++)
{
std::cout << "填第 " << i << "个缓冲区" << std::endl;
alBufferData(m_buffers[i], m_format, m_initBuf + i * BUFF_SIZE, BUFF_SIZE, m_frequency);
}
std::cout << "所有缓冲区入队列 !" << std::endl;
alSourceQueueBuffers(m_sourceID, BUFF_NUM, m_buffers);
free(m_initBuf);
m_initBuf = NULL;
playSound();
}
if (0 < remainLen) // 缓冲满还有剩余
{
ALuint bufId;
alGenBuffers(1, &bufId);
alBufferData(bufId, m_format, buffer + cpyLen, remainLen, m_frequency);
alSourceQueueBuffers(m_sourceID, 1, &bufId);
}
}
else
{
refreshQueueBuffer(); // 更新缓存
ALuint loopBufID;
alGenBuffers(1, &loopBufID);
alBufferData(loopBufID, m_format, buffer, (ALsizei)length, m_frequency);
// 新替换缓冲区重新如队列等待 OpenAL 处理
alSourceQueueBuffers(m_sourceID, 1, &loopBufID);
playSound();
}
}
#pragma mark -- 开启播放
void SYOpenALPlayer::playSound()
{
if (SourcePlaying != sourceState())
{
alSourcePlay(m_sourceID);
}
}
#pragma mark -- 暂停播放
void SYOpenALPlayer::pauseSound()
{
if (SourcePaused != sourceState())
{
alSourcePause(m_sourceID);
}
}
#pragma mark -- 停止播放
void SYOpenALPlayer::stopSound()
{
if (SourceStopped != sourceState())
{
alSourceStop(m_sourceID);
}
}
#pragma mark -- 清理 OpenAL
void SYOpenALPlayer::clearOpenAL()
{
alDeleteSources(1, &m_sourceID);
alDeleteBuffers(BUFF_NUM, m_buffers);
alcDestroyContext(m_context);
alcCloseDevice(m_device);
}

Demo

参考资料

------ 本文结束 ------
欣赏此文?求鼓励,求支持!