
< まえがき >
私は何十万、何百万もするシステムを所有するオーディオマニアでもなければ、人より音質にこだわりがあるわけでもない。とはいえ音楽やコンピュータ(とりわけマルチメディアやネットワーク関連)を趣味とする者としては、最低限それらを活かした音楽鑑賞の環境は構築しているつもりだ。基本的には楽をしたいので、出来る限り既存のツールの組み合わせで作るようにはしているが、残念なことにいつも上手く行くとは限らない。どうしても既存のツールで満足できないときは、やむを得ず重い腰を上げることになる。つまり素人が一から勉強し、足りない部分を自分で作る他ないのである。
すべては foobar2000 と WaveSpectra の組み合わせでビットパーフェクト録音ができたことに端を発する。これについては過去にも挑戦したことがあるが、そのときはどうしても自分の推論と結果が一致せず、自分の理解に誤りがあるのでは?とそのまま放置になっていた。今回の前進は VAC (Virtual Audio Cable) という仮想デバイスを見つけたことが大きい。それまでは検証用の仮想デバイスとして VB-Audio (VB-Audio Virtual Cable) を使っていた。しかしここにビットパーフェクトを妨げる要因があったのである。

< 仮想デバイス >
仮にこのページに読者がいるのであれば、私以上に詳しい方がほとんどであろう。よって基本的なことは理解されているものとして話を進める。さてパソコン内で再生された音声をハードウェアを経由せずに録音するには「ループバック」という仕組みを使うのが手っ取り早い。例えば「再生デバイス」とはプレイヤー等からデータを受け取り、何らかの形(例えばスピーカー)で音声を出力するデバイスである。また「録音デバイス」とは何らかの形(例えばマイク)で音声を入力し、レコーダー等へデータを送るデバイスである。そして肝心の「仮想デバイス」であるが、本考察においては再生デバイスの音声出力先と録音デバイスの音声入力先を仮想的にケーブル接続することでループバック機能を実現したソフトウェアデバイスのことを指す。前述の VB-Audio や VAC はこのタイプの仮想デバイスである。再生デバイスと録音デバイスがセットになっており、両者間が仮想ケーブルで接続されていることで再生デバイスに送ったデータが録音デバイスから取り出せるという仕組みだ。これにより専用機器がなくてもプレイヤーで再生した音声をレコーダーで録音することができるようになる。

< ホストAPI >
以下はプレイヤーから出力されたデータがループバックによってレコーダーに届くまでの「流れ」である。
1) プレイヤー
2) 再生デバイス (Output Device)
3) ループバック
4) 録音デバイス (Input Device)
5) レコーダー
流れとしては分かりやすいが、再生デバイスや録音デバイスを利用する上での重要な要素が一つ欠けている。それが「ホストAPI」である。オーディオデバイスにアクセスするためのオーディオドライバと言ったほうが通りは良いだろうか。いわゆる MME や DirectSound, ASIO、そして本考察の主役である WASAPI などがこれに相当する。同じ再生デバイスで音を鳴らすにしても利用するホストAPIが異なればデータの処理方法は変わる(概念的にいうなら「通るルート」というべきか)。ホストAPIと仮想デバイスの概念を踏まえると前述の「流れ」は以下のように書き換えることができる。
1) プレイヤー
2) ホストAPI
3) 仮想デバイス (再生デバイス=>ループバック=>録音デバイス)
4) ホストAPI
5) レコーダー
ホストAPIについては MME や DirectSound の音質劣化が有名であろう(ただし「音質劣化」というと聴き手の主観も入ってしまうので、本考察では「入出力におけるデータの変化」として扱う)。これは音声データがカーネルミキサー(現オーディオエンジン)を経由することが原因である。内部ではミキシングやピーク調整のためにリサンプリングが行われ、データの変化を引き起こす。これらの問題を解決すべくサードパーティからは ASIO という API がリリースされたが、現在の Windows には同等の機能を持つ WASAPI が搭載されている。これにより Windows のオーディオシステムは一新され、基本的には WASAPI の二つのモードで構成されているといって良いだろう。一つはオーディオエンジンを経由する「共有モード」。これは言わば旧来システムの置き換えである。実のところ、レガシーAPIの MME や DirectSound も WASAPI 共有モード上のエミュレーションで動作する。もう一つはダイレクトでデバイスにアクセスする「排他モード」。オーディオエンジンを経由しないのでデータが変化することはない。ただしミキシングが出来ないため、デバイスを利用するアプリケーションは一台に限られる(これが「排他」と呼ばれる所以)。ビットパーフェクトにおいては、この WASAPI 排他モードが重要なファクターの一つとなる。
https://docs.microsoft.com/en-us/archive/blogs/windows_multimedia_jp/4-windows7
< ビットパーフェクトのための三条件 >
「ビットパーフェクトの実現」であるが、要はプレイヤーから送出されたデータがルート上で一切変化せずにレコーダーに到達すれば達成できる。前述の「流れ」において言えば、プレイヤーが仮想デバイスの再生側に、レコーダーが仮想デバイスの録音側に、それぞれ WASAPI 排他モードでアクセスし、且つ仮想デバイス内でデータが変化しなければ良い。
1) プレイヤーが WASAPI 排他モードで再生デバイスにアクセス
2) レコーダーが WASAPI 排他モードで録音デバイスにアクセス
3) ループバック時にデータが変化しない仮想デバイス
< 検証 (VB-Audio編) >
最初の検証は WASAPI 排他モードが利用できるプレイヤーとして foobar2000、同様のレコーダーとして WaveSpectra、そして仮想デバイスとして VB-Audio を用いて行った。すでに書いたとおり、ビットパーフェクトは成功せず、結果として量子化におけるデータ化け、標本化におけるサンプルの重複や欠落が確認された。三条件の (1) と (2) は満たしているため、VB-Audio のループバックが疑わしいというところまでは辿り着いたが、当時はこれを証明する手立てがなく、調査は一旦打ち切りとなった。
https://vb-audio.com/Cable/

