Unityでビデオキャプチャデバイス(GV-USB2)から音声を録音、リアルタイム再生しようとした話

UnityEngineにはMicrophoneと言うAPI?があります。
ただ、どうも少なくともGV-USB2はオーディオインプットデバイスとして列挙されないんですよね。
そこでデバイスを列挙する処理に関して色々検証してみた事を
記事にしてみようと思います。

一応現状の結果としては物理的な対応をする事にしようとは思っているのですが、
自分としても実際に遅延検証に入る前段階として
現状を整理したいなと思ったので綴っていきます。

始めに

筆者はあんまりネイティブやデバイスやライブラリの規格や仕様の事は詳しくないので、
論理的と言うよりは検証をつらつら書いていくような内容になります。
おそらく、もっと最適な解や途中解を用いた効率的な検証方法等あると思うのですが、
同じく基礎知識が無くて手探りで検証をしていこうと思っている人に向けての記事になるかと思います。

あと、そう言う前提で、思い浮かぶ環境を書いておきます。
マシン:BTOのミニタワーデスクトップ
OS:Windows10(64bit)
CPU:i3-6100
メモリ:12GB
グラボ:マザーボード標準
Unity:2019.4.5f1

検証作業の途中にあるライブラリのインポート等に関しては語ったり語らなかったりすると思います。
自分も今回初めてやった事も多かったですが、調べると出て、
その通りやったら出来たと思うので、
全般的な作業手順については書いてなかったら自己補完して対応して頂ければと思います。

1.UnityEngine.Microphone

前述になりますが、UnityEngineにはMicrophoneと言うAPI?があります。
UnityEngine.Microphone.devicesMicrophone.devices
にUnityEngineで使用出来るサウンド入力デバイス名が列挙されていて、
UnityEngine.Microphone.Start()の引数で指定してやることで
UnityEngine.AudioClipにリアルタイム出力させてやるように出来るのですが、
UnityEngine.Microphone.devicesMicrophone.devices
にはGV-USB2が列挙されません。

ちなみに確か、
同じくビデオ入力デバイス名が取得出来、
GV-USB2の名前が確認出来る
UnityEngine.WebCamTexture.devices[x].name
を指定したり、
他のソフトから見た名前を指定してStartしてやってもうまく行かなかったと思います。

2..NET

正直何やってるのかあまり分かってないですが、取り敢えずここ
http://blog.livedoor.jp/cogorou/DSLab/TestVarious.html
の内容をUnityにぶっ込んで
必要なDLLをPluginsに引っ張って来て、
UnityEngine.MonoBehaviour系アダプタを作って動かしてみるアプローチ。

この検証はUnity5系の時にビデオ入力を撮ろうとした時にやった事があって、
果たしてその時に正しいDLLを引っ張って来れていたのかは怪しいが、
多分5系のDLLは持ってくる方法が調べればいっぱい出てくるし、
間違いにくい構造をしていたと思うので、
多分間違っていないんじゃないかなと思う。

それを改めて動かすと
System.Type.GetTypeFromCLSID()
にて以下のエラーが出る。
System.NotImplementedException: The requested feature is not implemented.

当時はこれで諦めたし、これに関して更に追及してみる予定は無いのだが、
取り敢えず現在のバージョンでどのような挙動になるかは確認する事にした。

2019系ではDLLの場所がややこしくなっているが、
結論から言うとこの案は現状使う予定が無く、
掘り下げる予定も無ければ、
多分別の機会に紹介すると思うので、
今回は正しいDLLの場所に関しては省略する。

また、これもその時にも説明すると思うが、
.Net Standard 2.0
はDLLの構成の都合、
依存関係で再生時だかビルド時だかにエラーを吐くのと、
そもそも.Netのバージョンも上げないと多分あんまり意味がないので、
PlayerSettingsからApi Compatibility Levelを
.Net 4.x
に変更する。

諸々整えた後、改めて動かすと先程の
System.Type.GetTypeFromCLSID()
は通過するようだが、直後の
System.Activator.CreateInstance ()
にて
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.MethodAccessException: Method `System.__ComObject..ctor()' is inaccessible from method `System.__ComObject..ctor()'
at (wrapper managed-to-native)
...
と言うエラーが出力される。
見る感じCOMオブジェクトを作れないようだが、
これも追及するのは気が引けるので、
このアプローチは断念することにした。


この辺りからは主にDirectShowラッパー系の検証です。

3.DirectShowNet

基本的にはここ
https://blog.ishitoya.info/entry/20080524/1211598985
に書いてある通りに進める。

CodeProjectのリンクは消失していて、
ざっくりは探したが見つからず、
そもそもこの技術がポンと使えそうかどうかが分からないと、
その辺りを追求する意味も無いので、
取り敢えずはリンク先で更に参考にしたと書かれているページの
サンプル内に入っているDirectShowNetを摘まみ食いする事にする。

他にも微調整等したかも知れないが忘れたので省略。
基本的には2に同じく必要なDLLをPluginsに引っ張って来て実行。
すると
DsDev.GetDevicesOfCat()
が成功せず
outパラメータへの出力も確認出来ない為以後の処理を実行出来ない。

