GStreamerで映像のストリーミングをしながら静止画を保存する

# はじめに
仕事でGStreamerを使うことがあったんですが、私は全然ド素人。

ストリーミングする部分は他の人が書いたものをそのままコピペして使ったわけですが、ストリーミングと同時にストリーミングしてる映像を切り出して静止画として使いたい処理があり、GStreamerに入れる前に静止画にすることも考えたんですけど、GStreamerの処理としてできるなら楽かなぁ、と思って調べてみました。

# 色々試してみたこと
## まずは基本の形
`gst-launch-1.0` を使って `/dev/video0` から映像を取得して、`fakesink` に入れて捨てるサンプル。
```shell
$ gst-launch-1.0 -e v4l2src device=/dev/video0 ! video/x-raw, width=1920, height=1080, framerate=60/1 ! queue ! videoconvert ! fakesink
```

## tee でパイプラインをふたつに分けてみる
取得した映像を `tee` でふたつにわけで、ふたつとも `fakesink` に入れて捨てるサンプル（サンプルでもひどい気がする）
```shell
$ gst-launch-1.0 -e v4l2src device=/dev/video0 ! video/x-raw, width=1920, height=1080, framerate=60/1 ! tee name=t \
    t. ! queue ! videoconvert ! fakesink \
    t. ! queue ! videoconvert ! fakesink
```
これで、上の `fakesink` は普通にストリーミングに使用する想定でこのままにしておいて、下の `fakesink` を静止画保存用に使っていきます。

## multifilesink で保存
色々調べてみて、「なるほどね。こうすればいいんだろ（よく分からない）」で、こうなった。
```shell
$ gst-launch-1.0 -e v4l2src device=/dev/video0 ! video/x-raw, width=1920, height=1080, framerate=60/1 ! tee name=t \
    t. ! queue ! videoconvert ! fakesink \
    t. ! queue ! videoconvert ! videoscale ! video/x-raw,width=720,height=480 ! jpegenc ! multifilesink location=capture_%05d.jpg max-files=10 next-file=max-duration max-file-duration=300000000000
```
`videoscale` でリサイズして、`jpegenc` でJPEGファイルにして、`multifilesink` で保存する。
`location=capture_%05d.jpg` でファイル名指定して、`max-files=10` で保存されるファイルの最大数指定。`next-file=max-duration max-file-duration=300000000000` の指定で、300秒毎にファイル保存。
ちなみに、`capture_%05d.jpg` でファイルが勝手に連番で保存されていくけど、`max-files` でファイル数を指定している場合、`capture_00010.jpg` が保存されると `capture_00000.jpg` が消える。`capture_00000.jpg` が新たに保存されるみたいに番号が戻るのかと思ったけど、そうはならないらしい。

## 一見できてるように見えたけど
上のスクリプトでうまくいってるように見えるんですよ。
「これでできたな GStreamer チョロいぜ」とか思ったんですけど、このスクリプト、非常に重い。最初に `framerate=60` を指定してるのに、上の `fakesink` でフレームレートを確認すると、18フレーム/秒くらい。これは使えない。
たぶん、この指定方法だと、全フレームの画像サイズ変換して、JPEG変換して、そのうち5分に1度静止画保存してるんでしょうね。それは重いか。。

というわけで改良版。
```shell
$ gst-launch-1.0 -e v4l2src device=/dev/video0 ! video/x-raw, width=1920, height=1080, framerate=60/1 ! tee name=t \
    t. ! queue ! videoconvert ! fakesink \
    t. ! queue ! videoconvert ! videorate ! video/x-raw, framerate=1/300 ! videoscale ! video/x-raw, width=720, height=480 ! jpegenc ! multifilesink location=capture_%05d.jpg max-files=10
```
`videorate` でフレームレートを 1/300 に下げてから、`videoscale` 以降の処理をするように修正。`multifilesink` に来るのはフレームレート 1/300 で 5分に1回だから、`next-file=max-duration max-file-duration=300000000000` の指定は不要になった。

## もうちょっと短く書ける
`videorate` と `videoscale` はまとめてこんな感じで書いてもいいらしい。
```shell
$ gst-launch-1.0 -e v4l2src device=/dev/video0 ! video/x-raw, width=1920, height=1080, framerate=60/1 ! tee name=t \
    t. ! queue ! videoconvert ! fakesink \
    t. ! queue ! videoconvert ! videorate ! videoscale ! video/x-raw, width=720, height=480, framerate=1/300 ! jpegenc ! multifilesink location=capture_%05d.jpg max-files=10
```

## ただし、これだとダメ
上の `videorate` と `videoscale` の順序を入れ替えただけだけど、これをやるとフレームレートが 20フレーム/秒くらいまで下がった。
このような順序にしてしまうと、全フレームに対してサイズ変更だけはやってしまうということかな。
```shell
$ gst-launch-1.0 -e v4l2src device=/dev/video0 ! video/x-raw, width=1920, height=1080, framerate=60/1 ! tee name=t \
    t. ! queue ! videoconvert ! fakesink \
    t. ! queue ! videoconvert ! videoscale ! videorate ! video/x-raw, width=720, height=480, framerate=1/300 ! jpegenc ! multifilesink location=capture_%05d.jpg max-files=10
```

# まとめ
簡単にできるかなと思った、ストリーミングしながら静止画保存ですが、なかなか手強かったのでメモ的に試してみたスクリプトとその結果を書いてみました。

マシンパワーが強くて全フレームに対してガリガリ処理を入れても問題ない処理速度であれば色々考えなくていいのかもしれませんが、今回は非力なマシンでやる必要があったので色々試してみました。

ちょっと特殊な用途かもしれませんが、こんなこともできたよって感じで読んでいただけたら幸いです。