USBオーディオのフロー制御

現在位置のナビ

トップコンピュータの国雑記帳オーディオ譚 → USBオーディオのフロー制御

説明

USBオーディオのフロー制御は複雑です。 2020年現在でも、買ってきたOSそのままではフロー制御に失敗しているケースが見られるので解説してみます。 自分でUSBオーディオのターゲットやOSのデバイスドライバを書く人向けの記述ですので、技術的バックボーンのない人にはチンプンカンプンかもしれません。

ついでにAmeneroやXMOSのDSDネイティブ転送路についても書きます。

フロー制御って何?

ちょっとだけ、素人向けに導入を書きます。

USBオーディオは、PCMとかDSDを再生します。 それぞれサンプリング周波数というものがあり、一定周期で再生データを入れ替えます。この一定周期というのが曲者です。

CDのデータを無加工でUSB DACに送ろうとすると、1秒間に44100個のデータを送らなくてはなりません。 左右2チャンネルあって、それぞれ2バイトのデータから出来ています。 1秒間に送るデータは (44100 x 2 x 2)で176400バイトになります。 CDアルバム1時間分を再生するには、当速度の転送を3600秒続けることになります。

再生するには、正確なクロックが必要です。 2020年現在では、HiFiオーディオのUSB DACは非同期転送と言って、USB DAC側に正確な水晶発振器を搭載しています。 パソコン側にも水晶発振器がありますが、USB DACのクロックとごくわずかずれます。 パソコン側で1秒間に176400バイト送ったつもりでも、USB DACのクロックで見ると「1秒間あたり16バイト足りない」とか「毎秒4.5バイト多い」という状況が当たり前のように発生します。

この辻褄合わせをするために、USB DACの側から「データを少し減らして」とか「増やして」と細かく指図するのがフロー制御です。

USBオーディオのAsynchronous転送フロー制御は破綻した

ここから、玄人向けの記述になります。

USBオーディオのフロー制御が決められたのは、USBオーディオクラス1の時代です。 まだUSBの最高速度がFullSpeed = 12Mbpsで「24/96のDACで充分だろう」と思われていた時代です。

2020年の現在、USB HiSpeedで352.8kHzfsとかDoPで5.6MHzが再生できないと、HiFi機器として認めてもらえません。 規格を決めた人の想定外だと思いますし、いろいろ破綻しています。

破綻の理由は、転送量が多すぎて、転送速度が早すぎるからです。 フロー制御指示をするためのフィードバックが間に合わずに、バッファアンダーラン/オーバーランが発生しやすくなっています。

おまけにDoPなんていう規格もあります。 DoPが要求している動作は、「仕様を書くのは簡単」で「作るのは大変」なものです。 すこしだけ『エレキ工房No.5』で解説しました。

USBオーディオのAsynchronous転送プロトコルでハイレゾデータの転送が大変なため、バルク転送で逃げるメーカーも出てきました。 最初にUSBオーディオでバルク転送を使ったのはElectroArtこと田力氏が、某オーディオメーカーの下請けで作成したシステムでしょう。 Webmasterが『エレキ工房No.5』に書いたのはその次でしょうか。

バルク転送のフロー制御は、オーディオのフロー制御よりも単純です。 512バイトづつデータを送りつけて、Ackを確認します。 USBオーディオのターゲット側で受け取れなくなったらAckを返しません。 ホスト側はAckが返ってくるまで同じデータを再送します。

バルク転送でUSBプロトコル的には処理が簡単になりました。 ターゲットも作りやすいです。 でもホスト側は、大変になりました。

バルク転送でオーディオデータを送るには、Ackが返ってこなかった時のリトライを短い間隔で行う必要があります。 パソコンが用意しているハードウェアタイマーの割り込み間隔では足りないことがあるのです。 あるいは、OSのAPIに適切なタイマー設定がなかったりします。

バルク転送でオーディオデータを送るために、Ackが返ってこなかった時にイベントドリブンではなくポーリングでタイミングチェックして、CPUパワーを使ってしまうことがあるのです。

USBオーディオプロトコルの別の問題

USBオーディオクラスのプロトコルには、もう一つ問題があります。 ホストからターゲットに対してサンプリング周波数やボリューム設定などのコマンドを出せます。 ターゲットでは、指示された処理の間ホストを待たせて、処理が終わったらAckを返します。 このAckを返すまでのタイミングが規格に決められていないのです。

Ackタイミングが決められていないため、あまりホストを待たせるとホスト側がタイムアウトして同じコマンドを繰り返し送ってきます。 すぐにタイムアウトする気の短いOSもあります。

ターゲット側にも事情があります。 サンプリング周波数設定時には、まず22MHz/25MHzの2系列のマスタークロックを切り替えます。 その後で、DAC LSIの設定をしますが、LSIによってはI2Cでコマンドを送らなければなりません。 丁寧に確認とリトライをしていると、すぐに数ミリ秒経過してホスト側がタイムアウトしてしまいます。