< 検証 (VAC編) >
再び "事" が動き出したのはつい最近のこと。なんとなく VAC (Virtual Audio Cable) をインストールしてみたのがキッカケだった。以前にも見たような気はするが、VB-Audio Virtual Cable と似たような名称なので混同していたかも知れない。とにかく VAC で前回と同様の検証をすることにした。プレイヤーは foobar2000、レコーダーは WaveSpectra である。あまり期待はしていなかったが、なんとこれが見事にビットパーフェクト! やはり原因は VB-Audio にあったのだ。
https://vac.muzychenko.net/en/

(2020/11/30)
< 模式図 >
本記事はあくまで素人による趣味の範囲内での検証であり、私なりの解釈がベースになっている。正確性に欠けるところもあるかと思うが、そこはご容赦いただきたい。……というわけで、まずはビットパーフェクトの三条件について、それらを満たすルートとそうでないルートの関係を模式図で表してみた。私が頭の中で描くイメージでもある。

ホスト API としてオーディオエンジンを経由する MME と経由しない WASAPI (排他モード)、仮想デバイスとして VB-Cable と VAC を例に取る。実線はデータが変化しないルート、点線はデータが変化するルートである。続いてはプレイヤーから出力されたデータがどういうルートでレコーダーに到達するか、そのときデータはどのように変化するのかを検証していきたい。
(2020/12/01)
< 解析ツール >
これから様々な検証をするにあたり、その都度 foobar2000 で再生し、WaveSpectra で録音するというのはいささか効率が悪い。そこで検証に先立って解析用のプログラムを作ることにした。コマンドラインツールが便利だろうということで開発言語は Python を選択。再生や録音は PyAudio モジュールに任せるつもりでいたが、残念なことに提供されるバイナリは WASAPI 排他モードに対応していない。結局ソースコードを入手し、排他モードが機能するよう修正を加えた上で再コンパイルする必要があった(核となる部分はC言語で書かれている)。ネットの情報では数行の追加で対応できるようなことが書かれていたので軽く見ていたが、実は結構手こずらされた。特に録音処理の部分は WASAPI 排他モードとの相性が悪いらしく、どうしても正常な書き出しが出来ない。仕方ないので、ここだけは PortAudio のサンプルコードを参考に全面的に書き換えることにした (PyAudio は PortAudio のラッパー)。開発経緯は本題から逸れるのでこれくらいで……。
解析ツールの仕組みは至ってシンプル。
1) 再生デバイス用のホストAPIを指定。
2) 録音デバイス用のホストAPIを指定。
3) 利用する仮想デバイスを指定。
4) 入力用 WAV ファイルの再生。
5) 出力用 WAV ファイルの録音
プログラムは上記手順のバッチ処理が基本となっており、他に検証用のユーティリティをいくつか実装している。以下はその一例。VAC と VB-Audio における量子化のバラツキを検証したものである。いずれも再生デバイスと録音デバイスはホスト API に WASAPI 排他モード (以降 WASAPI) を指定。データ化けの発生を仮想デバイス内に限定させた上での検証である。元となるデータは 0x1234 のみで構成された7秒の WAV ファイル。これを5秒間録音し、出力された WAV ファイルのデータを解析している。
VAC:

