GitHubで公開しているAndroidのUnityアプリでUVC機器からの映像を取得するためのプラグインのリポジトリUVC4UnityAndroidにWebカメラから音声を取得したいんだけどってリクエストが来てました。
UVC4UnityAndroidでUVC機器から映像を取得するのに使っているオレオレライブラリ自体はUAC1.0に対応していて音声取得も可能です。
なのでUnity側へ引き渡す方法をちょっと調べてみようかなぁ〜と軽い気持ちで調べ始めたところ…驚愕の事実が発覚😱
いつからかわかりませんが、オレオレライブラリのUAC機能が動作しなくなっているのです😱
より正確にはUAC機能が動作しなくなった端末がいくつかあるという状態、とりあえずすぐに確認できる端末ではPixel3(Android12)を除いて全部だめでした😭
ということで色々調べてみました。
- MotoG100/MotoG10/Pixel6pro/Fire HD 8 Plus/Fire HD 10 Plus/Aquos Widh SM-20はだめ、Pixel3は大丈夫なのでAndroidのバージョンがどうということじゃないみたいだけど比較的新しいのは全滅な感じ。
- 不具合の内容としては、UACのインターフェースを指定したUSBDEVFS_CLAIMINTERFACEのioctl呼び出しがerrno=EADDRNOTAVAIL(99)で失敗する。
- グルグルしてみましたがそのものズバリは見当たらす、ただしUAC/USB DAC関係は2020-2021年頃から今に至るまで不具合多発してる感じ。
- 再起動したら改善するかも?
何の効果もなかった😭 - 録音のパーミッションの有無で改善するかも?
何の効果もなかった😭 - 開発者オプションにある「デフォルトのUSB設定」で改善するかも?
何の効果もなかった😭
USB DACが使えなくなった場合には「デフォルトのUSB設定」を「データ転送なし」にすることで改善する場合があるらしいです。 - 開発者オプションにある「USBオーディオルーティングを無効化」で改善するかも?
何の効果もなかった😭 - アプリのAPIレベルやターゲットAPIレベルを下げると改善するかも?
何の効果もなかった😭 - AndroidのIssue TrackerにあがってるPixel6/6pro等でUSB DACへ音声出力できない件に関係あるかも?
Pixel 6 prevents custom USB audio driver from playing
よくわからんけど解決の手がかりにはならなかった。
なんもわからん💫
ところで、ご存じの方は少ないかもしれませんが元々AndroidでのUAC機器アクセス時にはずぅ〜っと昔から問題がありました(知ってる限りでは2017年の時点でも問題あった)
何かというとUAC機器のインターフェースをclaim(USBDEVFS_CLAIMINTERFACEのioctl呼び出し)する場合、呼び出し元のスレッドがJavaVMにアタッチしていないとだめというイミフな制約です。もうちょっと詳しく言えば、JavaVmがアタッチしていないネイティブ側のワーカースレッド上でclaimするとerrno=EBUSYでずっこけるというものです。
というかこんな制約よく見つけたな昔の自分👍
想像では、Android OS/カーネルドライバー側がUACのインターフェースを常時握りしめてて、ユーザーランドのアプリがclaimしようとしたときにAndroid OS/カーネルドライバー側が一時的に手放すような動作になっているんじゃないかなと。結果としてnative側から呼び出したときにAndroidOSのJava側へのアクセスが必要になるためにJavaVMへアタッチしてないとだめだった、みたいなことかなと。
そんなこんなで丸一日近くグダグダしてもうだめかなぁと思っていると、降ってきました📡
先の想像通りなのであればJava側ならUAC機器のインターフェースをclaimできる可能性がありそうです。
つまり「あらかじめUACのインターフェースをJava側でclaimしとけばいけんじゃね?」
とりあえずUsbDeviceからUACのインターフェースに対応するUsbInterfaceを取得するようにして、Java側でclaimするようにしてみたところ、ちょっと動作が安定しないものの今まで100%動かなかったのが動くようになりました\(^o^)/
ちなみに、Java側でclaimするにはUsbDeviceConnection#claimInterface
、claimを解除するにはUsbDeviceConnection#releaseInterface
を使います。
ということで今日のワークアラウンドは
- native側でUACへアクセスする場合にはあらかじめJava側でインターフェースをclaimInterfaceしておく。
- 使い終わったらJava側でreleaseInterfaceを呼んで解放する。
なんにしても、ドキュメントにない破壊的デグレを定期的にぶち込むのはいい加減やめてほしい😠