Unityでビデオキャプチャデバイス(GV-USB2)から受け取った音声入力をリアルタイム再生しようとした話1 デバイス検出処理検証

この記事を書き始めてからだいぶ時間が経過してしまい、

元々書いていたブログサービスが終了して、

やや入力や表示のフォーマットずれが起きている事、

当時の記憶が曖昧になった事、

テストプロジェクトの衛生状態が情報を整理しにくい事、

整理していくに連れて記事内の記述の区分分けが不適切である事等が分かったので、

おそらくそのうちまた書き直すと思います。

 

 

まずは各ライブラリの音声入力デバイスを検出する処理について検証します。
GV-USB2が音声入力デバイスとして検出されない事には
性能の検証やリアルタイム再生の技術検証をしても意味が無いからです。

始めに

筆者はあんまりネイティブやデバイスやライブラリの規格や仕様の事は詳しくないので、
手当たり師団検証して結果をつらつら書いていくような内容になります。

おそらく、もっと最適な解や途中解を用いた効率的な検証方法等あると思うので、
技術的な知見を広める為の記事では無く、
主に同じく
基礎知識が無く手探りで検証をしていこう
と思っている人向けの記事になるかと思います。

検証環境に関しても疎いながら思い浮かぶ事を一応書いておきます。
マシン:BTOのミニタワーデスクトップ
OS:Windows10(64bit)
CPU:i3-6100
メモリ:12GB
グラボ:マザーボード標準
Unity:2019.4.5f1

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

また、元々ここでの答えの通り対応したい人も居るかも知れないので
先に結果だけ書いておきますが、
見る限りGV-USB2が音声入力デバイスとして検出されるライブラリはありませんでした。

なので、本来GV-USB2に接続する赤端子と黄色端子をステレオミニプラグに変換して
ライン入力に繋ぐアプローチを取る予定です。


それでは検証本文に入ります。

1.Unity標準

1-1.UnityEngine.Microphone
正常動作
GV-USB2の検出 ×

UnityEngineにはMicrophoneと言う標準APIがあります。

UnityEngine.Microphone.devicesMicrophone.devices
にUnityEngineで使用出来るサウンド入力デバイス名が列挙されます。

ただ、どうもGV-USB2はオーディオインプットデバイスとして列挙されません。

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


2.DirectShow(C#)

2-1..NET
正常動作 ×
GV-USB2の検出 ×

http://blog.livedoor.jp/cogorou/DSLab/TestVarious.html
Unity5系の時にビデオ入力を撮ろうとした時にやった事があるが正しく動かなかったもの。

多分5系のDLLは持ってくる方法が調べればいっぱい出てくるし、
間違いにくい構造をしていたと思うので、
多分間違っていないんじゃないかなと思うが、
当時果たしてその時に正しいDLLを引っ張って来れていたのかは分からない。

ただし、今回改めて必要なDLLをPluginsに引っ張って来て、
UnityEngine.MonoBehaviour系アダプタを作って動かしてみたがやはり動かなかった。

Api Compatibility Levelが
.Net Standard 2.0の時は
System.Type.GetTypeFromCLSID()
にて以下のエラー
System.NotImplementedException: The requested feature is not implemented.
が、
.Net 4.xの時は
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)
が出力される。

.Net Standard 2.0時のエラーは.Net 4.xに変更する事で解決し、
.Net 4.x時のエラーはCOMオブジェクトを作れないようだが、
これを追及するのは気が引けるので、
このアプローチは断念することにした。

2019系では5系などに比べてDLLの場所がややこしくなっている。
これに関しては多分別の機会に紹介すると思うので、
今回は正しいDLLの場所に関しては省略する。


3.ラッパー

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

3-1.アレンジ系ラッパー

オリジナル書式で書かれているので他のラッパーと処理フローに互換性が無いと思われるもの。

3-1-1.DirectShowNet
正常動作 ×
GV-USB2の検出 ×

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

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

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

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


3-2.ラッパー

元のフローに沿ってラップしていると思われるので他のラッパーと処理フローに互換性が有るもの。

3-2-1.NAudio1.10

NAudioはNuGetが必要そうなので、
UnityでNuGetを使えるようにする奴を入れる。

NuGetはApi Compatibility Levelを参照した上で
適切なライブラリをダウンロードするので、
NuGetでダウンロードする前に設定する。

もし先にダウンロードしてしまったら、
アンインストールしてApi Compatibility Levelを変えて再びインストールする。

3-2-1-1.NAudio1.10 Wave名前空間
正常動作
GV-USB2の検出 ×

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

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

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

他にも微調整等したかも知れないが忘れたので省略。
実行すると特にエラー出ず。
ただし
NAudio.WaveIn.GetCapabilities(x).ProductName
をログ出力してもGV-USB2の列挙は見当たらなかった。


3-2-1-2.NAudio1.10 CoreAudioApi名前空間
正常動作 ×
GV-USB2の検出 ×

3-2-1-1の結果を元に調べていたところ、以下
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()
をリーチさせても同じようなエラーが出る。

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


3-2-1-3.NAudio1.10 XAudio2名前空間

無さそう。未検証。


3-2-2.NAudio2.00

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

 

3-2-2-1.NAudio2.00 Wave名前空間
正常動作
GV-USB2の検出 ×

https://teratail.com/questions/186223
WaveはWaveInEventに変わってるようです。
構文エラーにインテリセンスを順番に決めて正常動作。

ただし変わらずGV-USB2に関する列挙は見当たらない。


3-2-2-2.NAudio2.00 CoreAudioApi名前空間
正常動作 ×
GV-USB2の検出 ×

NAudio.CoreAudioApi.MMDeviceEnumerator.EnumerateAudioEndPoints ()
が変わらず動かないようだ。
残念。

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


3-2-2-3.NAudio2.00 XAudio2名前空間

無さそう。未検証。


3-2-3.CSCore(NuGet)

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

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

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

3-2-3-1.CSCore(NuGet) Wave名前空間
正常動作 ×
GV-USB2の検出 ×

インテリセンスを漁って行くとCSCore.SoundIn名前空間に互換のある処理が見つかる。

CSCore.SoundIn.WaveInDevice.EnumerateDevices().ToList().Count
からデバイス数は取得出来るが,
CSCore.SoundIn.WaveInDevice.EnumerateDevices().ToList()[x].Name
が常にNullでデバイスを識別出来ない。


3-2-3-2.CSCore(NuGet) CoreAudioApi名前空間
正常動作
GV-USB2の検出 ×

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

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

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


3-2-3-3.CSCore(NuGet) XAudio2名前空間
正常動作 ×
GV-USB2の検出 ×

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
キャストしようとしても正しく動かない。


3-2-4.CSCore(NuGet)

 

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

 

3-2-5.SharpDX

多分NuGetでXAudio2とかで検索して出てきたんだと思う。

3-2-5-1.SharpDX Wave名前空間

不明。未検証。


3-2-5-2.SharpDX CoreAudioApi名前空間

不明。未検証。


3-2-5-3.SharpDX XAudio2名前空間
正常動作 ×
GV-USB2の検出 ×

多分名前空間を差し替えるだけで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 ()
と出る。

 

 

3-2-6.MFLib

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

 

3-2-6-1.MFLib Wave名前空間

無さそう。未検証。


3-2-6-2.MFLib CoreAudioApi名前空間

無さそう。未検証。


3-2-6-3.MFLib XAudio2名前空間

無さそう。未検証。



3-2-6-4.MFLib ___名前空間

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


 

メモ

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

 

 


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

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

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

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

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

では、またそのうち。