ホームに戻る
 DirectSoundメモ

1、はじめに

DirectSound は WAVE フォーマットのみを扱います。
MIDI であれば DirectMusic を使うべきであるし、
その他のフォーマットであれば WAVE 形式への変換が必要。

DirectSound の利点

ハードウェア上のバッファにおける複数の音のミックス。
前後左右を考慮した3Dサウンドの実現。
リバーブ等のエフェクトの実装。
音声入力を加工して即出力などの高速な入力処理。

音を出す手順は、
まずプライマリバッファを用意する。
プライマリバッファは1つでサイズはこちらで指定できない。
次にセカンダリバッファを用意。
セカンダリバッファは複数用意できサイズも自由。
セカンダリバッファに音データを入れ再生すると、
実際にはプライマリバッファにミックスされて再生される。

高速化において重要なのはバッファのフォーマットをそろえること。
周波数やチャンネルが異なると余分なCPUパワーを使う。
(8ビットか16ビットかは問わないそうです。)

長時間の連続再生にはバッファのサイズが確保できないため、
1本のバッファをリングバッファのように用いる。
実際にはバッファ中の何点かにシグナルを切り換えるイベントを用意し、
再生途中でこの点を通るとシグナル状態になる仕組みを利用する。
スレッド内でイベントを待機しイベントが来たらバッファを書きかえる。
DirectSound はこの方法を定石としているようである。

WAVE ファイルの扱いに手間がかかる。
まずこちらを扱うライブラリから作り始めたほうが良さそう。

高速化のために、
バッファは1〜2秒のものを用意し位置通知は2箇所が適切。
また、エフェクトやパンの設定は使わないのなら初期設定の時点で設定しない。
再生中にパン等の切り替えを行うとバッファは一度フラッシュされる、
再生中のパラメータ切り替えは負荷となる。
バッファは最初に初期化したものを先にハードウェアに割り当てる、
よく使うバッファほど先に初期化しておくべきである。

2、初期化

初期化は簡単です。成功だと DS_OK を返します。

LPDIRECTSOUND8 pDS = NULL;
HRESULT hr;
hr = DirectSoundCreate8(NULL, &pDS, NULL);
hr = pDS->SetCooperativeLevel(hWnd, DSSCL_PRIORITY);

3、プライマリバッファの作成

プライマリバッファのサイズには 0 を指定します。
0 を指定しますが実際は必要なサイズが割り当てられます。

LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;
HRESULT hr;

// プライマリバッファの作成

DSBUFFERDESC dsbd;
ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbd.dwBufferBytes = 0;

hr = pDS->CreateSoundBuffer(&dsbd, &pDSBPrimary, NULL);

// フォーマットの指定

WAVEFORMATEX wfx;
ZeroMemory(&wfx, sizeof(WAVEFORMATEX)); 
wfx.wFormatTag = (WORD)WAVE_FORMAT_PCM; 
wfx.nChannels = (WORD)2; 
wfx.nSamplesPerSec = (DWORD)44100; 
wfx.wBitsPerSample = (WORD)16; 
wfx.nBlockAlign = (WORD)(wfx.wBitsPerSample / 8 * wfx.nChannels);
wfx.nAvgBytesPerSec = (DWORD)(wfx.nSamplesPerSec * wfx.nBlockAlign);

hr = pDSBPrimary->SetFormat(&wfx);

4、セカンダリバッファの作成

フォーマットの指定が先になるのがプライマリと異なる。

LPDIRECTSOUNDBUFFER pDSBSecondry = NULL;
HRESULT hr;
WAVEFORMATEX wfx;

DSBUFFERDESC dsbd;
ZeroMemory(&wfx, sizeof(WAVEFORMATEX)); 
wfx.wFormatTag = (WORD)WAVE_FORMAT_PCM; 
wfx.nChannels = (WORD)2; 
wfx.nSamplesPerSec = (DWORD)44100; 
wfx.wBitsPerSample = (WORD)16; 
wfx.nBlockAlign = (WORD)(wfx.wBitsPerSample / 8 * wfx.nChannels);
wfx.nAvgBytesPerSec = (DWORD)(wfx.nSamplesPerSec * wfx.nBlockAlign);

ZeroMemory(&dsbd, sizeof(DSBUFFERDESC));
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS;
dsbd.dwBufferBytes = wfx.nAvgBytesPerSec * 2; // 2 sec
dsbd.lpwfxFormat = &wfx;

