[音声認識周辺] Windowsの仮想カメラを配布するまで

作成:2022年12月12日  最終更新:2022年12月13日

本件は、個人開発 Advent Calendar 2022(カレンダー2)の2022年12月12日づけ記事でもあります。

前日は @_Jun_ さんのリリースまで進み続けるんだ!これはお前が始めた個人開発だろ?でした。

翌日は @nishihara_zari さんの シンプルなスクロールで発火するJavaScriptライブラリ です。


今回作ったものは、2つのツールを組み合わせるものです。


これにより、Zoomなどのビデオ会議で、参加者が自分のカメラ映像に字幕をつけた状態で参加できるため、聞こえない/聞こえづらい人が含まれる状況での話し合いがしやすくなります。これは、以前に2020年5月頃に話題になった下記と同様のものを、より簡単に使えるようにするものです。

speech-to-text-webcam-overlay
https://github.com/1heisuzuki/speech-to-text-webcam-overlay

(参考)
しゃべった内容、リアルタイムで字幕化して映像に合成 ビデオ会議が便利になるシステムに注目集まる - ねとらぼ
https://nlab.itmedia.co.jp/nl/articles/2005/23/news036.html

1つめのツールについては、既に別記事で主要な部分を書いているので、ここでは割愛します。

Webブラウザの音声認識APIを使う:ブラウザ間の差異など
https://mekiku.com/view.php?a=82

元ネタと改変した点


次の記事とコードを使わせていただきました。ありがとうございます。

WinRTのWindowsGraphicsCaptureAPIでキャプチャしたウィンドウをDirectShowで自作した仮想カメラに映そう - Qiita
https://qiita.com/HexagramNM/items/8493350d40608433421c

GitHub - HexagramNM/NMUniversalVCamFilter: WinRTのGraphicsCaptureAPIでキャプチャしたウィンドウを仮想カメラとして映すサンプル
https://github.com/HexagramNM/NMUniversalVCamFilter

こちらで変更・追加したのは主に次のことです。


これだけ?超楽勝では?と思われるところですが、実際にやってみたところ、意外に苦戦したので、ここに記録しておこうというわけです。

なお、コードは冒頭で書いたVCam4Captionにあるので、ここではほとんど割愛しています。

GUIDを変更する


これは本当に簡単で、Visual StudioでGUIDを生成して、定義(元コードの NMVCamFilter.h 冒頭)を書き換えるだけです。クラスID名を変更した場合は、それを使うところも書き換える必要があります。

WindowsはこのGUIDでフィルタを管理するため、既存のものと被るとおかしなことが起こります。私もうっかり同じGUIDで別の仮想カメラを作って謎の挙動に悩まされました。とにかく最初に変えておくことが大事です。

ウインドウをタイトル文字列で選択する


これについては元記事で言及されているので簡単……と思うところですが、実は素直にはいきません。

ウインドウのタイトルを列挙して文字列比較する処理は元記事に言及されているので書いて、元々のピンのコード(NMVCamPin.cpp)に入っていたキー押下対応処理を削除します。しかし、これだけだとビルドが通りません。対策は次のものです。


ここまでの改変で、目的のタイトルのウインドウがあった場合にキャプチャして仮想カメラの映像にすることが可能となります。

映像ソース更新チェックを追加する


ここまでの改変だけだと、ちょっと気になる問題があります。所定のタイトルのウインドウがある時は仮想カメラは正常に動作するのですが、当該ウインドウを閉じても、仮想カメラでは閉じる直前の状態を出し続けてしまうのです。

今回は、CSourceStreamのFillBufferメンバ関数(オーバーライド済み)で、GraphicsCaptureItemオブジェクトが存在し続ける場合に、「現在時刻と最終更新時刻を比較し、一定以上だった場合は改めて対象ウインドウをスキャン、見つからなければ対象がない場合の表示に切り替える」という処理を入れています。

なお最終更新時刻としては、Direct3DのFrameArrivedイベント処理が呼ばれた時点とみなして、その時の現在時刻を(onFrameArrived関数で)メンバ変数に代入することで管理しています。

ここまでで、一通り動作する仮想カメラができあがりました。

インストーラを作成する


今回のツールは、ビデオ会議の出席者にセットアップしてもらう必要があります。このため「regsvr32で仮想カメラを登録」などを利用者に操作してもらうことはできず、自動化しなければなりません。このために、インストーラを作ることにしました。

インストーラ作成にはInno Setupを使用しました。スクリプトでインストーラが作成できるので、コントロールしやすく重宝します。

regsvr32で登録、アンインストール時はregsvr32で登録解除、というバッチファイルを作成し、インストーラで仮想カメラのdll(64bit、32bit)とバッチファイルを配置して、バッチファイルを実行するよう設定します。

Direct3Dのエラー対応


しかし、インストールしてもうまくいかないPCが多数あることが判明しました。うまくいかないPCの場合、一般的なアプリケーションなどでカメラを列挙することすら失敗します。

調査の結果、D3D11CreateDevice関数の呼び出しで異常が起こっていると判明しました。検索すると解決法も見つかりました。Windowsのオプション機能「グラフィックス ツール(Graphics Tools)」を有効にすれば動きます。

(参考)
D3D11CreateDeviceでのエラー[ DirectX アプリケーションが、存在しないか一致しない SDK コンポーネントに依存する操作を要求しました。] - Qiita
https://qiita.com/A567W/items/00fe7680db685fc2902d

Windowsのオプション機能は、dismでバッチファイルから有効化できます。

dism /online /add-capability /capabilityname:Tools.Graphics.DirectX~~~~0.0.1.0


ただし、機能が有効化されているかを次のような方法で確認しても、 %errorlevel% では判定できません(存在する機能なら有効化されてなくても 0 になってしまうので)。

dism /online /get-capabilityinfo /capabilityname:Tools.Graphics.DirectX~~~~0.0.1.0


がんばって判定するなら、dismを /English オプションをつけて実行して標準出力内容を分析する形になりそうですが、今回は無条件で有効化を試みることにしました。有効化済みでも少し待つことになってしまいましたが……。

64bitと32bit


元記事にもあったように、仮想カメラのdllは64bit Windowsなら64bitと32bit両方のdllを用意する必要があります。これは仮想カメラを使うアプリケーションにあわせる必要があるためです。また、64bit Windowsで Program Files (x86) 以下にインストールすると、たとえdllが64bit対応でも、64bitアプリケーションから仮想カメラが動かせません(動きませんでした)。

バッチファイルでdllを登録する際には %PROCESSOR_ARCHITECTURE% を参照して分岐することで、64bitと32bit両方のWindowsに対応できるのですが、Inno Setupでインストール先を自動的に分ける方法は分かりませんでした。このため、64bitインストーラと32bitインストーラは別に作成しています。

もっとも、「字幕つきカメラを動かして仮想カメラ化してビデオ会議に使う」のは、結構負担があるため、メモリが4GiB利用できない32bit Windowsでこのツールを使う意味はあまりなさそうな気がします。

というわけで、これでようやく配布可能なインストーラができあがりました。


◀(前記事)[音声認識03] OBS Studioと仮想カメラプラグインで、字幕つきカメラを仮想カメラにする
▶(次記事)Chromeブラウザにログインしないまま、Googleのサービスを使う

(一覧)[2.技術情報 (tech)]