引き続きサウンド周りのドライバを書いているのですが、
BGM用にMP3再生を入れることにしました。 多重再生とループ再生とメモリからの読み込みが可能であることが今回の課題です。 おちは非常にしょーもないんですが、そこんとこよろしくお願いしますということで・・・
ちなみにサウンドドライバはDirectAudio(DirectMusic)を使って作ってます。 事の起こりはDirectMusic単体でMP3の再生ができないと思い込んでしまったことです。
SDKのサンプル(PlayAudio)でMP3ファイルの再生ができなかったんで、 そうなんだなーと判断しちゃったんですね。 そこでできそうな奴を片っ端から調べていくと 1)
waveOut系 2) acm系 3) mci系 4) DirectShow系 5) LAME系 とまぁ大体こんなもんだったんで、それぞれMP3ファイルを単に鳴らすだけのサンプルを作ってみました。
1)のwaveOut系ですが、楽勝です。ヘッダから周波数等のデータを取得して構造体にぶち込んで waveOutOpen waveOutPrepareHeader
waveOutWrite waveOutReset waveOutUnprepareHeader waveOutClose
こんな感じで命令を呼び出すだけです。面白いことにwaveOutWriteという命令はメモリから出力デバイスに データを書き込む命令なんですが、再生途中で呼び出しても良いんですね。
普通の感覚だとwaveOutWriteを呼んだ瞬間に再生しているデータが指定されたデータに切り替わりそうなもんですが、 waveOut世界では再生途中でwaveOutWriteを呼び出すとバッファリングされて現在再生しているデータが
終了した後に再生されるようになってます。ストリーミングを実現しようと思ったら確かに理にかなってますね。 ループ再生にはコールバック関数で対応できます。
ちなみにwaveOutOpen可能なデバイス数は1つの模様。 もしかすると複数のサウンドカードを搭載しているとデバイス数が増えるのかもしれませんが、
普通の環境ではwaveOutGetNumDevsの返値が1になります。 そんなわけでどうやら多重再生はできなさそうです。 2)
acm系命令は比較的使用例が多い手法ですが、acm単体ではデコードができるだけで再生はDirectSoundなんかの 手を借りないとできないようです。というか再生をサポートしたのがwaveOut系命令と言えるでしょう。
1)と同じなんですがストリーム処理を自前でやらなければならないのがめんどくさいです。 初期化時に一気にデコードすれば楽なんですが、いかんせん非圧縮状態だと1曲数十Mバイトになるので
メモリを食いまくってしまいます。それにそんなことすると再生する瞬間にCPUがデコード処理で 手一杯になってしまって再生開始に手間取ってしまいます。 3)
mci系命令の命令は1)に輪をかけて簡単です MCIWndCreate MCIWndOpen MCIWndPlay MCIWndStop
MCIWndClose の順で命令を呼ぶだけでOK。ヘッダを読み下す必要もありません。 MCIWndOpenの引数にファイル名を指定するだけです。
逆にいうとファイル名を指定するしか方法がないのでメモリから読み出すことはできなさそうです。 再生開始にちょっと手間取るのが難点。あとループ再生するためにWindowsのメッセージループに送られる
MCIWNDM_NOTIFYMODEメッセージを見に行かないといけないというのがライブラリの独立性を損ねそうで嫌な感じです。 4)DirectShow系も簡単です。
CoCreateInstance( CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder,
(void**)&pGraphBuilder ) pGraphBuilder->QueryInterface( IID_IMediaControl,
(void **)&pMediaControl ) pGraphBuilder->RenderFile( pwcFileName, NULL
) pMediaControl->Run() pMediaControl->Stop() RELEASE( pMediaControl
); RELEASE( pGraphBuilder ); ってなもんです。これまたファイル名指定だけで再生できてしまうお手軽さなんですが、
メモリからの読み出し方が不明。もちろんDirectShowはストリーミングに主眼を置いて作られているので できるんでしょうけど、なんだかよく分かりません。多分IMemAllocator辺りを使うんだと思うのですが…
CMemAllocatorクラスをnewしようとするとAssertに引っかかってうまくいきません。 資料も出回ってないのでお手上げ状態。どなたかアドバイスPlease〜
ループ再生については3)と一緒でWindowsのメッセージループに送られるWM_DIRECTSHOWEVENTを見に行かなければなりません。 5)はフリーのmp3デコードルーチン使おうという話ですが2)同様再生にDirectSound等が必要になります。
第一、難しそうです(^^; そんなこんなで、どれもニーズに合わないんで困ってしまいました。
しまいにはTwinVQやらOgg Vorbisでも使おうかと血迷うありさま。 で、メルトダウン寸前の脳味噌でMSDNを漁っていると、 DirectMusicローダーは、オーディオ圧縮マネージャ
(ACM) でサポートされているフォーマットの圧縮されたウェーブフォーム オーディオ ファイル、 および非圧縮ウェーブ ファイルをロードできる。 って書いてあるじゃないですか。
ぐはぁ、なんだCodecさえあればDirectMusicでも再生できるっつーことか(DirectX7以前は知しらないが少なくともDirectX8では)。
でも、前に試したときはMP3ファイル再生できなかったんだけどな〜とうだうだ考えていて、 ふと思い出したことにはMP3形式のWaveとかいうのがあったなぁという事。
チャンク内にMP3を放り込んでやることでWAVEの格好をするという奴です。 早速BlizTagEditorなるヘッダ書き換えツールを使って新しいMP3ファイルを作成してみたもののやはり再生できません。
ちっ、またMSに騙されたか!、ACM越しの再生なんてできないじゃないですか〜 と怒り心頭だったんですが、最後に念のためサウンドレコーダーでADPCMを作って試してみるとあら不思議ちゃんと再生できます。
ついでにこれまたサウンドレコーダーでMP3形式のWaveを作ってみると・・・、再生できますねぇ。 すまんMS、早とちりだったらしい。 どういうことかというとRIFF/WAVE
MP3ファイルとMP3形式のWaveファイルを混同していたのが駄目だったらしいです。 後者にID3タグとかの付加情報を付けたのが前者という位置付けのようです。
つまりヘッダ書き換えなんて姑息な手段を使わないでまじめにWAVE->WAVE形式MP3にエンコードしておけば問題なかったんですね〜。 といった訳で午後のコーダーなら
gogo ファイル名 -riff wave SCMPXなら Encoding MPx -> WAVEヘッダ みたいにエンコードしてあげると問題なくDirectMusicでも再生できます。
あーしょもない。 でも本来チャンク構造をとっているはずのRIFF/WAVE MP3ファイルが再生できない方が問題ありな気もするんですけどね。
TAGデータとかは読み飛ばしてデータ部分を読むだけでいいんじゃないんだっけ? まぁその辺差し引いてもDirectMusicに再生を任せると自動的にストリーミングしてくれるんでありがたいこと限りなし。
もちろん多重再生とループ再生とメモリからの読み込みもOK…じゃないですね〜 なんかループ再生が挙動不審です。 長めのMP3ファイルを再生するとループに失敗します。
デバッガによると DSWAVE: ERROR: Create (Viewport): Attempt to open a conversion stream
failed. とかおっしゃってます。 だぁぁ、相変わらず一筋縄ではいかないです。 てなわけで次回に続く(笑)
02/03/07追記
waveOutOpen可能なデバイス数は1ですが、スレッドを分けると複数回Open可能で多重再生もいける模様、要検証。 |