Dolby Digital Live を手作りしたい実験

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出力をリアルタイムエンコードして光デジタル出力できた** 。専用出力プラグインを書けばレイテンシの問題も解決できる気がしている。

1. レイテンシの問題は解決してない
2. Jack → GStreamerプラグインは適当にバグフィックスして自前でビルドした
3. GStremaerのS/PDIF出力は使えなかったので、RTPに載せてMedia Player Classicで出力した

こういう感じの構成で実現する。

![SnapCrab_NoName_2019-4-23_5-44-25_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/3f600df9-a026-1c9c-34ee-f160b506d89b.png)

- エンコーダへのオーディオの取り込みは、仮想サウンドデバイスである 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を使った類似技術)実装が機能しなくなった。

![SnapCrab_NoName_2019-4-21_18-58-18_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/6baec5e9-571d-461a-82b7-bf2b9fc99bc9.png)

まぁ実際HDMI経由での接続なら直接マルチチャンネルなPCMを伝送できるし、ある種レガシー技術なのは疑いようがないと思う。(Windowsオーディオ側の問題は修正されたらしいが、ASUSがドライバをアップデートしていないので手元では依然使えていない。)

また、光デジタルは **依然サラウンド出力の最大公約数である** 。ホームシアターが光デジタル接続しかサポートしていないなら、依然Dolby Digital Liveのような手法が必須になる。流石にホームシアターを名乗るようなロケーションではBlu-Rayくらい再生できる環境を整えている(= HDMIによるマルチチャンネルPCM伝送が可能である)とは思うが、サウンドバーとかサラウンドヘッドホンのような機器では未だに現役となっている。

HDMIでもeARCが出現するまでARCによって伝送できる音声のスペックは光デジタル同様であったため、 [SonyのWH-L600](https://www.sony.jp/headphone/products/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` ドライバを使用してサーバを起動する。

![SnapCrab_NoName_2019-4-23_6-3-40_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/c75c1dfe-0834-7a91-f03e-8fa30594f5c6.png)

サンプリングレートは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/Voicemeeter/) でも同じことができる。

![SnapCrab_NoName_2019-4-23_6-6-27_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/4b820e9b-d7a8-324e-395e-3126b957cdde.png)

公式サイト 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上でクラッシュするバグを修正](https://github.com/okuoku/gstjack-windows/commit/0b1b12fc6e20db23f200940345e0166f6806d9c3) している。これは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の入力も出現する。これを、下図のように接続することで、実際のエンコードを行える。

![SnapCrab_NoName_2019-4-23_6-29-48_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/a1b8497f-51e3-b4c0-4dd3-916df95ec58b.png)

あとは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サーバを立てるのはサボることにする。

上記のパイプラインでは、

1. ホスト `127.0.01` (`udpsink`)
2. ポート番号 `5000` (`udpsink`)
3. Payload Type `100` 版を `ac3/48000/6` のストリームとして使用 (`rtpac3pay`)

しているので、以下のようなSDPセッションとして書ける:

```:test.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](http://aften.sourceforge.net/) のような単体のエンコーダライブラリもある。

GStreamerの `directsoundsink` にはS/PDIFサポートが組込まれているものの、これを正常に使う方法はわからなかった。そもそも [`directsoundsink` では `WAVE_FORMAT_DOLBY_AC3_SPDIF` をフォーマットに指定しているものの](https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/blob/7fb8abf8bbf089095e483824cf46363d782868b7/sys/directsound/gstdirectsoundsink.c#L513) このフォーマットはAC-3のフレームを直接受けるわけではないのでどうやっても正常に再生できないと思う。AC3FilterのSpdiferや [VLCのtospdif](https://git.videolan.org/?p=vlc.git;a=blob;f=modules/audio_filter/converter/tospdif.c;h=c468af79d64b748998545fee949e94b7aa80215f;hb=0d2f717083cb0e6e98ccc9b75182671c02daa562) のように、S/PDIFのヘッダを先頭に入れておく必要がある。