syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

XAudio2とMedia FoundationでMP3/WMA再生

経緯

以前、DirectXの学習を兼ねて開発していたゲームエンジンC++で様々なDirectX APIを直接叩いていました)では、効果音 (SE) の再生にXACT3を、BGMの再生にDirectShowを使っていました。本当はBGMにもXACT3を使ってもよかったのですが、MP3/WMAフォーマットの音楽ファイルを直接ストリーム再生したかったのでDirectShowを使っていました。ちなみにMIDIを扱っていた頃は、SE/BGMともに知る人ぞ知る太古のAPI、DirectMusicを使っていました*1

ですが、Windows 8で導入されたWindowsストアアプリ(のちのWindows 10におけるユニバーサルWindowsプラットフォームアプリ)ではXACT3もDirectShowも使えなくなりました。デスクトップアプリではともに一応使えますが、Windows SDK 8.0以降ではXACT3は廃止されてしまっています。

Microsoftが推奨している新しいオーディオ系の代替APIはいくつかあります。これらはデスクトップアプリでもWindowsストアアプリでもほぼ同様に使えます。一部はXboxとも共通のAPIとなっています。

  • Windows Core Audio (Windows Vista以降における新しいオーディオ基盤)
  • XAudio2 (DirectSoundの後継)
  • Media Foundation (DirectShowの後継)

Core AudioにはWASAPIと呼ばれる、高音質な排他モードをサポートするライブラリも含まれています。しかしゲームエンジンに使うのであれば、同時発音の処理は必須であり、たとえ音質を犠牲にしてもミキシング機能をとる必要があるため、結局XAudio2を使わざるを得ないようです。

最初に候補として検討したのは「DirectX Tool Kit (DirectXTK, DXTK) for Audio」です。DXTK AudioはXAudio2を使ったXNAライクの簡易音声再生補助ライブラリなのですが、どうもこのライブラリはWAVフォーマット (RIFF) にしか対応していないようです。DXTK AudioはまたWaveBank (xwb) にも対応しているのですが、XACT3やXNAで使用可能だったXMA/xWMA圧縮フォーマットには対応していません。つまりXACT3/XNA時代の資産はほとんど使えないといっても過言ではないでしょう。尺が比較的短いSEの再生には使えますが、BGMの再生となるとやや微妙です。また、ポーリングベースではなくイベントベースで実装されていることにも注意が必要です。

XAudio2は、実はWMA (xWMA) 圧縮フォーマットのオーディオデータをデコードする機能を標準で備えているようです。旧DirectX SDK June 2010に付属するxWMAEncode.exeなどを使えば、ゲーム用途におけるBGMファイルのサイズ問題は一応クリアできるでしょう。ただし、Windows 8.xに標準搭載されているXAudio2 v2.8では、xWMAはサポートされていません。Windows 10に標準搭載されているXAudio2 v2.9では、xWMAのサポートが復活しているようですが、xWMAを使い続けるのはどうも微妙な感じがします。

Media Foundation

かつてのDirectShowのように、MP3/WMAフォーマットのメディアファイルを直接ストリーム再生できるようにするには、それらのフォーマットに対応したデコーダーが必要となります。そこで出番となるのが、Media Foundation。やはりDirectShowの後継だけあって、MFが出てくるのは必然ともいえます。

MSDN Magazineに、WindowsストアアプリでXAudio2とMedia Foundationを使ってストリーム再生をするサンプル(C++/CX)が掲載されていました。

ただしこのMSDNサンプルは、オーディオプレイヤーの3大必須機能(Play/Pause/Stop)を実装していないうえにメモリやリソースの寿命管理がgdgdで、とても流用できるような類のものではありません。
まずデスクトップアプリ向けに書き直し、その後で改めてストアアプリ向けに再移植してみました。

サンプルコードは下記2つのソリューション&プロジェクトを含んでいます。DXTK (2015-08-18版) も比較を兼ねてお試しで使っているので、それぞれの環境に合わせたビルドが必要になります。

  1. MFCベースのデスクトップアプリ。ビルドにはVisual Studio 2015 Pro/Community以上が必要。
  2. Windows 8.1用のストアアプリ。ビルドにはVisual Studio 2013 Pro/Community以上が必要。

https://sygh-jp.github.io/content_hosting/my_program_ss/MyMfcDXTKAudioTest1_ss_2015_09_23a.png
https://sygh-jp.github.io/content_hosting/my_program_ss/MyWin81DXTKAudioTest1_ss_2015_09_23a.png

