GStreamerでビデオを連続ファイルにエンコード・記録する

dev-mokochanです。multifilesrcでデバイスからの入力を連続ファイルへ記録できる...かと思えばさにあらず。日本語記事も少ないようで、知る限りを書きました。

## 環境
筆者はJetsonの複数モデルで開発をしています。GPUでのNVIDIA SDK使用環境も共通項があるかもしれません。

## Jetson JetPack　/　L4T-ML
投稿の時点での[Jetpack最新バージョンは 4.6](https://developer.nvidia.com/jetpack-sdk-46
)です。

これまでの10W,15Wに加えて20WでJetsonを駆動するnvpmodelが追加されました。enc/decの能率もあがり、フレンドリーであるとは公式も言及しています。

また、L4Tコンテナでやろうとする方向けの注意としては（１）ホストのJetpackバージョンとコンテナイメージバージョンを合わせる（２）ミニマム構成のL4T-BASEもありますが、機械学習 machine learning向けにCUDAやPyTorch、cuDnnも含まれるコンテナL4T-MLの方が後々自分の目的に適しているかどうか検討することです。
（１）に関して、Jetpack 4.5.0と 4.5.1および 4.5.2の違いはBSPやバグフィックスだそうで、L4Tのイメージは r32.5.0となります。Dockerfile内をr32.5.1やr32.5.2と書き換えてもイメージがないとしてコケるのでご注意。

## GStreamer ver1.14.5

```
dev-mokochan@jacky-01:~/Documents$ gst-inspect-1.0 --version
gst-inspect-1.0 version 1.14.5
GStreamer 1.14.5
https://launchpad.net/distros/ubuntu/+source/gstreamer1.0
```

