はじめに
前回の記事で NVIDIA のハードウェアエンコーダである NVENC(および NVDEC)を使いやすくした NvPipe を Unity で使えるようにしたアセットの紹介をしました。
しかし問題点として、NvPipe はバッファのポインタしか食わせる口がなく、Unity でテクスチャをエンコードしたい場合は一度 ReadPixels()
して GPU からデータをとってきて、それを再度 GPU に与えるという無駄な工程が必要でした。そこで、NvPipe ではなく NVENC を直接使ってエンコードすることで、GPU 上にあるテクスチャを直接利用してエンコードすることを試みてみました。
方針
NvEncoder のサンプルとして video-sdk-samples というサンプルが GitHub に上がっています。
ただこのリポジトリには、本来 Video Codec SDK のサイトから規約に同意しないとダウンロードできないファイルが含まれていたり、エンコード部分のコードのライセンスの判断が難しいなど、色々と問題があります。また、エンコードのパフォーマンスを最大限引き上げるためのマルチスレッド化の対応も配布されているコードでは(多分)そのままでは利用できません。ただし、GeForce のドライバをインストールすると自動で導入される nvEncodeAPI[64].dll を利用する nvEncodeAPI.h だけは MIT ライセンスで配布されているので、これのみに依存するコードで且つ非同期エンコーディング可能かつシンプルなものを書いてみることにしました。
ダウンロード
執筆現在ではまだ安定していないので Releases に .unitypackage は上げていないため、試したい方は直接 clone してきてください。
利用方法
Example
シーンを見てみますと、Render Camera
というゲームオブジェクトがあるのでそれを見てみます。
uNvEncoder コンポーネントをまず用意します。TextureEncoder
コンポーネントでインスペクタから指定された Texture
をエンコードする設定で uNvEncoder
を初期化し、毎フレーム、インスペクタで指定された Texture
(ここでは Camera
コンポーネントの Target Texture
にしていした RenderTexture
をエンコードします。エンコードが完了すると、バッファのポインタとサイズが onEncoded
イベントリスナにやってきます。実際にコードを見てみましょう。
まずはエンコーダのコードです。
... public class TextureEncoder : MonoBehaviour { public uNvEncoder encoder = null; public Texture texture = null; public int frameRate = 60; public bool forceIdrFrame = true; void OnEnable() { ... encoder.StartEncode(texture.width, texture.height, frameRate); StartCoroutine(EncodeLoop()); } void OnDisable() { StopAllCoroutines(); encoder.StopEncode(); } IEnumerator EncodeLoop() { for (;;) { yield return new WaitForEndOfFrame(); Encode(); } } void Encode() { ... encoder.Encode(texture, forceIdrFrame)); .... } }
StartEncode()
で開始、StopEncode()
で終了します。毎フレームレンダリング後(WaitForEndOfFrame()
のタイミング)で Encode()
を呼びます。 Encode()
には Texture2D
または System.IntPtr
(テクスチャのネイティブポインタ、実体は ID3D11Texture2D
)を渡すことができます。
では、次に受け取り部分です。
... public class OutputEncodedDataToFile : MonoBehaviour { ... FileStream fileStream_; BinaryWriter binaryWriter_; void Start() { fileStream_ = new FileStream(filePath, FileMode.Create, FileAccess.Write); binaryWriter_ = new BinaryWriter(fileStream_); } void OnApplicationQuit() { fileStream_.Close(); binaryWriter_.Close(); } public void OnEncoded(System.IntPtr ptr, int size) { var bytes = new byte[size]; Marshal.Copy(ptr, bytes, 0, size); binaryWriter_.Write(bytes); } }
OnEncoded()
でやってきたバッファをファイルに書き出しています。この OnEncoded()
はインスペクタ上で uNvEncoder
コンポーネントの onEncoded
に直接セットします。出力された H264 のファイルは VLC プレイヤーなどで再生することが出来ます。
改善点
現状まだエンコード結果がたまにガビガビになるバグがあるのでそこをなんとかしたいです。。また、一つのエンコーダしか作れないので複数の対応も行います。
おわりに
次回は uNvEnc(エンコード)と uNvPipe(デコード)を利用してカラーバッファとデプスバッファを転送するリモートレンダリングをしてみます。