Windows 8.1ストアアプリではなく、Windows 10向けのUWPアプリにしてもよかったんですが、まだまともなWindows 10開発環境を準備できていないので今回はあきらめました。フルスクリーンだと上下左右の余白が寂しい感じです。
デスクトップ版はWindows SDK 10を使っていますが、ターゲット環境をWindows 10に設定していないため、XAudio2 v2.9 (XAudio2_9.dll) ではなくv2.8 (XAudio2_8.dll) がリンクされます。Windows 7あるいはそれ以前のWindowsで動かすためには、旧DirectX SDK June 2010を使ってビルドし、XAudio2 v2.7 (XAudio2_7.dll) をリンクする必要があります。
※2016-03-17追記:
SharpDX 3.0.2を使ったWPFサンプルも追加しました。XAudio2 v2.7が使われるため、DirectX SDK June 2010もしくはエンドユーザーランタイムを導入すればWindows 7でも実行できます。今回はC++版のクラス設計をほぼそのまま移植しているため、一部C#らしくないコードになっていますが意図的なものです。
※2018-02-26追記:
Windows 10向けUWPアプリのサンプル (VS2015) も追加しました。Windows 8.1ストアアプリとほぼ同じです。

なお音声サンプルとして、「魔王魂」さんからダウンロードさせていただいたフリー素材を使用しています。音声ファイルの利用は下記サイトの利用規約に従ってください。

コードの二次利用は自由ですが、自己責任でお願いします。仮に損害が発生したとしても、当方は一切の責任を負いません。
なお、とるに足らないテスト用アプリではありますが、コレをこのままストアに提出することは絶対にやめてください。コレはあくまでサンプルコードです。そのまま提出してもたぶん審査で落ちるだけだと思いますが、もしコレをベースにして似たようなアプリを作成する場合、必ず何らかの独自機能をきちんと付加した上で、Package.appxmanifestのPublisherとPublisherDisplayNameをきちんと更新してください。この約束が守られていないアプリを発見した場合、しかるべきところに通報させていただきます。

C++/CX

簡単のため、ストアアプリ版のコードではMVVMは使っていません。MFC同様にコードビハインドを書いているだけなので、対比は楽だと思います。そのほか、ストアアプリ版はバックグラウンド再生にも対応させていないので、アプリが非アクティブになると再生が一時停止します。
しかし久しぶりにC++/CXを使ったんですが、PPL(特に例外処理)がめんどくさすぎて途中死にそうになりました。C++/CX版PPL (WinRT版PPL) は通常のC++版PPL (デスクトップ版PPL) と違って、C#のasync/awaitのようにタスク完了時に呼び出し元スレッドへのコンテキストの復帰を行なってくれるんですが、例外処理はどのみち煩雑すぎです。タスクチェーンの仕組みもよく考えられているとは思うんですが、やはり記述性や可読性の向上のためにresumable/awaitが欲しいところです。Visual C++ November 2013 CTPやVisual Studio 2015では実験的にresumable/awaitが実装されていますが、/awaitコンパイルオプション(/sdlや/RTCと共存できない*2*3)を明示的に指定する必要があったりして、まだ完成にはほど遠いようです。また、C#のasync/awaitとは違い、GUIアプリケーションのイベントハンドラーを気軽に(同期的に)書けるようになるという類のものではありません。やはりGUIフロントエンドはC#で記述するのが今のところベストだと思います。

ちなみにSharpDXにもXAudio2とMedia Foundationのローレベルなラッパーがあるのですが、リソース寿命管理や例外処理のことを考えると、世代別GCベースのC#から参照カウントベースのCOMコンポーネントを使うのはかなりしんどいです。Javaと違ってC#はある程度ローレベルな記述も可能な言語ではありますが、COMに関してはやはりC++のほうが親和性が高いです。DirectX全般にも該当しますが、もしC#でCOMコンポーネントを扱いたい場合、C++/CLIC++/CXでラップして、独自の上位レベルI/Fを公開するライブラリを作成したほうがかえって効率的だと思います。

*1:単純に音楽ファイルを再生するだけであれば、WinMMやMCI (Media Control Interface) 関数、WMP (Windows Media Player) コンポーネント、あるいはWPF/WinRTのような上位レベルAPIを利用するのが簡単なのですが、ゲーム開発で利用するとなるとパフォーマンス上の理由からDirectXファミリー一択になります。

*2:/sdlに関してはVisual Studio 2015 Update 2で改善されたようです。Visual Studio 2015 Update 2 | Microsoft Docs

*3:/RTCに関してはVisual Studio 2017で改善されたようです。Visual Studio 2017 15.0 リリース ノート | Microsoft Docs