Windows上の5.1chサラウンドアプリケーションを光デジタル + Dolby Digitalでリアルタイム出力できないか実験。
やりたい事
サラウンドでゲームがしたい 。ただ、いわゆる光デジタルでの接続では、ゲーム音声をリアルタイムにDolby Digital(AC-3)エンコードしなければ、これは実現できない。
(まぁGame Cube等で見られるようにPro Logic II https://www.nintendo.co.jp/nom/0211_2/index.html とかでも良いのかもしれないけど)
世間的には、このような機能は"Dolby Digital Live"として市販の機器に搭載されている(されていた)。
できたこと
レイテンシが厳しくてゲームは無理だがそれ以外は出来た。つまり、 Windowsアプリの5.1出力をリアルタイムエンコードして光デジタル出力できた 。専用出力プラグインを書けばレイテンシの問題も解決できる気がしている。
- レイテンシの問題は解決してない
- Jack → GStreamerプラグインは適当にバグフィックスして自前でビルドした
- GStremaerのS/PDIF出力は使えなかったので、RTPに載せてMedia Player Classicで出力した
こういう感じの構成で実現する。
- エンコーダへのオーディオの取り込みは、仮想サウンドデバイスである VB Audio Hi-Fi Cable ( https://www.vb-audio.com/Cable/ )を使用。
- ASIO → GStreamer の中継のためにJACKオーディオサーバ( http://jackaudio.org/ )を使用。
- Dolby AC-3 へのエンコードにはGStreamer( https://gstreamer.freedesktop.org/ )を使用。
- GStreamer → Media Player Classic の中継のためにはRTPを使用。
何故今さらDolby Digital Liveなのか
Dolby Digital Live とは、 PC上のアプリケーション (要するにゲーム) の5.1ch出力を光デジタル出力できる 機能で、だいたい2000年代前半のPCやサウンドカードにたまに搭載されていた。5.1chのオーディオを光デジタルで伝送できる程度までDolby AC-3 CODECでリアルタイム圧縮してスピーカーシステムに伝送する。
もっとも、これは 歴史的事情の賜物 で、DVDが見られるホームシアターを構築している人はそれなりに居るのでそれをPCなりゲーム機のゲームに流用できないかという発想に基いている。DVDのサラウンド音声は帯域の都合で常に圧縮音声で、その音声の伝送には光デジタル端子が使われる。
ただ、 Windows 10のCreator's Update以降、Dolby Digital Live / DTS Interactive が急に使えなくなる事件が起きた 。Windows 10のアップデートで、Realtekなど既存のDolby Digital LiveやDTS Interactive(CODECとしてDTSを使った類似技術)実装が機能しなくなった。
まぁ実際HDMI経由での接続なら直接マルチチャンネルなPCMを伝送できるし、ある種レガシー技術なのは疑いようがないと思う。(Windowsオーディオ側の問題は修正されたらしいが、ASUSがドライバをアップデートしていないので手元では依然使えていない。)
また、光デジタルは 依然サラウンド出力の最大公約数である 。ホームシアターが光デジタル接続しかサポートしていないなら、依然Dolby Digital Liveのような手法が必須になる。流石にホームシアターを名乗るようなロケーションではBlu-Rayくらい再生できる環境を整えている(= HDMIによるマルチチャンネルPCM伝送が可能である)とは思うが、サウンドバーとかサラウンドヘッドホンのような機器では未だに現役となっている。
HDMIでもeARCが出現するまでARCによって伝送できる音声のスペックは光デジタル同様であったため、 SonyのWH-L600 のように非圧縮のサラウンド音声を扱えないデバイスは実在する。
作戦
作戦は至ってシンプルで、オーディオのエンコードを適当にやるにはGStreamerが簡単なので、
- GStreamerにPCアプリケーションのサラウンド音声を入力する方法
- GStreamerがエンコードしたAC-3を光デジタル(S/PDIF)出力する方法
の2点を探せば良いということになる。
GStreamer
こういう目的にはGStremaerを使うのが簡単なことが多い。実行したいパイプラインは至ってシンプルで:
gst-launch-1.0.exe jackaudiosrc connect=0 buffer-time=20000 ! audio/x-raw,channels=6,rate=48000 ! \
avenc_ac3 ! rtpac3pay pt=100 ! udpsink host=127.0.0.1 port=5000
のように、 jackaudiosrc
→ avenc_ac3
→ rtpac3pay
→ udpsink
のようなパイプラインになる。
GStreamerは最近はWindows版の公式バイナリを配布しており、公式のダウンロードページ https://gstreamer.freedesktop.org/download/ から MinGW 64bit 版のruntimeとdevelopmentの両方をダウンロード / インストールする。MSVC版は手元では正常に動作しなかった。(MinGW版でも、MSVC向けのインポートライブラリが付属するため特に問題なくMSVCで開発できる)
JACKサーバの起動
Jackの公式サイト( http://jackaudio.org/ )からWindows版のサーバをダウンロードし、Jack Controlアプリから dummy
ドライバを使用してサーバを起動する。
サンプリングレートは48000にする。
公式のWindowsビルドは、ASIO → Jackブリッジである JackRouter
を自動的にインストールし、ASIO対応のアプリをJackに接続できる。
VB-Audio Hi-Fi Cable でWindowsオーディオを取り込む
今のところ、公式のWindows版ではportaudioドライバでサラウンドを扱えないため、VB-Audio Hi-Fi CableでWindowsオーディオをASIO経由で取り込み、それをJackに流し込む。 Hi-Fi Cable でない方はASIO非対応なので注意 。多分同作者の Voicemeeter でも同じことができる。
公式サイト https://www.vb-audio.com/Cable/ からダウンロード / インストールし、アプリ上のボタンを押せばJackに接続される。(ASIO DeviceはJackRouterを選択する。)
Jack Control の "Connect" ボタンから、実際に接続されているのを確認できる。デフォルトではDummyドライバに接続されてしまうので、"Disconnect All"ボタンで切断しておく。
jack
プラグインのビルド
GStreamerのパイプラインとJACKを接続するには、 jack
プラグインを使用する。これはWindowsの公式ビルドでは提供されないため、適当にCMakeでビルドできる奴を用意した:
https://github.com/okuoku/gstjack-windows - ビルド用の CMakeLists.txt
を書いただけ。
CMakeサポートをインストールしたVisualStudioの "フォルダを開く" 機能で直接ビルドできる。ビルドされた libgstjack.dll
は C:\gstreamer\1.0\x86_64\lib\gstreamer-1.0\libgstjack.dll
にコピーしておく。
gst-inspect-1.0 jack
で正常にプラグインが認識されているかを確認できる。
Plugin Details:
Name jack
Description JACK audio elements
Filename C:\gstreamer\1.0\x86_64\lib\gstreamer-1.0\libgstjack.dll
Version 1.0.0
License LGPL
Source module xxlocal
Binary package xxlocal
Origin URL xxlocal
jackaudiosink: Audio Sink (Jack)
jackaudiosrc: Audio Source (Jack)
ちなみに↑のリポジトリでは、 Windows上でクラッシュするバグを修正 している。これはGStreamerの本流にも投げておいた( https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/merge_requests/189 )。
リアルタイムAC-3エンコードとRTPによるストリーミング
というわけで、ここまでの準備が整えば、上記のパイプライン:
gst-launch-1.0.exe jackaudiosrc connect=0 buffer-time=20000 ! audio/x-raw,channels=6,rate=48000 ! avenc_ac3 ! rtpac3pay pt=100 ! udpsink host=127.0.0.1 port=5000
を実行できる。この gst-launch
コマンドでこのパイプラインを実行するとJack Controlの"Connection"にはGStreamerの入力も出現する。これを、下図のように接続することで、実際のエンコードを行える。
あとはWindowsアプリケーションから "Hi-Fi Cable Input " で音声を再生すれば、パイプラインに音声を投入できる。
FIXME: チャンネルオーダーはそれぞれで任意に設定可能なので、手調整するのはちょっとダサい。
Media Player ClassicによるRTPストリームの再生
上記のパイプラインでは、エンコードされたAC-3フレームをRTPでwrapしてUDPで投げるだけなので、UDPで受信して再生するクライアントが必要になる。
ここでは、Media Player Classic (MPC-HC https://mpc-hc.org/ )のストリーミング再生機能を使用して再生してみた。光デジタル出力を正常に扱えるプレーヤは意外と少く、例えばVLCではストリーミング再生時には光デジタル出力が使えなかった。
圧縮されたオーディオは一般に"フレーム"と呼ばれる単位に分割される。これをUDPパケットに載せるプロトコルとして一般に使われるのがRTP( https://ja.wikipedia.org/wiki/Real-time_Transport_Protocol )で、AC-3パケットの載せ方はRFC4184 ( https://tools.ietf.org/html/rfc4184 ) に規定されている。
GStreamerでは、 avenc_ac3
が自動的に入力オーディオをAC-3圧縮した上でフレームに分割してくれるため、 rtpac3pay
でRFC4184形式のパケットに詰め、 udpsink
で送出すればRTPストリームが完成する。
一般的なRTPアプリケーションではアプリケーションにこのストリームを認識させるために、RTSP( https://ja.wikipedia.org/wiki/Real_Time_Streaming_Protocol )サーバを別途立てそちらに接続させることが多い。しかし、実はRTSPでやりとりされるSDP( https://ja.wikipedia.org/wiki/Session_Description_Protocol )の内容をテキストファイルに書き、拡張子を .sdp
とすることで直接開けるため、今回はRTSPサーバを立てるのはサボることにする。
上記のパイプラインでは、
- ホスト
127.0.01
(udpsink
) - ポート番号
5000
(udpsink
) - Payload Type
100
版をac3/48000/6
のストリームとして使用 (rtpac3pay
)
しているので、以下のようなSDPセッションとして書ける:
v=0
c=IN IP4 127.0.0.1
m=audio 5000 RTP/AVP 100
a=rtpmap:100 ac3/48000/6
これをMPC-HCで開けば、エンコードされたストリームを光デジタルに出力できる。
感想
やればできる。けど、実用的に使うには専用の出力モジュールを書いた方が良さそうだ。AC-3は特許も切れているし、 aften のような単体のエンコーダライブラリもある。
GStreamerの directsoundsink
にはS/PDIFサポートが組込まれているものの、これを正常に使う方法はわからなかった。そもそも directsoundsink
では WAVE_FORMAT_DOLBY_AC3_SPDIF
をフォーマットに指定しているものの このフォーマットはAC-3のフレームを直接受けるわけではないのでどうやっても正常に再生できないと思う。AC3FilterのSpdiferや VLCのtospdif のように、S/PDIFのヘッダを先頭に入れておく必要がある。