このエントリはOpenNI Advent Calendar 2011 : ATNDの12月11日分です!!
Advent Calendarでの、僕の全プロジェクトはこちらです
Xtionについたマイクから入力された音を、PCのスピーカーから出力してみましょう
Xtionから入力される音声データはWAVE形式なのですが、C#でWAVEデータをストリーミング出力する方法がわからなかったので、C++でやった方法を使っています。CのAPIをC++/CLIでラップしC#で使えるようにしています。
StreamingWavePlayer
// StreamingWavePlayer.h #pragma once #include <Windows.h> #include <mmsystem.h> #pragma comment( lib, "winmm.lib" ) using namespace System; using namespace System::Runtime::InteropServices; namespace Win32 { public ref class StreamingWavePlayer { public: StreamingWavePlayer( int nSampleRate, int nBitsPerSample, Byte nChannels, int audioBufferCount ) { audioBufferCount_ = audioBufferCount; audioBufferNextIndex_ = 0; // WAVEデータの設定 WAVEFORMATEX wf; wf.wFormatTag = 0x0001; // PCM wf.nChannels = nChannels; wf.nSamplesPerSec = nSampleRate; wf.wBitsPerSample = nBitsPerSample; wf.nBlockAlign = wf.wBitsPerSample * wf.nChannels / 8; wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; handle_ = new HWAVEOUT; MMRESULT mmRes = ::waveOutOpen( handle_, WAVE_MAPPER, &wf, NULL, NULL, CALLBACK_NULL ); if ( mmRes != MMSYSERR_NOERROR ) { throw gcnew Exception( "waveOutOpen failed\n" ); } // 音声データ用のバッファの作成と初期化 waveHeaders_ = new WAVEHDR[audioBufferCount_]; memset( &waveHeaders_[0], 0, sizeof(WAVEHDR) * audioBufferCount_ ); for ( int i = 0; i < audioBufferCount_; ++i ) { waveHeaders_[i].lpData = new char[MAX_BUFFER_SIZE]; waveHeaders_[i].dwUser = i; waveHeaders_[i].dwFlags = WHDR_DONE; } } !StreamingWavePlayer() { ::waveOutPause( *handle_ ); for ( int i = 0; i < audioBufferCount_; ++i ) { delete[] waveHeaders_[i].lpData; } delete waveHeaders_; delete handle_; } ~StreamingWavePlayer() { this->!StreamingWavePlayer(); } void Output( IntPtr buffer, int length ) { // バッファの取得 WAVEHDR* pHeader = &waveHeaders_[audioBufferNextIndex_]; if ( (pHeader->dwFlags & WHDR_DONE) == 0 ) { return; } // WAVEデータの取得 pHeader->dwBufferLength = length; pHeader->dwFlags = 0; CopyMemory( pHeader->lpData, (void*)buffer, length ); Output( pHeader ); } void Output( array<Byte>^ buffer ) { WAVEHDR* pHeader = GetBuffer(); if ( pHeader == 0 ) { return; } // WAVEデータの取得 pHeader->dwBufferLength = buffer->Length; pHeader->dwFlags = 0; Marshal::Copy( buffer, 0, (IntPtr)pHeader->lpData, buffer->Length ); Output( pHeader ); } private: WAVEHDR* GetBuffer() { // バッファの取得 WAVEHDR* pHeader = &waveHeaders_[audioBufferNextIndex_]; if ( (pHeader->dwFlags & WHDR_DONE) == 0 ) { return 0; } // WAVEヘッダのクリーンアップ MMRESULT mmRes = ::waveOutUnprepareHeader( *handle_, pHeader, sizeof(WAVEHDR) ); if ( mmRes != MMSYSERR_NOERROR ) { return 0; } return pHeader; } void Output( WAVEHDR* pHeader ) { // WAVEヘッダの初期化 MMRESULT mmRes = ::waveOutPrepareHeader( *handle_, pHeader, sizeof(WAVEHDR) ); if ( mmRes != MMSYSERR_NOERROR ) { return; } // WAVEデータを出力キューに入れる mmRes = ::waveOutWrite( *handle_, pHeader, sizeof(WAVEHDR) ); if ( mmRes != MMSYSERR_NOERROR ) { return; } // 次のバッファインデックス audioBufferNextIndex_ = (audioBufferNextIndex_ + 1) % audioBufferCount_; } private: static const int MAX_BUFFER_SIZE = 2 * 1024 * 1024; HWAVEOUT* handle_; WAVEHDR* waveHeaders_; int audioBufferCount_; int audioBufferNextIndex_; }; }
使う側
見た目はいつもどおりなので割愛します
using System; using System.Threading; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Threading; using OpenNI; using Win32; namespace AudioWPF { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { Context context; ImageGenerator image; AudioGenerator audio; StreamingWavePlayer wavePlayer; private Thread readerThread; private bool shouldRun; public MainWindow() { InitializeComponent(); try { // ContextとImageGeneratorの作成 ScriptNode node; context = Context.CreateFromXmlFile( "../../SamplesConfig.xml", out node ); context.GlobalMirror = false; image = context.FindExistingNode( NodeType.Image ) as ImageGenerator; audio = context.FindExistingNode( NodeType.Audio ) as AudioGenerator; wavePlayer = new StreamingWavePlayer( audio.WaveOutputMode.SampleRate, audio.WaveOutputMode.BitsPerSample, audio.WaveOutputMode.Channels, 100 ); // 画像更新のためのスレッドを作成 shouldRun = true; readerThread = new Thread( new ThreadStart( () => { while ( shouldRun ) { context.WaitAndUpdateAll(); ImageMetaData imageMD = image.GetMetaData(); // WAVEデータの出力 wavePlayer.Output( audio.AudioBufferPtr, audio.DataSize ); // ImageMetaDataをBitmapSourceに変換する(unsafeにしなくてもOK!!) this.Dispatcher.BeginInvoke( DispatcherPriority.Background, new Action( () => { image1.Source = BitmapSource.Create( imageMD.XRes, imageMD.YRes, 96, 96, PixelFormats.Rgb24, null, imageMD.ImageMapPtr, imageMD.DataSize, imageMD.XRes * imageMD.BytesPerPixel ); } ) ); } } ) ); readerThread.Start(); } catch ( Exception ex ) { MessageBox.Show( ex.Message ); } } private void Window_Closing( object sender, System.ComponentModel.CancelEventArgs e ) { shouldRun = false; } } }
初期化
通常通りに初期化します。AudioGenerator初期化後にStreamingWavePlayerを生成します。音声に関する設定はAudioGenerator.WaveOutputModeから取得できるので、それを渡します。
// ContextとImageGeneratorの作成 ScriptNode node; context = Context.CreateFromXmlFile( "../../SamplesConfig.xml", out node ); context.GlobalMirror = false; image = context.FindExistingNode( NodeType.Image ) as ImageGenerator; audio = context.FindExistingNode( NodeType.Audio ) as AudioGenerator; wavePlayer = new StreamingWavePlayer( audio.WaveOutputMode.SampleRate, audio.WaveOutputMode.BitsPerSample, audio.WaveOutputMode.Channels, 100 );
音声の出力
StreamingWavePlayer.Outputにバッファのアドレスとサイズを渡します。音声の場合はAudioGeneratorから直接データを取得しています。
// WAVEデータの出力
wavePlayer.Output( audio.AudioBufferPtr, audio.DataSize );
まとめ
音声の出力方法だけわかれば、特に難しいことはないですね。これで、WAVEデータが取得できるようになったので、録音や音声処理などにも使えるでしょう。