2018年12月10日 星期一

初探OpenAL

初探OpenAL

前言

  在前篇初探XAudio2裡使用了XAudio2來播放聲音,這次來使用OpenAL來播放聲音,為什麼使用OpenAL呢?因為Linux裡沒有XAudio2可以使用,在Linux有一套名為ALSA的API,但查了一下授權條款是GPL,所以決定放棄這一套API,所以在Linux使用OpenAL來實現播放聲音,在此把研究過程做個紀錄。

內容
  在開始之前,請先到OpenAL官網下載SDK,請下載下圖所示選項
下載OpenAL SDK
下載後解壓安裝即可,這樣才有OpenAL的環境與C++的Header和Lib可以用。請在專案中新增Lib路徑,路徑為"(SDK directory)/libs",檔名為"OpenAL32.lib",include路徑為"(SDK directory)/include"。

  OpenAL的範例可以在"(SDK directory)/samples"裡找到,但範例有用framework,如果需要教學網站的範例的可以參照OpenAL: Managing Playback StreamsOpenAL short example。在OpenAL的開始需要ALCdevice與ALCcontext,範例如下
#include <AL/al.h>
#include <AL/alc.h>

int main()
{
//
  ALCdevice* pDevice=alcOpenDevice(NULL);
  if(pDevice==NULL)
    return 1;
  ALCcontext* pContext=alcCreateContext(pDevice,NULL);
  if(pContext==NULL)
  {
    alcCloseDevice(pDevice);
    return 1;
  }
  if(!alcMakeContextCurrent(pContext) )
  {
    alcDestroyContext(pContext);
    alcCloseDevice(pDevice);
    return 1;
  }
  //OpenAL was ready...
  //...
  //program end...
  alcDestroyContext(pContext);
  alcCloseDevice(pDevice);
  return 0;
}

alcMakeContextCurrent()非常重要,如果這個步驟沒成功,所有的OpenAL資源將無法製造。

  接著來製造播放聲音,範例如下
#include <AL/al.h>
#include <AL/alc.h>
#include <windows.h>
ALenum GetChannelFormat(short channels,short samples)
{
  ALenum val=AL_INVAILD_VALUE;
  switch(samples)
  {
  case 16:
    if(channels>1)
      val=AL_FORMAT_STEREO16;
    else
      val=AL_FORMAT_MONO16;
    break;
  case 8:
    if(channels>1)
      val=AL_FORMAT_STEREO8;
    else
      val=AL_FORMAT_MONO8;
    break;
  }
  return val;
}
int main()
{
//
  ALCdevice* pDevice=alcOpenDevice(NULL);
  if(pDevice==NULL)
    return 1;
  ALCcontext* pContext=alcCreateContext(pDevice,NULL);
  if(pContext==NULL)
  {
    alcCloseDevice(pDevice);
    return 1;
  }
  if(!alcMakeContextCurrent(pContext) )
  {
    alcDestroyContext(pContext);
    alcCloseDevice(pDevice);
    return 1;
  }
  //OpenAL was ready...
  ALuint sourceID;
  alGenSource(1,&sourceID);
  if(alGetError()!=AL_NO_ERROR)
  {
    alcDestroyContext(pContext);
    alcCloseDevice(pDevice);
    return 1;
  }
  //Follow data load from WAV file
  WAVEFORMATEX format;
  unsigned char* pWAVRawData;
  size_t wavRawDataSize;
  //
  ALunit bufID;
  alGenBuffers(1,&bufID);
  alBufferData(bufID,GetChannelFormat(format.nChannels,format.wBitsPerSample),pWAVRawData,format.nSamplesPerSec);

  //Enqueue buffer
  alSoruceQueueBuffers(sourceID,1,&bufID);
  //Play
  alSourcePlay(sourceID);
  //wait play end
  ALint sourceState;
  alGetSourcei(sourceID, AL_SOURCE_STATE, &sourceState);
  while(sourceState==AL_PLAYING)
    ::Sleep(1);//sleep 1ms
  //
  //program end...
  alcDestroyContext(pContext);
  alcCloseDevice(pDevice);
  return 0;
}

流程的部分跟XAudio2很像,製造播放聲音的元件再推入Buffer來播放聲音,利用alGetSourcei()來確認是否播放完畢。

  最後來說Stream play的部分,XAudio2的Dequeue是自動的,但OpenAL的Dequeue的流程要手動,範例如下
ALuint tempBufferID=0;
alSourceUnqueueBuffers(sourceID,1,tempBufferID);
if(tempBufferID!=0)
{
  //Dequeue ok!
  //...
}

過程不會太難,但什麼時候會Dequeue會失敗?當這個Buffer還在播放中的話,BufferID就會錶持為0,如果你看OpenAL: Managing Playback Streams裡的話,會發現它使用alGetSourcei()透過AL_BUFFERS_PROCESSED來得知Buffer是否播放完畢,但查到這篇AL_BUFFERS_PROCESSED bug?所以我不打算用範例的方法檢測是否播完。

  整體來說XAudio2與OpenAL的用法很像,目前差異只有在Dequeue的部分,如果要整合的話相信並不困難,OpenAL可以用在Windows與Linux,但XAudio2卻只能跑在Windows,整合到底有沒有價值?只能日後再來檢驗了。

參考資料

ALSA
OpenAL官網
OpenAL: Managing Playback Streams
OpenAL short example
AL_BUFFERS_PROCESSED bug?

相關文章

WAV檔案格式學習心得
初探XAudio2

沒有留言:

張貼留言