プライマリ バッファへの書き込み

セカンダリ バッファがサポートしていない特別なミキシングや他のエフェクトを必要とするアプリケーションのために、DirectSound ではプライマリ バッファへの直接アクセスをが許されている。

プライマリ バッファへの書き込みアクセスを取得すると、DirectSound の他の機能は利用できなくなる。セカンダリ バッファのミキシングも行われないので、ハードウェア アクセラレーション ミキシングも利用できなくなる。

アプリケーションは、通常、プライマリ バッファへの直接アクセスではなく、セカンダリ バッファを使う必要がある。セカンダリ バッファへの書き込みは簡単に行うことができる。バッファ サイズが大きいため、次のデータ ブロックの書き込みまでの時間が長くなり、オーディオの音が途切れる危険が最小限に抑えられる。アプリケーションが必要とするオーディオが、ミキシングを必要としない 1 つのオーディオ ストリームのような単純なものであっても、セカンダリ バッファを使ってオーディオ データを再生することにより、より高い性能が実現できる。

プライマリ バッファへの直接の書き込みは、Windows Driver Model (WDM) では利点がない。WDM では、プライマリ バッファは、事実上、カーネル ミキサによってミキシングされるセカンダリ バッファである。WDM の詳細については、「DirectSound ドライバ モデル」を参照すること。

プライマリ バッファのサイズは指定できず、バッファを作成した後に返されるサイズを受け入れなければならない。通常、プライマリ バッファは極めて小さいので、アプリケーションがこの種のバッファに直接書き込む場合は、短い間隔でデータ ブロックを書き込み、以前のデータがもう一度再生されることを防がなければならない。

プライマリ バッファがハードウェア上に存在していない限り、そのバッファの書き込みアクセスは取得できない。これに該当するかどうかを確認するには、IDirectSoundBuffer8::GetCaps メソッドを呼び出して、DSBCAPS 構造体の dwFlags メンバで DSBCAPS_LOCHARDWARE フラグの有無をチェックする。ソフトウェアでエミュレートされたプライマリ バッファをロックしようとすると、呼び出しは失敗する。

アクセス可能なプライマリ バッファを作成するには、DSBUFFERDESC 構造体に DSBCAPS_PRIMARYBUFFER フラグを設定して IDirectSound8::CreateSoundBuffer メソッドに渡す。バッファに書き込む場合、協調レベルは DSSCL_WRITEPRIMARY でなければならない。

プライマリ サウンド バッファは、ループして再生しなければならない。そのためには、DSBPLAY_LOOPING フラグを必ず設定する。

次の例は、プライマリ バッファへの書き込みアクセスを取得する方法を示している。プライマリ バッファは IDirectSoundBuffer インターフェイスだけをサポートし、IDirectSoundBuffer8 をサポートしていないことに注意すること。

BOOL AppCreateWritePrimaryBuffer( 
  LPDIRECTSOUND8 lpDirectSound, 
  LPDIRECTSOUNDBUFFER *lplpDsb, 
  LPDWORD lpdwBufferSize, 
  HWND hwnd) 
{ 
  DSBUFFERDESC dsbdesc; 
  DSBCAPS dsbcaps; 
  HRESULT hr; 
  WAVEFORMATEX wf;
 
  // ウェーブ フォーマットの構造体を設定する。
  memset(&wf, 0, sizeof(WAVEFORMATEX)); 
  wf.wFormatTag = WAVE_FORMAT_PCM; 
  wf.nChannels = 2; 
  wf.nSamplesPerSec = 22050; 
  wf.nBlockAlign = 4; 
  wf.nAvgBytesPerSec = 
      wf.nSamplesPerSec * wf.nBlockAlign; 
  wf.wBitsPerSample = 16; 
 
  // DSBUFFERDESC 構造体を設定する。
  memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
  dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
  dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER; 
  // バッファ サイズは、サウンド ハードウェアによって決定される。
  dsbdesc.dwBufferBytes = 0; 
  dsbdesc.lpwfxFormat = NULL; // プライマリ バッファの場合は NULL でなければならない。
 
  // 書き込み優先協調レベルを取得する。
  hr = lpDirectSound->SetCooperativeLevel(hwnd, DSSCL_WRITEPRIMARY); 
  if SUCCEEDED(hr) 
  { 
    // バッファの作成を試みる。
    hr = lpDirectSound->CreateSoundBuffer(&dsbdesc, 
      lplpDsb, NULL); 
    if SUCCEEDED(hr) 
    { 
      // プライマリ バッファを目的のフォーマットに設定する。
      hr = (*lplpDsb)->SetFormat(&wf); 
      if SUCCEEDED(hr) 
      { 
        // バッファ サイズを知る必要がある場合は、GetCaps を呼び出す。
        dsbcaps.dwSize = sizeof(DSBCAPS); 
        (*lplpDsb)->GetCaps(&dsbcaps); 
        *lpdwBufferSize = dsbcaps.dwBufferBytes; 
        return TRUE; 
      } 
    } 
  } 
  // 失敗した。
  *lplpDsb = NULL; 
  *lpdwBufferSize = 0; 
  return FALSE; 
} 
 

次の例は、アプリケーションがカスタム ミキサを実装する方法を示している。サウンド デバイスがデータ ブロックの再生を繰り返すことのない十分に短い一定間隔で、AppMixIntoPrimaryBuffer サンプル関数を呼び出さなければならない。CustomMixer 関数はアプリケーションで定義された関数であり、アプリケーションで定義された APPSTREAMINFO 構造体の指定に従って複数のストリームをミキシングし、指定されたポインタに結果を書き込む。

BOOL AppMixIntoPrimaryBuffer( 
    APPSTREAMINFO* lpAppStreamInfo, 
    LPDIRECTSOUNDBUFFER lpDsbPrimary, 
    DWORD dwDataBytes, 
    DWORD dwOldPos, 
    LPDWORD lpdwNewPos) 
{ 
  LPVOID lpvPtr1; 
  DWORD dwBytes1; 
  LPVOID lpvPtr2; 
  DWORD dwBytes2; 
  HRESULT hr; 
  // 書き込みポインタを取得する。
  hr = lpDsbPrimary->Lock(dwOldPos, dwDataBytes, 
          &lpvPtr1, &dwBytes1, 
          &lpvPtr2, &dwBytes2, 0); 
 
  // DSERR_BUFFERLOST が返った場合は、復元してロックをリトライする。
 
  if (DSERR_BUFFERLOST == hr) 
  { 
    lpDsbPrimary->Restore(); 
    hr = lpDsbPrimary->Lock(dwOldPos, dwDataBytes,
            &lpvPtr1, &dwBytes1, 
            &lpvPtr2, &dwBytes2, 0); 
  } 
  if SUCCEEDED(hr) 
  { 
    // データをミキシングして、返されたポインタの位置に格納する。
    CustomMixer(lpAppStreamInfo, lpvPtr1, dwBytes1); 
    *lpdwNewPos = dwOldPos + dwBytes1; 
    if (NULL != lpvPtr2) 
    { 
      CustomMixer(lpAppStreamInfo, lpvPtr2, dwBytes2); 
      *lpdwNewPos = dwBytes2; // ラップ アラウンドしたため
    } 
    // データを解放して DirectSound に戻す。
    hr = lpDsbPrimary->Unlock(lpvPtr1, dwBytes1, 
            lpvPtr2, dwBytes2); 
    if SUCCEEDED(hr) 
    { 
      return TRUE; 
    } 
  } 
  // ロックまたはアンロックが失敗した。
  return FALSE; 
}