これも追及するのは気が引けるので、
このアプローチは断念することにした。

なんかNuGetでDirectShowLibをインストールしてるがこれテストしてなくない?
↑結構日を跨ぎながら色んな事検証してたので
こう言うのがある気がするのもあって文書に起こしてみようと思ってたんですよね。
記事書いていくうちでこれを使ってるテストプロジェクトがあれば
ここの記述は消しますが、
DirectShowNetのラッピングの形とDirectShowLibのラッピングの形で
外からコールに互換性を見出せなくて辞めたんだった気がしますが、
それにしても他のサンプルと互換性が無いか確認したり、
ここ
https://qiita.com/tera1707/items/df751cae4cec4e6da0db
のサンプルの検証したりしてないとおかしい気はします。
一旦保留。


NAudioに関しては掘ったり検証していくに連れて、
途中でバージョンが上がったり複数のデバイス列挙機構を内包していたりしたので、
一個一個大項目を区切って行く。

NAudioはNuGetが必要そうなので、
UnityでNuGetを使えるようにする奴を入れる。
NuGetはApi Compatibility Levelを参照した上で
適切なライブラリをダウンロードするので、
NuGetでダウンロードする前に設定する。
もし先にダウンロードしてしまったら、
アンインストールしてApi Compatibility Levelを変えて再びインストールする。

4.NAudio(1.10/NAudio.Wave名前空間)

Unityに入れてみた先駆者が居たので、こちら
https://qiita.com/nise_aoi/items/7923ab29a678aa5a9b14
に沿って進める。

ちなみにApi Compatibility Levelどちらでも変わらなかった気がするけれど、
この記事を書いている段階の設定は.Net4.xが設定されている。

記事元を追っていくとUnityがハングする理由に依存DLLが上がっているが、
そちらに関する検証はもっと先の内容に当たるので、
一旦ハングしてもデバイス列挙の挙動を見る事を優先、
NAudioの検証にはDLLは一旦必要ないものとする。

他にも微調整等したかも知れないが忘れたので省略。
実行すると特にエラー出ず。
ただし
NAudio.WaveIn.GetCapabilities(x).ProductName
をログ出力してもGV-USB2の列挙は見当たらなかった。
つまり、
処理自体は正常動作するが期待する結果は得られなかった。

5.NAudio(1.10/NAudio.CoreAudioApi名前空間)

4の結果を元に調べていたところ、以下
https://teratail.com/questions/53571
のようなQ&Aを見つけた。

Q&Aには別の関数を使ったと書かれていたので調べていくと、
別の別のデバイス列挙処理が出てきたのでこちらも検証。

https://ja.ojit.com/so/c%23/3223207
をMonoBehaviour化し実行。

すると
new NAudio.CoreAudioApi.MMDeviceEnumerator().EnumerateAudioEndPoints ()
にて以下のエラーが発生した。

Object reference not set to an instance of an object

厳密には
new NAudio.CoreAudioApi.MMDeviceEnumerator()
までは正しく動いているかどうかは分からないがコール上は成功している。

コピペだとアンリーチではあるものの、
ソースコード上は存在している
new NAudio.CoreAudioApi.MMDeviceEnumerator().GetDefaultAudioEndpoint()
をリーチさせても同じようなエラーが出る。

これも追及するのは気が引けるので、
このアプローチは断念することにした。

6.3のNAudio(1.10)化

3のDirectShowNetDLLをNAudioDLLに置換して
正常動作しなかった
DsDev.GetDevicesOfCat()
をNAudioの互換処理に置換するアプローチ。

ヒエラルキ等を漁ったものの
NAudioではDirectShowNetの書式に互換のある書式は無さそうだった。

後にも似たような事をやっていくが、
見る限りDirectShowNet側がカスタマイズ書式の印象。

NAudioはマイクロソフトが出してるとかだった気がするし、
他言語のマイクロソフトテクノロジを忠実にラップしているだけだとすると
辻褄も合うような気はする。


NAudioに関しては、
他のライブラリの検証をやってるうちにバージョンアップしていました。

既にバージョンアップは過去の事象で、
どちらとも既存のテクノロジには変わりなく、
記事上でライブラリを往復したくないので、
先に同じNAudioの2.00の検証内容を書きます。

7.NAudio(2.00/NAudio.Wave名前空間)

WaveIn系の処理に互換があるものが見つからなかった。
元々1.10でデバイスが列挙されていて、
列挙して欲しいデバイスは列挙されていなかった事は確認していたので、
こちらは省略する事にした。

なんか手掛かりらしきものを見つけたので一応コピペ。
https://teratail.com/questions/186223
実際にインテリセンスで見たりしたわけではないので
自分の問題に一致するヒントかは分からない。
2.00は暫くベータリリースだったようなので、
このアンサーが2.00に関する情報の可能性はあり。

8.NAudio(2.00/NAudio.CoreAudioApi名前空間)

