Open12

UnityからNotchLCを"直接"書き出したい

HidanoHidano

要件としては、Timelineを用いた映像コンテンツのレンダリングをする際に、ProResなどを経由せず最初から直接NotchLCで書き出したいというもの。

とりあえずググるとAVProのページがヒットする。
https://renderheads.com/products/avpro-video/
これは確か読み込み専用の製品だったはず。

映像書き出しにはAVProMovieCaptureという製品があるが、こちらはコーデック一覧を見るにNotchLCは対応してなさそう。読み込みはできるのに何故…
https://www.renderheads.com/content/docs/AVProMovieCapture/articles/codecs.html

まあできれば使い慣れたUnityRecorderで出力したいので、別の方法を模索する。

HidanoHidano

Unityユーザーの大半がお世話になっているであろうkeijiro先生のリポジトリに、FFmpegRecorderというものがある。
https://github.com/keijiro/FFmpegRecorder
懐かしい。UnityRecorderがProResに対応する前はこれでレンダリングをしたのを思い出す。


Hapに対応している点は現行のUnityRecorderよりも優れていると言える。Unityで最終出力を行う現場では今も現役なのかもしれない

こちらの更新は4年前で止まっており、NotchLCには対応していない。
しかし中でffmpegが動いているのは明白なのだから、少しの改造でNotchLCにも対応できるのではないだろうか。

HidanoHidano

まずffmpegでNotchLCに変換するコマンドを調査する。
FFmpegRecorderのプロジェクトに入っていたサンプルシーンをProRes422でレンダリングして、検証用素材とした。

これをMediaEncoderでNotchLCに変換し、ffprobeでコーデック情報などを出力させる。

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test_1.mov':
  Metadata:
    major_brand     : qt
    minor_version   : 512
    compatible_brands: qt
    encoder         : Lavf58.12.100
  Duration: 00:00:05.00, start: 0.000000, bitrate: 206248 kb/s
  Stream #0:0[0x1](eng): Video: notchlc (nclc / 0x636C636E), yuva444p12le(pc, gbr/bt709/iec61966-2-1), 1200x696, 204786 kb/s, 60 fps, 60 tbr, 15360 tbn (default)
      Metadata:
        handler_name    : VideoHandler
        vendor_id       : FFMP
        encoder         : NotchLC
  Stream #0:1[0x2](eng): Audio: pcm_s16le (sowt / 0x74776F73), 44100 Hz, stereo, s16, 1411 kb/s (default)
      Metadata:
        handler_name    : SoundHandler
        vendor_id       : [0][0][0][0]

pix_fmtの値は「yuva444p12le」ということになるのだろうか。

HidanoHidano

見様見真似でFFmpegPresetを編集し、UnityRecorderから出力させようとしたところ、以下の警告が出た。

FFmpeg returned with warning/error messages. See the following lines for details:
Unknown encoder 'notchlc'

NotchLCのエンコーダーが見つからないとのこと。表記方法が違うのだろうか。

HidanoHidano

調べていくに従って、不都合な真実が明るみに出てきた。
なんとNotchLCはencoderがクローズドになっており、利用には高額なライセンス料が必要との情報が、本家のスレッドにあるではないか。
https://forum.notch.one/t/command-line-encode-utility/851/16

つまりffmpegでデコードして別の形式に変換はできるが、NotchLCへのエンコードは出来ないというわけだ。
スレッド内でも非難囂々だが、それがNotchのビジネス判断であるなら最早何も言うまい。
しかし、次に自分でフォーマットを選べる案件が訪れた時、Hapを差し置いてNotchLCを選ぶことは無いだろう。

HidanoHidano

とは言えNotchLCを使わざるを得ない機会は訪れるので、別の方法も模索しよう。

公式ページのサポート一覧を見ると、TouchDesignerに対応していることが伺える。
UnityからSpoutなどでデータを送り、TouchDesignerから書き出すことは出来ないだろうか。

実はこのやり方は少し前から興味があって、TDとUnityを密に連動させられれば、VJやエフェクトをより高度に使用できるのではないかと考えていた。