ホスト側が待ってくれない場合、ターゲット側はコマンドを受け取った時点でAckを返して、非同期に処理を進めることになります。 ターゲット側は「すでに処理が終わっている」と思っているものだから、平気で次のコマンドを送ってきます。

サンプリング周波数のコマンドにAckを返した後で処理を続けていると、ホストから現在のサンプリング周波数が切り替わったかどうか問い合わせが来たりします。 まだ切り替え作業中だった場合、切り替え前後のどちらの周波数を返答するべきか迷います。 切り替え前の周波数を返すとホストに「このUSB DACはサンプリング周波数を切り替えてくれない」と思われます。 切り替え後の周波数を答えてしまうと、まだ切り替え中なのに音声データが送られて来たりします。

ここでホストを待たせるハンドシェークが規定されていないのは、USBオーディオプロトコルの欠点の一つです。

DSDネイティブ転送路の話

DoP以外に『DSDネイティブ転送』をうたったUSB DACデバイスがあります。 このネイティブ転送路について、調べた範囲で具体例で解説します。

まず、descriptors.zipに3種類のUSBオーディオインタフェースのディスクリプタを示します。 Amanero Combo384, DIYINHKのXMOS, SMSLのm100をそれぞれLinuxにつないで、lsusbコマンドで表示させた内容です。 以下、ディスクリプタの読み方を知らない人にもわかるように解説します。

まず、Amaneroのディスクリプタです。 通信路は2つあります。 どちらもPCMで2チャンネルの4バイトに32ビットデータを格納します。

AmaneroのDoPも32ビットで送ります。 DoPはそもそも3バイト24ビットに、8ビットのマーカーと16ビットのデータを格納する仕様でしたが、Amaneroはさらに8bitのパディングを加えて4バイト32ビット転送します。

次に、DIYINHKのXMOSのディスクリプタです。 通信路は6つありますが、3種類を2回繰り返しています。 PCMが二つ、2チャンネル4バイト24ビットと2チャンネル2バイト16ビットです。 RAWが一つ、2チャンネル4バイト32ビットです。

DIYINHKのXMOSのディスクリプタにはオーディオインタフェースの他にHIDインタフェースが登録されています。 おそらく、ファームウェア更新用のインタフェースだと思われますが、未確認です。

最後に、SMSL m100のディスクリプタです。 通信路は6つありますが、3種類を2回繰り返しています。 PCMが二つ、どちらも2チャンネル4バイト32ビットです。 RAWが一つ、2チャンネル4バイト32ビットです。

ざっとディスクリプタを眺めてみました。 AmaneroがどうやってDoPとDSDネイティブを切り替えているのかは不明ですが、XMOSを採用しているDIYINHKとSMSL m100ではRAWフォーマットがDSDネイティブの通信路のようです。 Linuxで強引にRAW通信路をSND_PCM_FORMAT_DSD_U32_LEとみなすパッチを作ってみました。

% diff sound/usb/format.c.org sound/usb/format.c
67c67
< 			pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL;
---
> 			pcm_formats = SNDRV_PCM_FMTBIT_DSD_U32_LE;

このパッチをあてたカーネルも、このページで公開します。

DIYINHKのXMOSがDSDネイティブを32bit little endianで転送することは、テストデータを送りながらロジアナで監視して確認しました。 SMSL m100は、同じXMOSだから同じフォーマットだろうという当てずっぽうです。 その他のデバイスがRAWフォーマットを使うことがあるかもしれません。 それがDSDネイティブである保証も、32ビット little endian である保証もありません。

OS別USBオーディオドライバの話1 Windows

WindowsにUSBオーディオクラス2のデバイスをつなぐ時は、つい最近までデバイスに対応したドライバをデバイスメーカーに供給してもらう必要がありました。 マイクロソフトのドライバは、クラス1までにしか対応していなかったのです。

Windows10の途中から、オーディオクラス2のデバイスドライバがマイクロソフトから供給されたようです。 Webmasterの自宅では、Amanro Combo384やSMSL m100をマイクロソフトのデバイスドライバで再生しています。

オーディオクラス1のドライバには、フロー制御のフィードバックに対する応答が遅いという問題がありました。 オーディオクラス2でも応答の遅さは同じようです。

OS別USBオーディオドライバの話2 Linux

WebmasterはALSAのUSBオーディオドライバのソースコードを読んで、以前から気になっていることがありました。

フロー制御のフィードバックが返ってきた時に、「本来の速度の75%未満」だと勝手にフィードバック量を水増ししてしまいます。 逆に「本来の速度の150%」より大だと、フィードバック量を減らします。 例えば、USBオーディオ側が「60%に抑えて」とフィードバックを返してきた時に、「60%は範囲外だから120%に違いない」という判断をしてしまうのです。 減らさなければならない時に増やしてしまいます。