VB-Audio

見ての通り、VAC はサンプル値のすべてが 0x1234 で占められているのに対して、VB-Audio は9割以上が 0x1233 に化けている。音質劣化という意味では誤差範囲だがビットパーフェクトにはなり得ないという結果である。

< 量子化におけるデータ化け検証 (仮想デバイス編) >
例に挙げた量子化成分におけるデータ化けについて、もう少しデータを取ってみる。サンプル値を 0x2345 に変えてみると面白いデータが取れた。
VAC:

VB-Audio:

先程と同様 VAC はデータ化けが発生しないが、VB-Audio は色々とバラついている。まずは 0x1233 と 0x1234 について。本来のバラツキはリサンプリングによる計算上の丸め誤差が原因だろうと推測されるが、これは明らかに直前のテストが影響している。おそらくはバッファ内に残ったデータがクリアされずに検出されたものと思われる。また 0x128E と 0x22E9 は 0x1234 から 0xABCD へ遷移する際に補完されたサンプルであろう。ここからも VB-Audio はループバックにおいて何らかの処理が挟まれていることが窺える。ちなみに 0x2345 の録音を連続して行うと残留データも 0x2345 になるため、先程のような現象は顕在化しない。いずれにせよ、ビットパーフェクト再生において VB-Audio を使う理由は今のところない(リサンプリングの無効化設定でもない限り)。
VB-Audio:

< 量子化におけるデータ化け検証 (再生・録音デバイス編) >
せっかくなのでホスト API の違いも検証しておこうと思う。以下は再生デバイスのみ MME、録音デバイスのみ MME、両デバイス共に MME とした場合の検証結果である。ほぼ一定の比率でサンプル値がバラつくことがお分かりになるだろう。
VAC: 再生デバイスのみ MME

VAC: 録音デバイスのみ MME

VAC: 両デバイス共 MME

両デバイスを MME にした場合、同じ比率で2回バラつくとすれば、中央値の 0x0000 は (0.771 x 0.771) + (0.115 x 0.115) + (0.115 x 0.115) = 0.621 となるので、ほぼ理論通りの値が出ていることになる (5秒のデータではサンプル数が少ないため、30秒のデータで検証)。
というわけで、量子化成分においては WASAPI (Output)、WASAPI (Input)、VAC の組み合わせでビットパーフェクトが期待できる環境になることが分かった。続いては標本化における一致性を検証していきたいと思う。
< 標本化におけるデータ化け検証 >
次は標本化におけるデータ化けを検証していく。時系列データとしてはサンプルが過不足なく(欠落や重複なく)転送できれば良い。この検証には三角波を用いることにする。サンプルの前後で一定の変化量を持つため、サンプルの欠落や重複をその長さを含めて検出できるようにするのが目的。周期は波形ビューワでの視覚的なチェックも想定して 1000サンプル。また周期を確認しやすいようにピーク値の前後のみ変化量を大きく取った(そういう意味では三角波という表現は正確ではない)。

まずは10分ほど録音し、ズレが生じていないか末尾を確認してみる。
VAC (w/WASAPI):

VB-Audio (w/MME):

VAC (w/WASAPI) で録音したデータはピーク位置が周期 (1000サンプル) の倍数になっており、VB-Audio (w/MME) はそこから明らかにズレていることが確認できる。前者で言えばバラツキの合計がたまたま1000の倍数になった可能性もあるし、後者で言えばリアルタイム処理である録音中にたまたま負荷が高くなり、処理落ちが発生した可能性もある。すべての周期が1000であることを確認しなければ、ハッキリとしたことは言えないが、現時点での参考データとして押さえておきたい。
(2020/12/02)