FFmpegでストリームから画像に出力する

お久しぶりです。streampackのfadoです。花粉の季節到来に最近猛威を振るっている新型コロナウィルスに加えて、外出する気力が奪われていきますよね。オリンピックの年ですし早く収束してくれることを祈ります！さて、今回は`FFmpeg`というツールを使ってストリームを画像に変換する流れを紹介したいと思います。本記事で少しでもみなさんの参考になれたら嬉しいです！


#注意事項
- 検証はAWS環境で行ったものとなります。
- 検証環境のOSは`Amazon Linux`にしました。
- 入力ファイルは`rtsp`ストリーミングと想定しています。
- Wowza Media Systems社が無料で提供する`rtsp`ストリームのサンプルを使用しています。
   - rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov

#作業の目的
- `rtsp`ストリームを`FFmpeg`を用いて画像(jpgファイル)に変換
- 出力されたjpgファイルをほぼオンザフライで`S3`に同期させます

#AWSリソース
- EC2
  - インスタンスタイプ: t2.small
  - OS: Amazon Linux
  - HDD: 30GiB
- S3

EC2にアタッチするIAMロールやセキュリティグループの設定等は省略させていただきます。

#検証環境の準備
###1.jpg画像用フォルダーを作成

```
$ mkdir -p ~/images/test1
```

###2.FFmpegのインストールとセットアップ。
- 自分でビルドする必要のない`FFmpeg Static Builds`を利用します。
    - https://johnvansickle.com/ffmpeg/

```
$ wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz
$ tar xvfJ ffmpeg-git-amd64-static.tar.xz
$ sudo cp -av ffmpeg-git-20200211-amd64-static/ffmpeg /usr/local/bin/
$ sudo cp -av ffmpeg-git-20200211-amd64-static/ffprobe /usr/local/bin/
$ which {ffmpeg,ffprobe}
/usr/local/bin/ffmpeg
/usr/local/bin/ffprobe
```
```
$ ffmpeg -version
ffmpeg version N-51730-gf15007afa9-static https://johnvansickle.com/ffmpeg/  Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 8 (Debian 8.3.0-6)
configuration: --enable-gpl --enable-version3 --enable-static --disable-debug --disable-ffplay --disable-indev=sndio --disable-outdev=sndio --cc=gcc --enable-fontconfig --enable-frei0r --enable-gnutls --enable-gmp --enable-libgme --enable-gray --enable-libaom --enable-libfribidi --enable-libass --enable-libvmaf --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-librubberband --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libvorbis --enable-libopus --enable-libtheora --enable-libvidstab --enable-libvo-amrwbenc --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libdav1d --enable-libxvid --enable-libzvbi --enable-libzimg
libavutil      56. 39.100 / 56. 39.100
libavcodec     58. 68.102 / 58. 68.102
libavformat    58. 38.100 / 58. 38.100
libavdevice    58.  9.103 / 58.  9.103
libavfilter     7. 75.100 /  7. 75.100
libswscale      5.  6.100 /  5.  6.100
libswresample   3.  6.100 /  3.  6.100
libpostproc    55.  6.100 / 55.  6.100
```

###3.incronのインストール
* `incron`はEPELレポジトリからインストールしますのでまずEPELリポジトリを有効にする必要があります。

```
$ sudo yum-config-manager --enable epel
$ sudo yum -y install incron
```
- バージョンを確認

```
$ incrond -V
incrond 0.5.9
```
- 自動起動を設定し、サービスを起動

```
$ sudo chkconfig incrond on
$ sudo /etc/init.d/incrond start
```

#作業の手順
###1.S3に同期させるためのスクリプトの作成
* ソースのjpgファイルが削除されたらターゲットのjpgファイルも削除されるように `--delete` オプションを付けます。
* `BUCKET名`は適切なものに書き換えてください。

```bash:aws-synctoS3.sh
#!/bin/bash
/usr/bin/aws s3 sync /home/ec2-user/images/test1 s3://BUCKET名/images/test1 --delete
```
- 実行権限を付与

```
$ chmod +x ~/aws-synctoS3.sh
```
###2.incrontabの設定
* `incrontab -e`で設定
* 設定後、`incrontab -d`でテーブルをreload
* どんなタイプが設定可能か`incrontab -t`で確認可能
* incronのログは `/var/log/cron`　に出力されます

```
$ incrontab -l
/home/ec2-user/images/test1 IN_MODIFY,IN_CREATE,IN_DELETE,IN_ONESHOT,IN_NO_LOOP /home/ec2-user/aws-synctoS3.sh
```
・`IN_MODIFY` ファイルが上書きされた場合でもトリガーされる
・`IN_ONESHOT` イベントを一回のみ監視する
・`IN_NO_LOOP` イベントが完了するまで監視をしない

- 問題ないかログを確認しましょう。

