Asynchronous Capture Screen on Unity 2019.3

Unity 2019.3から、あれこれ自作せずともやっと、ようやく、悲願の非同期スクリーンキャプチャが出来るようになりました。

前提知識として、Unity APIの呼び出しは、一部のAPIを除いてメインスレッドからの呼び出しのみが許可されています。レガシーな同期アプローチは検索すると吐いて捨てるほど出てくるのでそっちを見てください。

スクリーンキャプチャの流れは3工程あり、バージョンアップを経て段階的に非同期可能になっています。

  1. レンダーテクスチャのシステムメモリへの読み戻し
  2. 画像ファイルフォーマットへのエンコード
  3. ファイル書き込み

1は、ScreenCapture.CaptureScreenshotIntoRenderTexture が2019.1で追加され、ようやく非同期でシステムメモリに読み戻せるようになりました。

1で読み戻したメモリはレンダーテクスチャに指定したフォーマットのバイト配列(NativeArray<byte>)ですが、これを一般的な画像ファイルフォーマットにエンコードするには 2017.1から追加されているImageConversionクラスのAPI、ImageConversion.EncodeToPNG など(フォーマット別)を使用します。しかし、これは引数にバイト配列ではなくTexture2D を要求する上に、メインスレッドのみ呼び出し可能なので同期処理を避けられません。
流石に使えないAPIだと思ったのか、2019.3 でバイト配列(byte[])を使用する ImageConversion.EncodeArrayToPNG が追加されました。リファレンスにも”This method is thread safe.”と書かれていて、メインスレッド以外からの呼び出しを許可しているFreeFunctionアトリビュートがついています。

3は、.Net APIで以前から非同期可能です。

これらを組み合わせれば、”ほぼ”非同期にスクリーンキャプチャを行えます。

 

今後改善して欲しいところ。

  • ScreenCapture.CaptureScreenshotIntoRenderTexture リファレンスのサンプルコードが間違っている。
    using (var imageBytes = request.GetData<byte>()) で使用されているが、ユーザがこのNativeArrayに対してusingや明示的なDisposeの呼び出しを行うと、例外が出力される(InvalidOperationException: The NativeArray can not be Disposed because it was not allocated with a valid allocator.)。AsyncGPUReadbackRequest が管理しているはず?
  • NativeArray<byte>からbyte[]の変換をメインスレッドで行う必要があるが、そこで巨大なマネージドメモリのアロケートが走る。
    NativeArray<byte>のまま処理するAPI、つまりJob System with Burstでエンコードを行って欲しい。
  • ImageConversion.EncodeArrayToPNG のエンコード済みバイト列として巨大なマネージドメモリのアロケートが走る。
    必要メモリ量の計算と、事前確保したメモリを使用するAPIが欲しい。NativeArrayでの非同期ファイル読み書きをサポートして欲しい。
  • ImageConversion.EncodeArrayToPNG は出力フォーマットの指定ができない。つまり入力フォーマットと出力フォーマットは同一になる。また、RGBAのデータをRGBしかもたないJPGにすると、rowBytesを指定してもRGBAからRGBへ変換してくれないようだ。
  • 上下が反転している。グラフィクスAPI依存?ImageConversion.EncodeToPNG はTexture2D の段階でそれが打ち消されている?
    ユーザが面倒を見るべき処理ではないので、内部で処理して欲しい。

TwitchChatPlugin 1.0 for Yukarinette

TwitchChatPlugin 1.0 on GitHub

TwitchChatPlugin は、ゆかりねっとで音声認識した文章をTwitchの自身のチャンネルに発言することが出来ます。使用にはチャット用OAuthの取得が必要ですが、詳細はREADMEを読んで下さい。

streamingラグを考慮した遅延送信機能と、スパム抑制機能を追加しています。遅延時間は自身の配信設定やTwtichサーバーとのラグを考慮しながらいい感じに調整してみてください。

TwitchChatPlugin 0.1 for Yukarinette

ゆかりねっとプラグインを作りました。

TwitchChatPlugin 0.1 on GitHub