hr = pDS->CreateSoundBuffer(&dsbd, &pDSBSecondry, NULL);

パラメータの一部には以下のようなものがある。

DSBCAPS_CTRLFREQUENCY 周波数コントロール能力を有効に
DSBCAPS_CTRLFX エフェクト処理を有効に
DSBCAPS_CTRLPAN パン コントロール能力を有効に
DSBCAPS_CTRLVOLUME ボリューム コントロール能力を有効に
DSBCAPS_CTRLPOSITIONNOTIFY 位置通知機能を持つ
DSBCAPS_LOCHARDWARE ハードウェアミキシングを行う
DSBCAPS_LOCSOFTWARE ソフトウェアミキシングを行う
DSBCAPS_STATIC バッファをハードウェアに配置

5、書き込み(Lock と Unlock)
 
下の例では 0 の位置から 352800 バイトのサイズを確保しています。
バッファが作成され先頭ポインタとサイズを得ることができます。
pDSLockedBuffer からLRLR・・・の順でデータを書き込めば良い。
16ビットの場合縦の中心は 0 だが8ビットは 128 なので注意。

VOID *pDSLockedBuffer = NULL;
DWORD dwDSLockedBufferSize = 0;
HRESULT hr;

hr = pDSBSecondry->Lock(0, 352800, &pDSLockedBuffer, &dwDSLockedBufferSize, NULL, NULL, 0L);

// ここで書き込み

pDSBSecondry->Unlock(pDSLockedBuffer, dwDSLockedBufferSize, NULL, 0);

6、再生と停止

Stop しても位置はそのままなのに注意。

pDSBSecondry->SetCurrentPosition(0);
pDSBSecondry->Play(0, 0, 0); // 第3引数は DSBPLAY_LOOPING でループ
pSBSecondry->Stop();

7、連続再生

真ん中と終点の2点でイベントを発生させる。
IID_IDirectSoundNotify は dxguid.lib で定義されているので注意。

// イベントとスレッドの作成

HANDLE g_hNotificationEvent = NULL;
DWORD g_dwNotifyThreadID = 0;
HANDLE g_hNotifyThread = NULL;

g_hNotificationEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
g_hNotifyThread = CreateThread(NULL, 0, NotificationProc, hWnd, 0, &g_dwNotifyThreadID);

// バッファ内のイベント発生ポイントの設定

LPDIRECTSOUNDNOTIFY pDSNotify;
DSBPOSITIONNOTIFY aPosNotify[2];
HRESULT hr;

aPosNotify[0].dwOffset = dwBufferSize / 2 - 1;
aPosNotify[0].hEventNotify = g_hNotificationEvent;
aPosNotify[1].dwOffset = dwBufferSize - 1;
aPosNotify[1].hEventNotify = g_hNotificationEvent;

hr = pSBSecondry->QueryInterface(IID_IDirectSoundNotify, (VOID**)&pDSNotify);
pDSNotify->SetNotificationPositions(2, aPosNotify); // 2つ

// スレッド処理

DWORD WINAPI NotificationProc(LPVOID lpParameter){
  MSG msg;
  HWND hWnd = (HWND)lpParameter;
  BOOL bDone = FALSE;
  DWORD dwResult;

  while(!bDone){
    dwResult = MsgWaitForMultipleObjects(1, &g_hNotificationEvent, FALSE, INFINITE, QS_ALLEVENTS);

    switch(dwResult)
    {
    case WAIT_OBJECT_0 + 0:
      // バッファの書き換え
      break;
    case WAIT_OBJECT_0 + 1:
      while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){ 
        if(msg.message == WM_QUIT){
          bDone = TRUE;
        }
      }
      break;
    }
  }
  return 0;
}

8、効果

バッファの初期化の段階でパラメータを与える必要あり。

SetFrequency:Hz単位で設定可
SetPan:-10,000〜10,000 中央は 0
SetVolume:0〜-10,000 最大が 0

9、後片付け

if(pDSNotify){
  pDSNotify->Release();
  pDSNotify = NULL;
}
if(pSBSecondry){
  pSBSecondry->Release();
  pSBSecondry = NULL;
}
if(pSBPrimary){
  pSBPrimary->Release();
  pSBPrimary = NULL;
}
if(pDS){
  pDS->Release();
  pDS = NULL;
}

inserted by FC2 system