Object reference not set to an instance of an object
at NAudio.CoreAudioApi.MMDeviceEnumerator.EnumerateAudioEndPoints ()

変わらず動かないようだ。
残念。

一応DLLの構成が変わっていて、
参照DLL名自体は変わっているようには見える。

5に同じくこのアプローチは断念。


5に関してQ&Aを投げていた人を発見した。
https://stackoverflow.com/questions/48217715/c-sharp-unity-3d-naudio-throws-nullreferenceexception-while-checking-for-default
この人はCSCoreと言う上位互換を発見したと言っているので、
NuGetからCSCoreを入れてお手並みを拝見することにした。

ただしNuGetからダウンロードしようとしても
.nupkgはダウンロードされているがDLLが生成されない。

どこかで見たが、.nupkgはzipらしいので、
複製を.zipにリネームし展開してみる。
ディレクトリ名がマッチの範囲外なのか、
net35-client
と言うディレクトリがあるので、
これを他のNuGetでダウンロードしたものと同じ形になるように
プロジェクトに配置する。

9.5のCSCore化

7に同じくWaveIn系の処理に互換があるものが見つからなかった。
元々1.10でデバイスが列挙されていて、
列挙して欲しいデバイスは列挙されていなかった事は確認していたので、
こちらは省略する事にした。

10.6のCSCore化

https://github.com/filoe/cscore/blob/master/Samples/AudioPlayerSample/MusicPlayer.cs
この辺をヒントに置換していく。
型に関する定義やパラメータフォーマット等は違うようだが、
基本的な処理フローは大体同じようだ。

パラメータを
DataFlow.All, DeviceState.All
とすると40近いデバイスが列挙されるが、
GV-USB2と思われる名前は確認出来なかった。

ちなみにステートをAllにしているが、
これはどうやら認識された事はあるが
現在接続されていないものまで検出しているようだ。

これも
処理自体は正常動作するが期待する結果は得られなかった。

11.CSCore.XAudio2名前空間

https://github.com/filoe/cscore/blob/master/CSCore.Test/XAudio2/XAudio2Tests.cs
詳しくは知らないが、
CSCore.XAudio2.XAudio2.CreateXAudio2()
の実態が
CSCore.XAudio2.XAudio2_7
の場合は
CSCore.XAudio2.GetDeviceDetails()
でデバイスを列挙出来るらしい。

自分の環境は
System.Object.GetType()
を取ると
CSCore.XAudio2.XAudio2_8
だった。
勿論強引に
CSCore.XAudio2.XAudio2_7
キャストしようとしても正しく動かない。

12.AssetStoreのCSCore

ダウンロードはしてみたものの、
どうやらDirectShow関連のものとは全く別の
C#の標準的な機能のラッパーのようだ。

13.11のSharpDX化

多分NuGetでXAudio2とかで検索して出てきたんだと思う。
多分名前空間を差し替えるだけでXAudio2_7のコードが動いたが、
内部でXAudio2_7とXAudio2_8のアダプタをしているわけではなく、
InvalidOperationException: This method is only valid on the XAudio 2.7 requestedVersion [Current is: Version28]
SharpDX.XAudio2.XAudio2.CheckVersion27 ()
と出る。


ここでMediaFoundationと言う名前空間を見つける。(確か。
今までの書式に互換性が無くて諦めたんだった気がするが
このページを見ながらやった記憶があまり無いので一応置いておく。
https://github.com/sharpdx/SharpDX-Samples/blob/master/Desktop/MediaFoundation/MediaEngineAppVideo/Program.cs

14.MFLib

https://qiita.com/kenjiuno/items/fa8ff3483dfc2b48c466
MF.EnumVideoDeviceSources()
でUnityがハングする。


一旦途中経過まで。
まとめてると検証漏れだったりまだ簡単に検証出来そうな事が幾つかあったのと、
思ったより自分がやっている検証とそれを元に書いている文章のまとまりが無さ過ぎるので、
この記事を元にそのうちもう一回検証して同じ内容の記事を作ると思います。

まあ何を参考にして何をやったかが全然分からなくなっているわけですが、
社会人なんですからログとドキュメント化くらいはやっておきましょう。

最低でも一発目の検証でうまく行かなかった事はちゃんとログ付けた方が良いですね、
あまり技術検証とか得意じゃ無いんで今回は良い経験になりました。

多分やり残してる検証をやってから、実際に使うライブラリの検証をやっていくと思います。

余談ですが、AudioClipに変換したりOnAudioFilterReadを使ったりするより
そのままどこかにあるだろうサンプル通りデバイスに直接音を流してしまおうかと思っていたりします。
AudioClipはバッファをAddしていくような構造があるか怪しいと言うか
ぱっと検索した限りサンプルが見当たらなかったのと、
OnAudioFilterReadはAudioListenerに流れてきた波形を上書きするものなので、
ジャックして上書きするか混ぜる計算をやる必要がありそうですが、
前者はUnityのサウンド機能が動かなくなるのと、
後者は専門的な知識が必要そうだからです。

では、またそのうち。