```
$ sudo tail -f /var/log/cron
Feb 21 01:23:09 ip-10-16-10-61 incrond[2727]: ready to process filesystem events
Feb 21 01:49:01 ip-10-16-10-61 CROND[2776]: (root) CMD (/sbin/start --quiet update-motd)
Feb 21 01:53:52 ip-10-16-10-61 incrond[2727]: table for user ec2-user created, loading
Feb 21 01:55:31 ip-10-16-10-61 incrond[2727]: table for user ec2-user changed, reloading
```

###3.バックグラウンドでFFmpegコマンドの実行

```
$ ffmpeg -rtsp_transport tcp -i rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov -f image2 -s 1920x1080 -r 15 /home/ec2-user/images/test1/image-%08d.jpg  < /dev/null > /dev/null 2>&1 &
```
- パラメーターの説明

```
-rtsp_transport 
    rtspのトランスポートプロトコルを指定。udpでパケロスが生じた場合の回避策としてtcpに指定
-i
    入力ファイルもしくはストリームを指定
-f
    フォーマットを指定。FFmpegは賢いのでこのパラメーターは省略しても問題ない
-s
    解像度を指定 (wxh)
-r
    出力ファイルのフレームレートを指定
%08d
    出力のファイル名に8桁の数字で順番をつける
 
< /dev/null > /dev/null 2>&1 &
    FFmpegをバックグラウンドで起動させ、FFmpegの標準出力と標準出力エラーを/dev/nullにリダイレクトする
```

- `jobs`コマンドでバックグラウンドで動いているプロセスを確認できます。

```
$ jobs
[1]+  実行中               ffmpeg -rtsp_transport tcp -i rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov -f image2 -s 1920x1080 -r 15 /home/ec2-user/images/test1/image-%08d.jpg < /dev/null > /dev/null 2>&1 &
```

###4.指定したフォルダーにjpgファイルができたことを確認

```
$ ls ~/images/test1/image-0000* | head
/home/ec2-user/images/test1/image-00000001.jpg
/home/ec2-user/images/test1/image-00000002.jpg
/home/ec2-user/images/test1/image-00000003.jpg
/home/ec2-user/images/test1/image-00000004.jpg
```

###5.incronがトリガーされたことを確認

```
$ sudo tail -f /var/log/cron
Feb 21 03:14:48 ip-10-16-10-61 incrond[2727]: (ec2-user) CMD (/home/ec2-user/aws-synctoS3.sh)
Feb 21 03:14:49 ip-10-16-10-61 incrond[2727]: (ec2-user) CMD (/home/ec2-user/aws-synctoS3.sh)
Feb 21 03:14:51 ip-10-16-10-61 incrond[2727]: (ec2-user) CMD (/home/ec2-user/aws-synctoS3.sh)
```

###6.出力されたjpgファイルがS3に問題なく同期されたことを確認
![スクリーンショット 2020-02-21 12.22.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/6f10957d-4631-0519-c061-95b4f2e972ab.png)


#結果
`FFmpeg`を使ってrtspストリームを`15fps`でjpg画像に変換することができました。出力されたjpgファイルを`incron`を用いて`S3`に同期させることもできましたので本記事の目的は達成できたかと思います。今回は`15fps`を指定しましたので仕組み上、1秒に15回`S3`に同期されることになります。注意すべき点は`FFmpeg`の処理よりも`incron`プロセスの方が高負荷であることです。`30fps`や`60fps`に変換したい場合や2つ以上のストリームの画像変換を試したい場合、jpgではなくpngで出力させたい場合などインスタンスタイプを変更しながら検証する必要が出てきます。

私が検証した環境では`t2.small`で`FFmpeg`と`incron`と同時に走らせて`15fps`ならCPU使用率が約`80%`、ロードアベレージが`1.5`を超えた結果となりました。ギリギリですね。
![スクリーンショット 2020-02-21 12.17.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/429912/e1d68786-b658-5c17-401e-759733ad56d5.png)

#感想
`FFmpeg`はとても万能で動画や音声処理をするには欠かせないツールです。仕事でも動画変換をしたり動画配信したりで`FFmpeg`を使う機会がたくさんあります。使いこなせていない機能もまだまだあります。`FFmpeg`とはもっともっと仲良くなりたいなと考えている今日この頃の私です。実は`Amazon Linux 2`での検証もしてみたのですが`Amazon Linux 2`のEPELで採用されている`incron`のバージョンが新しくなり、`IN_NO_LOOP`がデフォルトとなったり、`IN_ONESHOT`は今回と違う動きを見せたりで少し苦戦していましたが再チャレンジし、いずれか報告できたらいいなと思っています。

#参考文献
https://www.ffmpeg.org/
https://ffmpeg.org/ffmpeg-formats.html#image2-2
https://en.wikibooks.org/wiki/FFMPEG_An_Intermediate_Guide/image_sequence
https://linux.die.net/man/5/incrontab