OggファイルをDirectSoundで再生する

OggVorbisファイルをDirectSoundで再生する方法のメモ書きです。

全体的な処理の流れは以下のようになります。

 OggVorbisファイルオープン
   ↓
 OggVorbisファイルのフォーマット・デコード後サイズ取得
   ↓
 DirectSoundの初期化
   ↓
 OggVorbisファイルをDirectSoundバッファにデコード
   ↓
 バッファの再生
   ↓
 後始末

以上の流れのソースコードを以下に記します。

#include <windows.h>
#include <stdexcept>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include <dsound.h>

#define RELEASE(obj) if ( obj ) { obj->Release(); obj = NULL; }

#pragma comment(lib, "libogg_static.lib")
#pragma comment(lib, "libvorbis_static.lib")
#pragma comment(lib, "libvorbisfile_static.lib")
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch ( uMsg ) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    OggVorbis_File vf;
    bool isOpened = false;

    IDirectSound8* pDS = NULL;
    IDirectSoundBuffer *pPrimaryBuffer = NULL;
    IDirectSoundBuffer8* pSecondaryBuffer = NULL;

    try {
        //---------------------------------------------------------
        //  Oggファイルの読み込み
        //---------------------------------------------------------
        LPCSTR fileName = "test.ogg";

        // OggVorbisファイルを開く
        if ( ov_fopen(fileName, &vf) )
            throw std::runtime_error("ファイルオープンに失敗しました。");

        isOpened = true;

        // OggVorbisファイルの情報を取得する
        vorbis_info *vi = ov_info(&vf, -1);

        if ( vi == NULL )
            throw std::runtime_error("OggVorbisファイルの情報取得に失敗しました。");

        // WAVEFORMATEX構造体へのデータの書き込み
        WAVEFORMATEX wf;
        wf.wFormatTag = WAVE_FORMAT_PCM;
        wf.nChannels = vi->channels;
        wf.nSamplesPerSec = vi->rate;
        wf.nAvgBytesPerSec = vi->rate * vi->channels * 2;
        wf.nBlockAlign = vi->channels * 2;
        wf.wBitsPerSample = 16;
        wf.cbSize = sizeof(WAVEFORMATEX);

        // デコード後のデータサイズの取得
        DWORD decodeSize = static_cast<dword>(ov_pcm_total(&vf, -1)) * vi->channels * 2;


        //---------------------------------------------------------
        //  再生用ウィンドウの作成
        //---------------------------------------------------------
        WNDCLASS wc;
        wc.style = CS_CLASSDC;
        wc.lpfnWndProc = WndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = static_cast<hbrush>(GetStockObject(WHITE_BRUSH));
        wc.lpszMenuName = NULL;
        wc.lpszClassName = TEXT("OggPlayTest");

        if ( !RegisterClass(&wc) )
            throw std::runtime_error("ウィンドウクラスの登録に失敗しました。");

        HWND hWnd = CreateWindow(
            wc.lpszClassName,
            TEXT("Ogg再生テスト"),
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            NULL,
            NULL,
            hInstance,
            NULL
        );

        if ( hWnd == NULL )
            throw std::runtime_error("ウィンドウの作成に失敗しました。");

        // ウィンドウの表示
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);


        //---------------------------------------------------------
        //  DirectSoundの初期化
        //---------------------------------------------------------

        // DirectSoundの初期化
        if ( FAILED(DirectSoundCreate8(NULL, &pDS, NULL)) )
            throw std::runtime_error("DirectSoundの初期化に失敗しました。");

        // 協調レベルの設定
        if ( FAILED(pDS->SetCooperativeLevel(hWnd, DSSCL_PRIORITY)) )
            throw std::runtime_error("協調レベルの設定に失敗しました。");

        // プライマリバッファの作成
        DSBUFFERDESC desc;
        desc.dwSize = sizeof(DSBUFFERDESC);
        desc.dwFlags = DSBCAPS_GLOBALFOCUS;
        desc.dwBufferBytes = decodeSize;
        desc.dwReserved = 0;
        desc.lpwfxFormat = &wf;
        desc.guid3DAlgorithm = GUID_NULL;

        if ( FAILED(pDS->CreateSoundBuffer(&desc, &pPrimaryBuffer, NULL)) )
            throw std::runtime_error("プライマリバッファの作成に失敗しました。");

        // セカンダリバッファの作成
        if ( FAILED(pPrimaryBuffer->QueryInterface(
                IID_IDirectSoundBuffer8,
                reinterpret_cast<lpvoid *>(&pSecondaryBuffer))) )
            throw std::runtime_error("セカンダリバッファの作成に失敗しました。");


        // プライマリバッファの開放
        RELEASE(pPrimaryBuffer);


        //---------------------------------------------------------
        //  OggVorbisファイルのデータをバッファにデコード
        //---------------------------------------------------------

        // バッファロック
        LPVOID pWrite;
        DWORD lockSize;
        if ( FAILED(pSecondaryBuffer->Lock(
                0,
                0,
                &pWrite,
                &lockSize,
                NULL,
                NULL,
                DSBLOCK_ENTIREBUFFER)) )
            throw std::runtime_error("バッファのロックに失敗しました。");

        DWORD totalReadSize = 0;
        INT bitstream;
        bool result = true;

        while ( totalReadSize < decodeSize ) {
            // データのデコード
            LONG readSize = ov_read(
                &vf,
                static_cast<char*>(pWrite) + totalReadSize,
                decodeSize - totalReadSize,
                0,
                2,
                1,
                &bitstream
                );

            if ( readSize == 0 ) {      // ファイルの終端まで読み込んだ
                result = true;
                break;
            }
            else if ( readSize < 0 ) {  // エラー発生
                result = false;
                break;
            }

            totalReadSize += readSize;
        }

        // アンロック
        pSecondaryBuffer->Unlock(pWrite, lockSize, NULL, 0);

        // ファイル読み込みの成否チェック
        if ( !result )
            throw std::runtime_error("OggVorbisファイルの読み込みに失敗しました。");


        // OggVorbisファイルを閉じる
        ov_clear(&vf);


        // 再生
        pSecondaryBuffer->Play(0, 0, 0);

        // ウィンドウメッセージループ
        MSG msg;
        while ( GetMessage(&msg, NULL, 0, 0) > 0 ) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // 停止
        pSecondaryBuffer->Stop();


        // バッファ開放
        RELEASE(pDS);


    }
    catch ( std::exception& e ) {
        if ( isOpened )
            ov_clear(&vf);

        RELEASE(pDS);

        MessageBoxA(NULL, e.what(), NULL, MB_OK | MB_ICONERROR);
        return -1;
    }

    return 0;
}