ソースコードのコメントを読むと、USBオーディオの規格から外れた動作をするUSB DACを救済する措置のようです。 規格外のDACは救済されるかもしれませんが、規格どおりに動作しつつフィードバックが75%未満や150%オーバーになった場合、誤動作します。 誤動作の結果、DACデバイス側でバッファアンダーラン/オーバーランが発生しやすくなります。

Webmasterは、ALSAのコードを書き換えてみました。 USBオーディオクラスの規格どおりに動作する代わりに、フィードバックが50%〜200%まで追従できるドライバです。 カーネルの sound/usb/endpoint.c に以下のパッチを当ててください。

2020年3月29日追記

初出のパッチにはバグがあったので、3月28日深夜にカーネルパッケージと合わせて差し替えました。

67a68
>  * (fs in Q26.6)
76a78
>  * (fs in Q29.3)
660,661c662,663
< 	/* assume max. frequency is 50% higher than nominal */
< 	ep->freqmax = ep->freqn + (ep->freqn >> 1);
---
> 	/* assume max. frequency is double value of nominal */
> 	ep->freqmax = ep->freqn * 2;
1187c1189
< 	if (unlikely(sender->tenor_fb_quirk)) {
---
> 	if (unlikely(ep->freqshift == INT_MIN)) {
1189,1191c1191
< 		 * Devices based on Tenor 8802 chipsets (TEAC UD-H01
< 		 * and others) sometimes change the feedback value
< 		 * by +/- 0x1.0000.
---
> 		 * This driver assumes all USB audio target follow standards.
1193,1212c1193,1194
< 		if (f < ep->freqn - 0x8000)
< 			f += 0xf000;
< 		else if (f > ep->freqn + 0x8000)
< 			f -= 0xf000;
< 	} else if (unlikely(ep->freqshift == INT_MIN)) {
< 		/*
< 		 * The first time we see a feedback value, determine its format
< 		 * by shifting it left or right until it matches the nominal
< 		 * frequency value.  This assumes that the feedback does not
< 		 * differ from the nominal value more than +50% or -25%.
< 		 */
< 		shift = 0;
< 		while (f < ep->freqn - ep->freqn / 4) {
< 			f <<= 1;
< 			shift++;
< 		}
< 		while (f > ep->freqn + ep->freqn / 2) {
< 			f >>= 1;
< 			shift--;
< 		}
---
> 		shift = (urb->iso_frame_desc[0].actual_length == 3) ? 2 : 0;
> 		shift -= ep->datainterval;
1214c1196,1197
< 	} else if (ep->freqshift >= 0)
---
> 	}
> 	if (ep->freqshift >= 0)
1219,1233c1202,1204
< 	if (likely(f >= ep->freqn - ep->freqn / 8 && f <= ep->freqmax)) {
< 		/*
< 		 * If the frequency looks valid, set it.
< 		 * This value is referred to in prepare_playback_urb().
< 		 */
< 		spin_lock_irqsave(&ep->lock, flags);
< 		ep->freqm = f;
< 		spin_unlock_irqrestore(&ep->lock, flags);
< 	} else {
< 		/*
< 		 * Out of range; maybe the shift value is wrong.
< 		 * Reset it so that we autodetect again the next time.
< 		 */
< 		ep->freqshift = INT_MIN;
< 	}
---
> 	spin_lock_irqsave(&ep->lock, flags);
> 	ep->freqm = f;
> 	spin_unlock_irqrestore(&ep->lock, flags);

このソースコードでビルドしたカーネルパッケージも公開します。 XMOSのRAW通信路を強引にSND_PCM_FORMAT_DSD_U32_LEとみなすパッチも入っています。

linux-image-4.15.18-strict-uac_015_amd64.deb
linux-headers-4.15.18-strict-uac_015_amd64.deb

OS別USBオーディオドライバの話3 Mac OS X

Mac OS Xでも、Snow Leopardの時代にLinuxと同じフロー制御のロジックが入っていました。 Mavericksの頃には修正されたみたいです。

USBオーディオクラス2のフロー制御に限って言えば、Mac OS Xは一番安定しているOSです。 でも、ビットパーフェクト再生する時に24bitデータまでしか渡せないという問題があります。 32bitデータを送ることでDSDネィティブ再生するUSB DACには、専用のデバイスドライバを用意しなくてはなりません。

Snow Leopardの頃には、俗に「integerモード」と呼ばれるAPIがあって、32bit整数を送ることができました。 その後、スーパーユーザーでないとopenできないようになり、やがてだれもアクセス出来なくなりました。 もしかしたら、まだ裏口が残っているのかもしれませんが、webmasterは見つけていません。

2020年3月28日 初出

2020年5月16日 追記


back button オーディオ譚へ