OpenAL(Open Audio Library)是自由软件界的跨平台音效API。它设计给多通道三维位置音效的特效表现。其 API 风格模仿自 OpenGL。刚开始接触的时候可以下载工具包Alut,里面提供丰富的处理接口文件加载等,不过建议在自己熟悉了整个流程之后研究下Alut的 源代码,之后尽量使用alc.c里面的东西自己实现Alut工具包的功能,这是一个机灵的程序员的想法!
废话不多说,OpenAl的初始化在一个进程中只能有一次,所以需要保证Al的初始化能够仅有的一次的正确性。设计模式不是有单例的嘛,要规范些可以参考一下。装好了OpenAl的SDk等之后会有很多小例子,那可是非常好的学习资料,本人也是从上面的例子学习过来的。OpenAl封装了很多底层的接口,这让我们在使用的时候非常的便利。基本的东西就不说了,相信只要有例子可依了解和掌握它还是非常的简单的。设置好了声音源以及声音buffer只需要调用alSourcePlay即可播放,播放的参数由alBufferData设定。
OpenAl的流缓冲是很容易的就可以实现,使用alSourceQueueBuffers将声音buffer加到声音源的source的队列中,用alGetSourcei(m_uiSource, AL_BUFFERS_PROCESSED,&m_iBuffersProcessed)取得缓冲队列中的空闲的buffer然后往里面添加数据即可。具体见代码吧。
上面说了必须保证OpenAl的初始化仅有一个。所以我把OpenAl的初始化类声明为全局变量。下面是初始化的过程:
CInitAl::CInitAl(void)
{
m_iHasInit = 0;
if(InitOpenAL() == 0)
{
m_iHasInit = 1;
}
}
/** @fn ~CInitAl(void)
*
* @brief al环境初始化类析构函数
*
* @return
*/
CInitAl::~CInitAl(void)
{
if(m_iHasInit == 1)
{
ExitOpenAL();
}
}
int CInitAl::InitOpenAL(void)
{
ALCdevice *pDevice;
ALCcontext *pContext;
const char *deviceSpecifier;
char deviceName[] = "DirectSound3D";
//得到设备句柄
pDevice = alcOpenDevice(deviceName);
//得到设备说明
deviceSpecifier = alcGetString(pDevice, ALC_DEVICE_SPECIFIER);
//建立声音文本描述
pContext = alcCreateContext(pDevice, NULL);
//设置行为文本描述
alcMakeContextCurrent(pContext);
//检查错误
if(alcGetError(pDevice) != ALC_NO_ERROR)
{
return -1;
}
return 0;
}
void CInitAl::ExitOpenAL(void)
{
ALCcontext *pCurContext;
ALCdevice *pCurDevice;
//得到当前文本描述
pCurContext = alcGetCurrentContext();
//得到用于当前文本描述的设备
pCurDevice = alcGetContextsDevice(pCurContext);
//重置当前文本描述为NULL
alcMakeContextCurrent(NULL);
//释放文本描述和设备
alcDestroyContext(pCurContext);
alcCloseDevice(pCurDevice);
}
声音源以及听者的信息
//源声音的位置.
static ALfloat SourcePos[] = {0.0f, 0.0f, 0.0f};
//源声音的速度.
static ALfloat SourceVel[] = {0.0f, 0.0f, 0.1f};
//听者的位置.
static ALfloat ListenerPos[] = {0.0f, 0.0f, 0.0f};
//听者的速度
static ALfloat ListenerVel[] = {0.0f, 0.0f, 0.0f};
//听者的方向 (first 3 elements are "at", second 3 are "up")
static ALfloat ListenerOri[] = {0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f};
OpenAl播放类声明:
#define NUMBUFFERS 4
#define SERVICE_UPDATE_PERIOD (20)
class COpenAl
{
public:
COpenAl(char* pFileName);
~COpenAl(void);
void Play(void);
private:
int Initialize(void);
int LoadAudioFormat(void);
int GetFormat(const int nChannels, const int wBitsPerSample);
static DWORD WINAPI PlayThread(LPVOID lpParame);
ALuint m_uiBuffers[NUMBUFFERS];
ALuint m_uiSource;
ALuint m_uiBuffer;
ALint m_iState;
ALint m_iLoop;
ALint m_iBuffersProcessed, m_iTotalBuffersProcessed, m_iQueuedBuffers;
WAVEFORMATEX m_stWfx;
unsigned long m_ulDataSize;
unsigned long m_ulFrequency;
unsigned long m_ulFormat;
unsigned long m_ulBufferSize;
unsigned long m_ulBytesWritten;
void* m_pData;
CWavInfo* m_pWavInfo;
};
初始化函数:
int COpenAl::Initialize(void)
{
int iLoop;
unsigned int nSize = 0;
if(cInit.m_iHasInit == 0)
{
if(cInit.InitOpenAL() != 0)
{
return -1;
}
}
//从文件中读取wav格式
if(LoadAudioFormat() != 0)
{
return -1;
}
//设置播放的文件参数以及分配块内存
m_ulFormat = GetFormat(m_stWfx.nChannels, m_stWfx.wBitsPerSample);
m_ulFrequency = m_stWfx.nSamplesPerSec;
m_pData = malloc(BLOCKSIZE);
if(m_pData == NULL)
{
return -1;
}
alGenBuffers(NUMBUFFERS, m_uiBuffers);
alGenSources(1, &m_uiSource);
alSourcef(m_uiSource, AL_PITCH, 1.0f);
alSourcef(m_uiSource, AL_GAIN, 1.0f);
alSourcefv(m_uiSource, AL_POSITION, SourcePos);
alSourcefv(m_uiSource, AL_VELOCITY, SourceVel);
alSourcei(m_uiSource, AL_LOOPING, AL_FALSE);
alListenerfv(AL_POSITION, ListenerPos);
alListenerfv(AL_VELOCITY, ListenerVel);
alListenerfv(AL_ORIENTATION, ListenerOri);
if (alGetError() != AL_NO_ERROR)
{
return false;
}
//首次加载好缓冲区的数据,之后数据由播放线程填充
for(iLoop = 0; iLoop < NUMBUFFERS; ++iLoop)
{
if(m_pWavInfo != NULL &&
m_pWavInfo->m_nHasError == 0 &&
m_pWavInfo->ReadABlockData(m_pData, &nSize) == 0)
{
alBufferData(m_uiBuffers[iLoop], m_ulFormat, m_pData, nSize, m_ulFrequency);
alSourceQueueBuffers(m_uiSource, 1, &m_uiBuffers[iLoop]);
}
}
return 0;
}
播放线程,这个是参考SDK中的playstream的。
DWORD WINAPI COpenAl::PlayThread(LPVOID lpParame)
{
COpenAl *cOpenAl = (COpenAl*)lpParame;
unsigned int nBytesWritten = 0;
while(true)
{
//openal buffers中已经播放过的个数
cOpenAl->m_iBuffersProcessed = 0;
//得到空闲的buffer个数
alGetSourcei(cOpenAl->m_uiSource, AL_BUFFERS_PROCESSED, &cOpenAl->m_iBuffersProcessed);
//对于每一个空闲的buffer,填充新的数据并添加到播放队列中
while(cOpenAl->m_iBuffersProcessed)
{
//从队列中移除播放结束的buffer,并取得buffer的标志进行数据填充
cOpenAl->m_uiBuffer = 0;
alSourceUnqueueBuffers(cOpenAl->m_uiSource, 1, &cOpenAl->m_uiBuffer);
//读取数据,并检查文件是否播放完成
if(cOpenAl->m_pWavInfo != NULL &&
cOpenAl->m_pWavInfo->ReadABlockData(cOpenAl->m_pData, &nBytesWritten) == 0)
{
//拷贝数据到buffer中
alBufferData(cOpenAl->m_uiBuffer, cOpenAl->m_ulFormat, cOpenAl->m_pData,
nBytesWritten, cOpenAl->m_ulFrequency);
//将填充好的buffer添加到播放队列中
alSourceQueueBuffers(cOpenAl->m_uiSource, 1, &cOpenAl->m_uiBuffer);
}
cOpenAl->m_iBuffersProcessed--;
}
// Check the status of the Source. If it is not playing, then playback was completed,
// or the Source was starved of audio data, and needs to be restarted.
alGetSourcei(cOpenAl->m_uiSource, AL_SOURCE_STATE, &cOpenAl->m_iState);
if(cOpenAl->m_iState != AL_PLAYING)
{
// If there are Buffers in the Source Queue then the Source was starved of audio
// data, so needs to be restarted (because there is more audio data to play)
alGetSourcei(cOpenAl->m_uiSource, AL_BUFFERS_QUEUED, &cOpenAl->m_iQueuedBuffers);
if(cOpenAl->m_iQueuedBuffers)
{
alSourcePlay(cOpenAl->m_uiSource);
}
else
{
// Finished playing
break;
}
}
}
return 0;
}
void COpenAl::Play(void)
{
HANDLE pHandle;
DWORD result;
MSG msg;
//创建播放线程
pHandle = CreateThread(0, 0, PlayThread, this, NULL, NULL);
if(pHandle == NULL)
{
return;
}
while(true)
{
//等待播放线程结束并处理接收到的信号或者消息
result = MsgWaitForMultipleObjects(1, &pHandle, false, INFINITE, QS_ALLINPUT);
if(result == (WAIT_OBJECT_0))
{
break;
}
else
{
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
DispatchMessage(&msg);
}
}
alSourceStop(m_uiSource);
alSourcei(m_uiSource, AL_BUFFER, 0);
return;
}
一周多学习的东西终于整理完成!希望大家可以多交流~~
As I am a beginner with this, please tell me if there are any mistakes in this article!