Jetson内の gstreamerはversion1.14.5となります。[NVidiaの動作検証もこのバージョンで行われています](https://forums.developer.nvidia.com/t/unintended-contrast-spread-when-using-jetson-gstreamer-and-h265-to-encode-videos/179488/17)。

＞On Xavier, it is with gstreamer 1.14.5 and may not work with later version(s). Each release is tested/verified with gstreamer 1.14.5. If you manually upgrade to 1.18, it may not work properly and the system can be unstable. 


## multifilesinkではなくsplitmuxsink
<details>
<summary>やっと本論です。曰く [multifilesinkだとフレームロスが生じた](https://forums.developer.nvidia.com/t/saving-camera-stream-to-multiple-files-using-gstreamer-without-frameloss/55323/4)、とのこと。splitmuxsinkの登場です。(トピックがこのあとどうなったかは折り畳みを展開ください)
</summary>
このトピック自体は出来たか出来なかったか分からないままcloseしています。
投稿者がomxh265encではうまくいかなかったとするのに対して、NVIDIA担当からはsplitmuxsinkで指定するmuxerを選ぶ必要があると回答。
横から失礼してくれた最後の投稿者のqtmux使用のアイディアには無回答です。個人的には qtmux, qtdemuxで行けてほしいところです。。。
keyframeや分割と関連して、他の記事にはエンコードオプションにiframeintervalを設定せよとの記述もありました。お試し下さい。
</details>

## splitmuxsink
Jetson(＋NVIDIA proprietry gstreamer 1.14.5)での話です。

以下はhttps://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good/html/gst-plugins-good-plugins-splitmuxsink.html をもとにしています。-eフラグはEOS発行のフラグです。C系言語やpythonでやろうとするときは不要というか、別の方法でEOS発行することとなります。移植時にはご注意ください

### パイプライン例

v4l2デバイスからのビデオ入力をmuxしてISO準拠mp4ファイルに記録します。時刻は ns=nanosec単位とのことで、0の数にはどうぞご注意ください。timeoverlayはタイムスタンプを焼きこみます。開発中に便利なエレメントです。

(原文)Records a video stream captured from a v4l2 device and muxes it into ISO mp4 files, splitting as needed to limit size/duration to 10 seconds and 1MB maximum size.

```
gst-launch-1.0 -e v4l2src num-buffers=500 ! \
    video/x-raw,width=320,height=240 ! videoconvert ! \
    queue ! timeoverlay ! x264enc key-int-max=10 !    \
    h264parse ! \
    splitmuxsink location=video%02d.mov  \
    max-size-time=10000000000 max-size-bytes=1000000
```
### プロパティの例
各エレメントのオプション値をプロパティと呼びます。splitmuxsinkエレメントでは、区切りを決めるmax-size-bytes, max-size-time　が汎用性の高いプロパティといえるでしょう。

#### max-size-bytes
規定ファイルサイズを超えないようにファイルを分割する。0で無効（つまりmax-size-timeだけ使いたいときは0とする）。

#### max-size-time
完成ストリーム長が規定時間を超えないようにファイルを分割するプロパティです。0で無効。

#### location
<details><summary>multifilesinkと同じ、locationでファイル名を指定します。</summary>フォーマット形式 %02dなどで連番指定を行います。

The “location” property
```  “location”                 gchar * ```
Format string pattern for the location of the files to write (e.g. video%05d.mp4).

Flags: Read / Write

Default value: NULL
</details>

#### muxerと対応コーデック
<details>
<summary>デフォルトのmuxerはmp4muxです。</summary>自明に指示しなければNULLとなり、デフォルトのmp4muxが使用されます。
ポイントは、muxerが現在利用中のコーデックに対応しているかどうかです。また、パイプラインのparserも適切に選ぶ必要があります。async-finalize=falseでしか有効にならない、というのはtrueにしても何も起きないか、はじかれるということでしょうか。
(追記) async-finalizeというエレメントプロパティは存在せず、async-handlingというプロパティだけがありました

---
The “muxer” property
```“muxer”                    GstElement *```
The muxer element to use (NULL = default mp4mux). Valid only for async-finalize = FALSE.

Flags: Read / Write

---

</details>


#### muxer-factory

async-finalize = TRUEで有効になる、muxer指定子。

...ということはつまりasync-finalizeで muxerプロパティと指定方法が切り替わるのですね

---

The “muxer-factory” property
```  “muxer-factory”            gchar *   ```
The muxer element factory to use (default = mp4mux). Valid only for async-finalize = TRUE.

Flags: Read / Write

Default value: "mp4mux"

####  muxer-properties

muxer factoryで指定したmuxのオプション指定を行う。propertiesとあるとおり、複数指定が可能です。

The “muxer-properties” property
```  “muxer-properties”         GstStructure *  ```
The muxer element properties to use. Example: {properties,boolean-prop=true,string-prop="hi"}. Valid only for async-finalize = TRUE.

Flags: Read / Write
<details>


### シグナルの例
例えばsplit-afterというシグナル(v1.16以降)は、現在進行中のGOPぶん以降は、別の新しいファイルに書き込みしてくれるとのこと。v1.14.5のJetsonでは使えません。

#### split-after

---
The “split-after” signal
```
void
user_function (GstSplitMuxSink *splitmux,
               gpointer         user_data)
```
When called by the user, this action signal splits the video file (and begins a new one) immediately. The current GOP will be output to the old file.

---

### format-location-full
リファレンスにpythonコールバックとして記載がありましたが、リファレンスの説明文は書きかけでいまいち判然としません。locationでのファイル名パターン指定では足りないときに使える、のでしょうか。ここまで手を突っ込むなら、パイプラインを起こす前に設定したいところですが...

（2021.1.2追記）本プロパティを使用してpythonでコールバックを得た方の投稿を元に、本家GStreamerのドキュメント集成がされたようです。Jetsonでも参考になるかも？試し次第記事化します。https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/issues/753

---
format-location-full 
Since : 1.12

def format_location_full_callback (splitmux, fragment_id, first_sample, udata):
    #python callback for the 'format-location-full' signal
Parameters:

splitmux – the
fragment_id – the sequence number of the file to be created
first_sample – A Gst.Sample containing the first buffer from the reference stream in the new file
udata – No description available
Returns (str) – the location to be used for the next output file. This must be a newly-allocated string which will be freed with GLib.free by the splitmuxsink element when it no longer needs it, so use GLib.strdup or g_strdup_printf (not introspectable) or similar functions to allocate it.
Flags: Run Last

---

## スクリプト例
### ソフトエンコーダで 10秒ごとに分割

```
gst-launch-1.0 -e v4l2src num-buffers=500 ! video/x-raw,width=320,height=240 ! videoconvert ! queue ! timeoverlay ! x264enc key-int-max=10 ! h264parse ! splitmuxsink location=video%02d.mov max-size-time=10000000000 max-size-bytes=1000000
```

## 次回
次の記事ではハードウェアエンコーダへのつなげ方を記載します。
それでは、happy encoding life!＼( 'ω')／