ブログ@kaorun55

HoloLensやKinectなどのDepthセンサーを中心に書いています。

Xtion Pro LIVE + OpenNI で音声データを扱う( AudioGenerator )

OpenNIで(たぶん)唯一動作報告のない AudioGenerator が、Xtion Pro LIVE + OpenNI で動きました:-)

環境

AudioGeneratorの基本

音声の取得が確認できる最小限のコードです

こちらを参考にしました(中国語のサイトです)

簡単にコードの解説をします。

// コンテキストを初期化する
xn::Context context;
XnStatus nRetVal = context.Init();

// オーディオジェネレータを初期化する
xn::AudioGenerator audio;
nRetVal = audio.Create(context);

コンテキストの初期化と、オーディオジェネレータの作成です。
OpenNIのサンプル含め、XMLから初期化しノードを探していましたが、これだとデバイスがないとエラーになったので、ユーザージェネレーターと同様に、ジェネレーターから作成したところ、うまくいきました。

// WAVEデータの設定をする
XnWaveOutputMode waveMode;
waveMode.nSampleRate = 44100;
waveMode.nChannels = 2;
waveMode.nBitsPerSample = 16;
nRetVal = audio.SetWaveOutputMode(waveMode);

次に、入力するWAVEデータの設定です。周波数44.1kHz、2チャネル(ステレオ)、サンプリングレート16のデフォルト設定です。
OutputMapModeと同様に、XnWaveOutputMode と xn::AudioGenerator::SetWaveOutputMode() を使って設定します。

// データの取得を開始する
context.StartGeneratingAll();

データの取得を開始します。これをしないとデータの取得ができません。

// データの更新を待つ
nRetVal = context.WaitOneUpdateAll(audio);

// データを取得する
const XnUChar* pAudioBuf = audio.GetAudioBuffer();
XnUInt32 nBufSize = audio.GetDataSize();

イメージや距離と同様に、データの更新を待ち、データを取得すします。AudioMetaDataもありますが、バイト単位の処理はしないので、ジェネレータから直接取得しています。

入力した音声を、PCのスピーカーから出力する

OpenNIに付属のサンプルが、Windowsで動作させた場合に、WAVEデータとしてスピーカーから出力するようになっていたので、上記を踏まえて動作するようにしてみました。また、わかりやすいように組み直しました。

基本的なところは同じで、WAVE関連の処理が増えています。
実行すると、右のマイクの音が右のスピーカーに、左のマイクの音が左のスピーカーに出るようになりました。

#include <iostream>
#include <vector>

#include <XnCppWrapper.h>
#include <mmsystem.h>

#define NUMBER_OF_AUDIO_BUFFERS 100

class AudioOutput
{
    xn::Context context;
    xn::AudioGenerator audio;
    XnWaveOutputMode waveMode;

    HWAVEOUT hWaveOut;
    std::vector<WAVEHDR>  AudioBuffers;

public:

    // OpenNIの初期化
    void initOpenNI()
    {
        // コンテキストの初期化
        XnStatus nRetVal = context.Init();
        if (nRetVal != XN_STATUS_OK) {
            throw std::runtime_error(xnGetStatusString(nRetVal));
        }

        // デバイスを作成する(XMLからの生成だと、デバイスがないといわれる)
        nRetVal = audio.Create(context);
        if (nRetVal != XN_STATUS_OK) {
            throw std::runtime_error(xnGetStatusString(nRetVal));
        }

        // 取得するWAVEデータの設定
        waveMode.nSampleRate = 44100;
        waveMode.nChannels = 2;
        waveMode.nBitsPerSample = 16;
        nRetVal = audio.SetWaveOutputMode(waveMode);
        if (nRetVal != XN_STATUS_OK) {
            throw std::runtime_error(xnGetStatusString(nRetVal));
        }

        // データの取得を開始する
        context.StartGeneratingAll();
    }