長い上汚いコードになってしまいました(泣)
実際に使うときはこのようなベタ書きはせずにクラス化や関数分割しましょう。

順を追って説明していきます。

1.ヘッダファイル、ライブラリの準備

まず、必要なヘッダファイルとライブラリをインクルードします。

#include <windows.h>
#include <stdexcept>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include <dsound.h>

#pragma comment(lib, "libogg_static.lib")
#pragma comment(lib, "libvorbis_static.lib")
#pragma comment(lib, "libvorbisfile_static.lib")
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")

OggVorbisのAPIを使用するためには、vorbis/codec.hとvorbis/vorbisfile.hをインクルードします。
また、libogg_static.lib、libvorbis_static.lib、libvorbisfile_static.libをインポートします。

DirectSoundのAPIを使用するためには、dsound.hをインクルードし、dsound.libをインポートします。
dxguid.libをインポートしているのは、DirectSoundのCOMオブジェクトのGUIDを扱えるようにするためです。

2.OggVorbisファイルの読み込み準備
OggVorbisファイルを開いて必要な情報を読み込みます。
この時点ではまだデータのデコードは行いません。

//---------------------------------------------------------
        //  Oggファイルの読み込み
        //---------------------------------------------------------
        LPCSTR fileName = "test.ogg";

        // OggVorbisファイルを開く
        if ( ov_fopen(fileName, &vf) )
            throw std::runtime_error("ファイルオープンに失敗しました。");

        isOpened = true;

        // OggVorbisファイルの情報を取得する
        vorbis_info *vi = ov_info(&vf, -1);

        if ( vi == NULL )
            throw std::runtime_error("OggVorbisファイルの情報取得に失敗しました。");

        // WAVEFORMATEX構造体へのデータの書き込み
        WAVEFORMATEX wf;
        wf.wFormatTag = WAVE_FORMAT_PCM;
        wf.nChannels = vi->channels;
        wf.nSamplesPerSec = vi->rate;
        wf.nAvgBytesPerSec = vi->rate * vi->channels * 2;
        wf.nBlockAlign = vi->channels * 2;
        wf.wBitsPerSample = 16;
        wf.cbSize = sizeof(WAVEFORMATEX);

        // デコード後のデータサイズの取得
        DWORD decodeSize = static_cast<dword>(ov_pcm_total(&vf, -1)) * vi->channels * 2;

3.再生用ウィンドウの作成
DirectSoundの再生対象となるウィンドウを作成します。
簡単のため、何も無いウィンドウにします。

        //---------------------------------------------------------
        //  再生用ウィンドウの作成
        //---------------------------------------------------------
        WNDCLASS wc;
        wc.style = CS_CLASSDC;
        wc.lpfnWndProc = WndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hInstance;
        wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = static_cast<hbrush>(GetStockObject(WHITE_BRUSH));
        wc.lpszMenuName = NULL;
        wc.lpszClassName = TEXT("OggPlayTest");

        if ( !RegisterClass(&wc) )
            throw std::runtime_error("ウィンドウクラスの登録に失敗しました。");

        HWND hWnd = CreateWindow(
            wc.lpszClassName,
            TEXT("Ogg再生テスト"),
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            NULL,
            NULL,
            hInstance,
            NULL
        );

        if ( hWnd == NULL )
            throw std::runtime_error("ウィンドウの作成に失敗しました。");

        // ウィンドウの表示
        ShowWindow(hWnd, nCmdShow);
        UpdateWindow(hWnd);

