N煎ログブログ

n番煎じと言っても過言ではない今更な、でも個人的に躓いたUnityやUE4等での開発についての云々を書いていきます

【UE4】FrameGrabberを用いたキャプチャの実装方法を読み解いてみる

目次

はじめに

この記事は、Unreal Engine 4 (UE4) その2 Advent Calendar 2018の9日目の記事になります。

qiita.com

8日目の記事はfumittuさんの

qiita.com

でした。

この記事について

2018/6/27に、おかず(Twitter: @pafuhana1213)さんが以下のような記事を投稿されました。

pafuhana1213.hatenablog.com

ざっくりまとめると、

今までUE4で画面をキャプチャする際には、SceneCapture2DやSceneCaptureComponent2Dという割と重い処理を実行しなければならなかったのを、FrameGrabberというものを使えば軽負荷で実装できる。

というものです。

しかし、FrameGrabberを用いた実装を行うにはC++でコーディングする必要があり、上記記事でもそのことが述べられています。しかし、コード自体の解説は割愛されていたので、私自身の勉強もかねて、git公開されているコード見ていきながらどんなアルゴリズムなのかを探り探り紐解いていきたいと思います。その為、所々間違った解釈等が発生する可能性がありますが、その際にはコメントなどで指摘いただければ修正致します。

記事内容の割愛要素

  • C++ファイルの作成方法
  • 「BeginStart」や「Tick」等、FrameGrabber関連以外の関数解説
  • コードの初期化処理部分

そもそも何をしてるの?

そもそも、なぜFrameGrabberは他のキャプチャ処理に比べて高速なのでしょうか。それは、FrameGrabberのキャプチャの考え方に秘密があります。

「再レンダリング」ではなく「既レンダリング」のフレームを取っている

「FrameGrabber」という名前から察する方もおられるかと思いますが、「SceneCapture2D」や「SceneCaptureComponent2D」(以降「既存キャプチャ」と呼称)は画面をレンダリングしてキャプチャするのに対して、「FrameGrabber」は既にレンダリングされた過去のフレーム(Frame)を取得(Grab)する。というものです。

例えるならば、既存キャプチャはページを毎度印刷して紙を取得していますが、「FrameGrabber」は既に過去に印刷した紙の束のどっかから抜き取って取得している感じです。再レンダリング(印刷)の必要がないので早いのです。

https://1.bp.blogspot.com/-Cfb0kecZndM/Uyk_NP3oESI/AAAAAAAAeNs/sFxyJrF-Qks/s400/printer_woman.png

再度印刷するよりも...

 

https://1.bp.blogspot.com/-ZqQQsOyX8Xc/VD3SYol2_aI/AAAAAAAAoTM/DFXLZg14l7E/s400/school_print_kubaru.png

既に印刷されている物を取り出す方が早いですよね。(出典:いらすとや)

 

FrameGrabberの中身を深く辿ってみる

コード自体を貼るととんでもない行数になる為、public関数だけまとめます。

関数(publicなもののみ) 概要
void StartCapturingFrames() フレームキャプチャを開始させる
void CaptureThisFrame(FFramePayloadPtr Payload) スレート(計画/予定の意だそうで)からイベントを受信した時に、フレームをキャプチャする
【引数】
・Payload(型:FFramePayloadPtr)
 - フレームデータ情報のポインタ
void StopCapturingFrames() フレームキャプチャを停止させる
void Shutdown() FrameGrabberをシャットダウンし、
スレッド操作が完了したことを確認する
TArray<FCapturedFrameData> GetCapturedFrames() キャプチャしたフレーム群を返す

 FFramePayloadPtr

FFramePayloadPtrについては私もまだまだ分からない事が多いので確信をもって言えませんが、「FFramePayloadPtr」は「TSharedPtr」というシェアードポインタをtypedefして付けられたものです。具体的には以下のように定義されています。

typedef TSharedPtr<IFramePayload, ESPMode::ThreadSafe> FFramePayloadPtr

 シェアードポインタについては公式リファレンスがあります。が、「非侵入型、参照カウントのスマートポインタの特別なタイプ」とは一体...うごごご。
api.unrealengine.com

ついでに、FFramePayloadPtrについてもリファレンスがあります。

FFramePayloadPtr | Unreal Engine

MovieSceneCapture

FrameGrabberのヘッダーファイルを見ると「MOVIESCENECAPTURE_API」というマクロ記述がありますので、「MovieSceneCapture」というモジュールであると予想できます。

そこで、もっと掘り下げて「MovieSceneCapture」を見てみると、どうやらここで色々なキャプチャ関連の仕組みを持っている模様?

MovieSceneCapture | Unreal Engine

つまり「FrameGrabber」は、元々(というより元から?)「MovieSceneCapture」の持つ機能の一つだということなのかもしれません。

実際にサンプルコードを見てみる

では実際に、FrameGrabberのサンプルコードを見ていきます。

(Gistを見ると関数名の後に「.cpp」が付いてますが、こうしないとc++向けの見た目にならないためですのでご承知ください)

StartFrameGrab関数

以下の要素のセッティングを行い、FrameGrabberのキャプチャ開始を行う。

  • ビューポートの取得
  • テクスチャの作成
  • マテリアルへのテクスチャ割り当て

gist.github.com

コメント「Get SceneViewport」下の処理に関してはコメントにも書かれていますが、「FRemoteSessionHost::OnCreateChannels()」内の処理を参考にされています(一部不要な処理は省かれています)。 

StopFrameGrab関数

FrameGrabberの実行の停止を行う。

実際にはReleaseFrameGrabber関数が担っている。

gist.github.com

ReleaseFrameGrabber関数

FrameGrabberによるフレーム取得処理の停止とスレッドの終了を行う。

gist.github.com

Capture関数

実際にキャプチャしたフレーム群の中から最後の物を取得し、その色情報をテクスチャに割り当てる。これはTick内で都度行う。

UE4では色情報は(R, G, B, A)ではなく、(B, G, R, A)という並びで、R・G・Bが逆並びであることに注意しなければならないようです。

gist.github.com

FrameGrabber->CaptureThisFrame(FFramePayloadPtr()); 

 ここで今のフレーム情報を確保している一覧に追加し、

FrameGrabber->GetCapturedFrames() 

 ここで今までにため込んだキャプチャ一覧を取得しているようです。

つまり、毎フレーム「CaptureThisFrame関数」でそのフレーム情報を確保するよう処理を行い、「GetCapturedFrames関数」で今までにため込んできたフレーム情報を取得するという事なのだと思われます。

流れをまとめてみる

こんな感じでしょうか(フローチャートではありません)。

f:id:isemito:20181209190733p:plain

まとめ

FrameGrabberの機能とその仕組みについて掘り下げてみてみました。

まだまだ完全に理解出来ているわけではないですが(シェアードポインタとかスレッドセーフとかFFramePayloadPtrとか)、これをもといいつか何か作りたいですね。

本当はこの記事内でFrameGrabberを用いた何かを作ろうと考えていたのですが、まだ思いついてません...。

探り探りの記事作成でしたので、色々と遠回りしたり脱線したりした箇所が多々見受けられるかと思います。それでも最後まで読んでくださった方、ありがとうございます。

最後に、FrameGrabberの存在について記事投稿されたおかず(@pafuhana1213)さんにはこの場をお借りして感謝致します。