AYU MAX

プログラミングとか作ったもの

UE4でウィンドウキャプチャー機能を作ってみた

WindowCapture2D

今回UnrealEngineで使えるウィンドウキャプチャープラグインをGitHub上に公開しました。

github.com

今はGitHubでのみ公開していますが、もう少し機能がまとまり次第マーケットプレイスにも出そうと思っています。

WindowCaptureへの思い

ある時にVRコンテンツをUE4でWindows上のアプリケーションをVR空間に表示したくて色々調べてたのですが、中々ダイレクトでマッチする機能は探せず。

当時はそんなに時間もなかったのでUE4で実現するのをあきらめていました。

そんな中ネットを見ているとUnityならドンピシャの機能を作られている方がいて、試してみると普通にやりたい事ができてしまい感動した記憶があります。

tips.hecomi.com

凄い凄いと思いながら、Unityでも実現できてるんだからUnrealでも作れるんじゃないだろうかと思い、トライしてみたのが始まりです。

Windowをキャプチャーする

私は25歳の時に今で言う「未経験からエンジニア」で転職したのですが、その時最初にやった業務がWindows上でのウィンドウキャプチャーでした。

C++, MFCで戦っていたのですが、当時はHDC? HWND?何それ?メモリDCってなに?って感じだったのを覚えています。

そのためウィンドウをキャプチャーするという技術には特別な思いがあります。

キャプチャー方法

私の知っているウィンドウキャプチャー方法は以下の2つです。

  • WindowのDCを取得してBitBlt
  • WindowのDCを取得してPrintWindow

速度的にはBitBltの方がPrintWindowよりも早くて良いのですが、ChromeやUE4で作ったアプリケーションなどはBitBltではキャプチャーできません。

Windowを直接キャプチャーするのではなく、デスクトップのDCからキャプチャーして必要な領域をトリミングすると、どんなウィンドウでもキャプチャーできるのですが、 キャプチャーするのにディスプレイモニターのリフレッシュレート分待たされてしまう問題や、重なったウィンドウの背面側はキャプチャーできない等問題も数多くありました。

もう一つのPrintWindowも当初はChromeのキャプチャーはできないと思っていたのですが、Microsoftのヘルプに載っていない引数(PW_RENDERFULLCONTENT=2)を与えるとキャプチャーできるようになる発見がありました。

そこで今回はPrintWindowで作ってみる事にしました。

(その他にDirectXを使ってウィンドウキャプチャーする方法もあるそうですが、これはまだ試していません)

UE4での実装

とりあえずTickの中でターゲットのウィンドウをキャプチャーしてテクスチャの中身を書き換えてみました。

テクスチャー(UTexture2D)の書き換えは以下のコードで実現できます。

auto Region = new FUpdateTextureRegion2D(0, 0, 0, 0, TextureTarget->GetSizeX(), TextureTarget->GetSizeY());
TextureTarget->UpdateTextureRegions(0, 1, Region, 4 * TextureTarget->GetSizeX(), 4, (uint8*)m_BitmapBuffer);

TextureTargetがターゲットとなるTexture2Dで、 m_BitmapBufferはBGRA形式のピクセルバッファです。

この2行で動的にテクスチャーの書き換えができます。

実行するとうまくいったかのように見えたのですが、対象のウィンドウのサイズが大きくなるとカクツキが酷くなってしまい困りました。

そこで次はキャプチャーの部分のみTaskを使ってバックグラウンドで処理してみました。

void ACaptureMachine::Tick()
{
    AsyncTask(ENamedThreads::BackgroundThreadPriority, [this]() {
        // キャプチャー処理を実施
        });
}

そうすると先ほどよりもフレームレートが上がり効果がありましたので、次に複数ウィンドウのキャプチャーにトライしました。

3枚のウィンドウを同時にキャプチャーしたところ、またまた極端にフレームレートが落ちてしまい困ります。

考えてみると、このウィンドウキャプチャーは1個のActorで1つのウィンドウをキャプチャーしており、Tick内で処理している以上直列に処理が繋がってしまいどんどん遅くなっているのではと仮説をたてました。

なのでTick内で処理するのはやめて、Actor内でスレッドを生成しスレッド内でキャプチャーをするように改造しました。

すると。。。

ちょいミスもありましたが、3枚のウィンドウをキャプチャーしても60fpsキープできるようになりました。

もっとウィンドウの枚数を増やしたり、4Kウィンドウフルサイズなどウィンドウを大きくしていくと負荷は高まっていくとは思いますが、とりあえずは最低限の機能は実装できたかなと思っています。

使い方

使い方は以下の通りです。(今後は仕様変更により使い方が変わる可能性はあります)

  1. 「WindowCapturePlane」アクターをレベルに置く。 image

  2. 置いたアクターのプロパティを設定する。

image

プロパティ 説明
Capture Target Title キャプチャー対象のウィンドウタイトル文字列を入力
Title Matching Window Search Capture Target Titleに入れた文字列とウィンドウタイトルのマッチング方法を選択
Frame Rate キャプチャーレート (fps)
Check Window Size 有効にするとウィンドウのサイズ変更に追従しますが、処理負荷が高くなります
Cut Shadow ウィンドウの影を除いてキャプチャーします

Title Matching Window Search

説明
PerfectMatch 入力文字列との完全一致 
ForwardMatch 前方一致で比較
PartialMatch 部分一致で比較
BackwardMatch 後方一致で比較
RegularExpression 正規表現を使って比較

以上でPlayすると設定がうまくいけばウィンドウがキャプチャーされます。

ただし以下の点にはお気を付けください。

※1 現状ウィンドウの探索は起動直後に1回のみのため、対象のウィンドウが表示されている状態でUE4側をPlayする必要があります

※2 対象のウィンドウを最小化してしまうとキャプチャーできなくなります

今後の展開

現状ウィンドウをキャプチャーすることはできるようになりましたが、表示のみで操作ができません。

例えばVRで使うことを考えたときに、空間内でウィンドウを操作できた方が使い道が広がると思うので、是非ウィンドウの操作(マウスやキーボード入力の転送)も実装してみたいと思います。

あとはネットワーク共有とかもやってみたいけど、これは難しそうなので断念するかもです。