OpenAl音频播放

来源:互联网 发布:软件功能模块接口列表 编辑:程序博客网 时间:2024/05/21 18:46

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);//重置当前文本描述为NULLalcMakeContextCurrent(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);voidPlay(void);private:intInitialize(void);intLoadAudioFormat(void);intGetFormat(const int nChannels, const int wBitsPerSample);static DWORDWINAPI PlayThread(LPVOID lpParame);ALuint    m_uiBuffers[NUMBUFFERS];ALuint    m_uiSource;ALuintm_uiBuffer;ALintm_iState;ALintm_iLoop;ALintm_iBuffersProcessed, m_iTotalBuffersProcessed, m_iQueuedBuffers;WAVEFORMATEXm_stWfx;unsigned longm_ulDataSize;unsigned longm_ulFrequency;unsigned longm_ulFormat;unsigned longm_ulBufferSize;unsigned longm_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 playingbreak;}}}return 0;}

最后是播放的入口函数,这样就是结束了。

void COpenAl::Play(void){HANDLEpHandle;DWORDresult; MSGmsg;//创建播放线程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!

原创粉丝点击