    // WAVEの初期化
    void initWave()
    {
        // WAVEデータの設定
        WAVEFORMATEX wf;
        wf.wFormatTag = 0x0001; // PCM
        wf.nChannels = waveMode.nChannels;
        wf.nSamplesPerSec = waveMode.nSampleRate;
        wf.wBitsPerSample = waveMode.nBitsPerSample;
        wf.nBlockAlign = wf.wBitsPerSample * wf.nChannels / 8;
        wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec;
        MMRESULT mmRes = waveOutOpen(&hWaveOut, WAVE_MAPPER, &wf, NULL, NULL, CALLBACK_NULL);
        if (mmRes != MMSYSERR_NOERROR)
        {
            throw std::runtime_error( "Warning: Failed opening wave out device. Audio will not be played!\n" );
        }

        // 音声データ用のバッファの作成と初期化
        AudioBuffers.resize( NUMBER_OF_AUDIO_BUFFERS );
        xnOSMemSet(&AudioBuffers[0], 0, sizeof(WAVEHDR)*AudioBuffers.size());

        const XnUInt32 nMaxBufferSize = 2 * 1024 * 1024;
        for (int i = 0; i < NUMBER_OF_AUDIO_BUFFERS; ++i)
        {
            AudioBuffers[i].lpData = new XnChar[nMaxBufferSize];
            AudioBuffers[i].dwUser = i;
            AudioBuffers[i].dwFlags = WHDR_DONE; // mark this buffer as empty (already played)
        }
    }

    // メインループ
    void run()
    {
        int nAudioNextBuffer = 0;

        printf ("Press any key to exit...\n");

        // 今のデータを捨てる
        audio.WaitAndUpdateData();

        while (!xnOSWasKeyboardHit()) {
            // データの更新
            XnStatus nRetVal = audio.WaitAndUpdateData();
            if (nRetVal != XN_STATUS_OK) {
                throw std::runtime_error(xnGetStatusString(nRetVal));
            }

            // バッファの取得
            WAVEHDR* pHeader = &AudioBuffers[nAudioNextBuffer];
            if ((pHeader->dwFlags & WHDR_DONE) == 0) {
                printf("No audio buffer is available!. Audio buffer will be lost!\n");
                continue;
            }

            // WAVEヘッダのクリーンアップ
            MMRESULT mmRes = waveOutUnprepareHeader(hWaveOut, pHeader, sizeof(WAVEHDR));
            if ( mmRes != MMSYSERR_NOERROR ) {
                OutputErrorText( mmRes );
            }

            // WAVEデータの取得
            pHeader->dwBufferLength = audio.GetDataSize();
            pHeader->dwFlags = 0;
            xnOSMemCopy(pHeader->lpData, audio.GetAudioBuffer(), pHeader->dwBufferLength);

            // WAVEヘッダの初期化
            mmRes = waveOutPrepareHeader(hWaveOut, pHeader, sizeof(WAVEHDR));
            if ( mmRes != MMSYSERR_NOERROR ) {
                OutputErrorText( mmRes );
                continue;
            }

            // WAVEデータを出力キューに入れる
            mmRes = waveOutWrite(hWaveOut, pHeader, sizeof(WAVEHDR));
            if ( mmRes != MMSYSERR_NOERROR ) {
                OutputErrorText( mmRes );
                continue;
            }

            // 次のバッファインデックス
            nAudioNextBuffer = (nAudioNextBuffer + 1) % NUMBER_OF_AUDIO_BUFFERS;
        }
    }

private:

    // エラーメッセージの出力
    void OutputErrorText( MMRESULT mmRes )
    {
        CHAR msg[250];
        waveOutGetErrorText(mmRes, msg, 250);
        std::cout << msg << std::endl;
    }
};

void main()
{
    try {
        AudioOutput audio;
        audio.initOpenNI();
        audio.initWave();
        audio.run();
    }
    catch ( std::exception& ex ) {
        std::cout << ex.what() << std::endl;
    }
}