4.DirectSoundの初期化
DirectSoundを初期化します。
ここで再生対象のウィンドウハンドルを指定し、再生用バッファの作成を行います。

        //---------------------------------------------------------
        //  DirectSoundの初期化
        //---------------------------------------------------------

        // DirectSoundの初期化
        if ( FAILED(DirectSoundCreate8(NULL, &pDS, NULL)) )
            throw std::runtime_error("DirectSoundの初期化に失敗しました。");

        // 協調レベルの設定
        if ( FAILED(pDS->SetCooperativeLevel(hWnd, DSSCL_PRIORITY)) )
            throw std::runtime_error("協調レベルの設定に失敗しました。");

        // プライマリバッファの作成
        DSBUFFERDESC desc;
        desc.dwSize = sizeof(DSBUFFERDESC);
        desc.dwFlags = DSBCAPS_GLOBALFOCUS;
        desc.dwBufferBytes = decodeSize;
        desc.dwReserved = 0;
        desc.lpwfxFormat = &wf;
        desc.guid3DAlgorithm = GUID_NULL;

        if ( FAILED(pDS->CreateSoundBuffer(&desc, &pPrimaryBuffer, NULL)) )
            throw std::runtime_error("プライマリバッファの作成に失敗しました。");

        // セカンダリバッファの作成
        if ( FAILED(pPrimaryBuffer->QueryInterface(
                IID_IDirectSoundBuffer8,
                reinterpret_cast<lpvoid *>(&pSecondaryBuffer))) )
            throw std::runtime_error("セカンダリバッファの作成に失敗しました。");


        // プライマリバッファの開放
        RELEASE(pPrimaryBuffer);

バッファの作成はほぼ処理が決まっています。
今回はデコード後全体のサイズ分のバッファを作成しています。

5.OggVorbisデータをDirectSoundバッファにデコードする
バッファの作成が出来たため、いよいよデコードを実行します。
作成したバッファにOggVorbisデータをデコードします。

        //---------------------------------------------------------
        //  OggVorbisファイルのデータをバッファにデコード
        //---------------------------------------------------------

        // バッファロック
        LPVOID pWrite;
        DWORD lockSize;
        if ( FAILED(pSecondaryBuffer->Lock(
                0,
                0,
                &pWrite,
                &lockSize,
                NULL,
                NULL,
                DSBLOCK_ENTIREBUFFER)) )
            throw std::runtime_error("バッファのロックに失敗しました。");

        DWORD totalReadSize = 0;
        INT bitstream;
        bool result = true;

        while ( totalReadSize < decodeSize ) {
            // データのデコード
            LONG readSize = ov_read(
                &vf,
                static_cast<char*>(pWrite) + totalReadSize,
                decodeSize - totalReadSize,
                0,
                2,
                1,
                &bitstream
                );

            if ( readSize == 0 ) {      // ファイルの終端まで読み込んだ
                result = true;
                break;
            }
            else if ( readSize < 0 ) {  // エラー発生
                result = false;
                break;
            }

            totalReadSize += readSize;
        }

        // アンロック
        pSecondaryBuffer->Unlock(pWrite, lockSize, NULL, 0);

        // ファイル読み込みの成否チェック
        if ( !result )
            throw std::runtime_error("OggVorbisファイルの読み込みに失敗しました。");


        // OggVorbisファイルを閉じる
        ov_clear(&vf);

今回のプログラムの場合、デコードしたらOggVorbisファイルへのアクセスは必要なくなるため、ファイルを閉じます。

6.再生
デコードされたデータが格納されたDirectSoundバッファを再生します。

        // 再生
        pSecondaryBuffer->Play(0, 0, 0);

        // ウィンドウメッセージループ
        MSG msg;
        while ( GetMessage(&msg, NULL, 0, 0) > 0 ) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

再生中は空ウィンドウを表示させ続けるようにします。
今回はウィンドウを閉じると以降の後始末を行うようにしました。

7.後始末
再生を停止してからDirectSoundオブジェクトの後始末を行います。
これによりDirectSound関連のリソースはすべて開放されます。

        // 停止
        pSecondaryBuffer->Stop();

        // バッファ開放
        RELEASE(pDS);

以上のような流れでOggVorbisファイルをDirectSoundで再生できます。
既にお気づきの方もいらっしゃるかもしれませんが、OggVorbisのデコード処理はループ文の中で行っています。
したがって、ストリーミング再生も簡単に実現できます。
サイズの小さな効果音ファイルの再生では、メモリ上にデータをすべて展開しても問題ないですが、BGMファイルのような巨大なファイルの場合はストリーミング再生が必要不可欠になるでしょう。

COMMENTS & TRACKBACKS

  • Comments ( 2 )
  • Trackbacks ( 0 )
  1. By OGG 再生

    この記事のお陰で再生出来ました!! 有難うございました!!

    • By ftvoid

      コメントありがとうございます!
      無事OggVorbis形式のファイルを再生できたということで、良かったです。