TwitchChatPlugin は、ゆかりねっとで音声認識した文章をTwitchの自身のチャンネルに発言することが出来ます。使用にはチャット用OAuthの取得が必要ですが、詳細はREADMEを読んで下さい。

ニコニコ動画やYoutube Liveでは既に同種のプラグインはあるのに、私が主に利用しているTwitch向けには無かったというので、やってみようかなと思ったら割と楽に出来たやつです。一番大変だったのはWPFでのウインドウ作りだったかもしれない。

とはいえそれよりも面倒臭そうな、streamingラグを考慮した遅延送信とスパムコントロールは後回しです。非同期処理が避けて通れませんが、使用しているTwtichLibに非同期メソッドが容易されていなさそうなのと、適当にスレッドプールに投げて終わりというわけには行かないだろうなという状況があるので。
そういうわけでバージョンは0.1のpre-releaseとなっています。

もう一つ、汚い言葉を使用したときのフィルタリングも機能も本来は必要で、これが無いとTwitch側の機械判断 (AutoMod) に引っかかってペナルティをもらう可能性があります。そういう言葉を使わない善良な人でも音声認識の都合、誤認識で思わぬワードが出ることもあるので注意して下さい。
これはGoogleの音声認識部分である程度はやってくれるのと、Twitch側の処理は日本語に対してはまだそう厳しくないだろう(未対応の可能性すらある)というのと、ゆかりねっとアプリケーション側でやったほうがいい機能だろうかつ、やってくれそうという判断から優先度は低いです。

SubtitlePlugin 1.0 for Yukarinette

作ったと言うほどのものではありませんが、ゆかりねっとプラグインを作りました。

SubtitlePlugin 1.0 on GitHub

SubtitlePluginは、ゆかりねっとの詳細字幕ウインドウを、読み上げ機能無しに使用するプラグインです。なんと5行書いただけの何もしない、いわゆるnullデバイス的なやつです。

ゆかりねっとでは、字幕表示をする場合、簡易字幕と詳細字幕がありますが、簡易字幕による字幕表示のウインドウはChromeのそれであり、キャプチャツールでこれを取り込むにはかえって手間がかかります。簡易字幕はウインドウ名が親ウインドウと被っているので、OBSの場合では音声認識を開始する度にキャプチャ先を設定し直さなくてはなりません。
そこで、通常のWindowsのウインドウである詳細字幕を用いたいところですが、私の見落としでなければ、この詳細字幕ウインドウを利用するには読み上げ機能のプラグインを必ず一つは選択する必要があり、しかもその機能から呼び出される読み上げアプリケーションなりが使用可能な状態でなければ、そのプラグインは無効化されます。つまり、読み上げ機能無しに字幕機能だけを使うことが出来ないのです。

読み上げ機能無しにゆかりねっとを用いるなんて意味無いじゃないかと思われるかも知れませんが、個人的には読み上げ機能は無くていいかなという感じでしたし、字幕のためにわざわざ適当な、特にフリーの読み上げアプリケーションを有効にして、気の抜けるような読み上げ音声を聞かなくてはならないというのはナンセンスなのです。
また、googleの音声認識APIを呼び出して字幕として出力するアプリケーションをフルスクラッチで作るよりも、既に組み込まれているゆかりねっとのプラグインとして作った方が楽だろうという判断です。結果的に開発者に要望を出す手間よりも楽でした。

Getting Started with GitHub

GitHub をはじめました。手始めに過去のツールのソースコードなんかを順次置いていっています。

過去に何度かツールのソースコードを公開してくれないかという要望が、特に海外ユーザからあったものの、”諸事情”によって敢えてしないという状態でした。最近では時間が経過したことや、BioWare Social Networkが停止したこともあり、構わないだろうという判断によって公開に至りました。

余談ですが、Dragon Age Conversation Readerは、当時韓国で非公式なローカライズツールとして利用されていたようで、その際にデコンパイルして韓国語対応するわ、敢えてつけなかった編集機能もつくわ、バージョン管理機能もつくわと魔改造をされていたのを、ソースコードを整理している最中に思い出しました。一から作った方が早いんじゃないかな。