HidanoHidano

取り急ぎ、KlakSpoutでTouchDesignerに映像を転送し、TouchDesignerからNotchLCで出力できることは判明した。

ただ当然ながら同期は取れていないので、ここをどうにかする必要がある。

HidanoHidano

少々手こずりながらも、それらしい挙動をするプロトタイプが用意できた。

KlakSpoutは出来れば手を付けたくなかったが、SpoutSenderがpartialクラスになっているなど流用しにくい点があったため、プロトタイプ段階ではローカルPackageにして直接編集することにした。

TouchDesigner側ではMovie File Out TOPのStop-Frame Movie機能を使うことが肝となった。
これを使うことで、OSCが届く度に1フレームずつ動画に描き出していく仕組みがすぐに完成した。
もちろんNotchLCもネイティブ対応しており、その他ProRes4444やHapHDRなる規格にもドロップダウン一つで切り替えられる。あまりにも強力だ。Unityも見習ってほしい。

一方Unity側の制御はまだ課題が多く、特にフレームレートを上げるに従って同期がズレていくのが映像制作においてはクリティカルだ。
横着せずに、UnityRecorderよろしくTimeline拡張のスクリプトで処理した方が実用的にも賢明なのだろうと考えた。

HidanoHidano

それから2週間、暇さえあればこれに取り組んでいたが、Timeline拡張でGameビューと同期を取るのは沼であった。
TouchDesigner内でデバッグ用のボタンを作り1フレームずつ実行できるようにしたが、数秒間隔を空けてフレームを送る場合と、素早く連続で実行する場合で描画がスキップされたりされなかったりする。
この時は正攻法としてPlayableBehaviourを継承したTimeline拡張スクリプトを作っていたが、UnityRecorderのコードを読むとMixerBehaviour(RecorderPlayableBehaviour.cs)ではフラグの管理くらいしか行っておらず、本命の書き出し操作は別で行っているようだった。

ならばそれに便乗させて頂こうということで、UnityRecorderで連番画像の書き出しができるImageRecorderのコードをコピーし、BeginRecording(),RecordFrame(),EndRecording()の関数にTouchDesignerと通信する処理を差し込んでみた。

結果は上手くいったように見える。
高フレームレート時に描画がスキップされる問題は、TouchDesigner側で一瞬待つことによって、ゆっくり進めた時と同じように逐次実行されるようになった。
https://drive.google.com/file/d/1HCyCxRYnO7Y44fjoDvUS0A93k-bmGcyh/view?usp=sharing
動画内のタイムコードはPremiereProでHapに書き出したものをKlakHapで再生している。中央左のフレーム表示はUnityのTimeクラスで取得した経過時間(秒)にフレームレートの60を掛けたもので、少なくともUnity内ではズレていないことを保証するものである。

中央右のCubeは飾りだが、何となくGameObjectを何も描画していないと何かおかしくなりそうな予感がしたので念のため添えておいた。
こういった明確な根拠がない宗教じみた行為も、Unityでの映像制作においては有効に働く場合がある(もちろん働かない場合もある)。

HidanoHidano

UnityRecorderが提供する機能を利用するにあたって、internalな関数やクラスにアクセスする必要があった。
UnityRecorderをローカルパッケージにしてスクリプトを追加してしまえば簡単に解決するが、常に改変したUnityRecorderと一緒に管理しないといけない上、本家UnityRecorderがアップデートされた際に反映するのに大変手間がかかってしまう。

これについては、こちらの記事で紹介されている【方法2】のInternalsVisibleToAttributeを用いることで解決できる。
テストコード用と思われるUnity.Recorder.Testsという名前のアセンブリからのアクセスが許可されているため、自作のコードにAssemblyDefinitionを設定し、NameをUnity.Recorder.Testsに設定することでアクセスできるようになる。

こうすることで、UnityRecorder本体には一切手を付けずに書き出しフォーマットの追加が可能になる。
コードを記述する際の必要事項などはこちらの記事が大変参考になったので紹介させていただきたい。