自動運転シミュレーターを録画するシステムを構築してみた

# はじめに
[技術チャレンジ部](https://challenge-club.connpass.com/)の、とも(Tomo)ことAirです。
[自動運転AIチャレンジ](https://www.jsae.or.jp/jaaic/)に参加しているメンバーを応援するために、自動運転をシミュレーションしている様子を画面キャプチャーで録画するシステムを構築しました。

# お品書き
## 機能拡張のベースとなるスクリプト
- 自動運転シミュレーターを繰り返し実行するスクリプト
    - メンバーにより既に作成済み。これに録画機能を付ければ、録画システムはきっとできる！

## 録画
- [OBS Studio](https://obsproject.com/)
    - クロスプラットフォームの配信ソフトとして有名だが、以下の理由で録画に採用
        - [ハードウェアエンコード](https://obsproject.com/kb/hardware-encoding)による録画ができ、今回の長時間録画のエンコード時間短縮や負荷低減に効果ありそう
        - [WebSocketによる制御](https://obsproject.com/kb/remote-control-guide)ができるので、仕組みが理解できれば、別でやっているVR配信にも応用できそう
        - 開発着手時点では、画面のレイアウトや録画シーンが未定だったため、マウスでの設定が便利そう
- [obs-cmd](https://github.com/grigio/obs-cmd)
    - OBS StudioをWebSocketで制御するCLIとして使用
    - 制御ソフトはいろいろありそうだが、今回は以下の理由で採用
        - 最新のWebSocket v5プロトコルに対応
            - [obs-cli](https://github.com/muesli/obs-cli)はv4までの対応の模様
        - ミニマルな機能だが、録画の開始と停止ができる
        - バイナリ配布があり、インストールが簡単
- [ffmpeg](https://ffmpeg.org/)
    - 録画ファイルの結合に使用
    - [OBS公式](https://obsproject.com/ja/download)によると、「Linuxのリリースは公式にはUbuntu 14.04以降で利用可能です。 FFmpeg が必要です。」とのことで、OBSにも必要

## 画面レイアウト
- [Zenity](https://help.gnome.org/users/zenity/stable/index.html.ja)
    - 画面にパッチ名とループ回数を表示させるために使用
    - コマンドラインから簡単にウィンドウを作成できる
- [xdotool](https://github.com/jordansissel/xdotool)
    - 画面レイアウトの変更に使用
    - Wayland環境では困難が予想される
        - 2023年度インテグレーション大会では、Ubuntu 22.04にnvidia製ドライバーを入れる手順のため、この環境ではX.orgになる模様(要出典)で問題なさそう
        - 2023年度シミュレーション大会では、CPU環境も提供されるため、環境によってはWaylandになる可能性があり、要注意かも
        - とはいえX.orgへの変更は[できるはず](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0717)

## 録画ファイルの公開
- [Apache HTTP Server](https://httpd.apache.org/)
    - 容量が必要だが、アップロード回数も多めになることが予想され、YouTubeなどの利用が難しそうなため、録画ファイル公開用webサーバーを自前で用意
    - MakerFaire向けに使用していたサーバーを利用
- [OpenMediaVault](https://www.openmediavault.org/)
    - 録画ファイル保存用NFSサーバーとして使用
- sftp
    - 録画ファイルのアップロードに使用

# 対応内容とポイント
## 録画
対応内容diffは[こちら](https://github.com/air-gh/aichallenge-tools/commit/3474e45ac18c77ee05a54f680ddbac1453e0a035)。

### OBS Studio
OBSは手動で起動しておいた上で、スクリプトから`obs-cmd`経由で録画することにする。

- [公式の手順](https://obsproject.com/ja/download)でppaを追加し、`obs-studio`をインストールする
- `obs-cmd`経由でWebSocketで制御するために、`ツール(T)->WebSocketサーバー設定`で、WebSocketサーバーを有効にしておく
- デフォルトでサーバーパスワードが設定されているはずなので、`ツール(T)->WebSocketサーバー設定->接続情報を表示`で、パスワードを控えておく
- シーンや画面レイアウトの設定を事前にしておく

### obs-cmd
- linux-amd64用の実行ファイルを、[Releases](https://github.com/grigio/obs-cmd/releases)からダウンロードしておき、`obs-cmd`にリネームして、`~/bin/`などPATHの通った場所に置く
- 先ほど調べておいた、OBSのサーバーパスワードを、
    - `~/.config/obs-cmd.toml`に、`OBS_WS_PASSWORD = "PassWord"`として設定する
    - …はずだったのだが、今見たら[仕様が変わった](https://github.com/grigio/obs-cmd/commit/af35b01ae50b58548bbb8d975dfb3070d2335708)ようなので、`obs-cmd --websocket obsws://localhost:4455/PassWord info`のように、`--websocket`オプションでパスワードを設定する
        - `--websocket`オプションは以下表記を省略
- WebSocketサーバーが有効になったOBSを`obs`コマンドで起動した状態で、`obs-cmd info`コマンドが通るか手動で確認しておく
- スクリプトからは、`obs-cmd recording start`と`obs-cmd recording stop`で録画を開始/停止する

例
```shell
obs-cmd --websocket obsws://localhost:4455/PassWord recording start
sleep 60
obs-cmd --websocket obsws://localhost:4455/PassWord recording stop
```

### ffmpeg
- `sudo apt install ffmpeg`で、ffmpegをインストール
- 結合する動画ファイルのリストを`ls`で作る
    - lsの`-Q`オプションでは、ダブルクオートで囲われるが、[ffmpegの仕様](https://trac.ffmpeg.org/wiki/Concatenate)ではシングルクオートで囲う必要があるため、sedで変換する
        - 囲っておけば、ファイル名にスペースがあっても問題ない
            - 実際、OBSのデフォルトでは、ファイル名にスペースが入る
    - [ffmpegの仕様](https://trac.ffmpeg.org/wiki/Concatenate)で、シングルクオートで囲われたファイル名の先頭に`file `という文字列も付与する必要があるため、同じくsedで付与する

例

```shell
REC_LIST="rec_file_list_tmp.txt"
ls -Q ${REC_PATH}/2023-*.mp4 | sed "s/\"/\'/g" | sed "s/^/file /" > ${REC_LIST}
```

- `ffmpeg -f concat`で動画ファイルを結合する
    - 念の為`-safe 0`をつけておき、絶対パス/相対パスのどちらでも指定できるようにしておく
    - 結合する動画ファイルのリストのファイルを`-i`で指定する

例

```shell
REC_LIST="rec_file_list_tmp.txt"
REC_RESULT_NAME="result_${TARGET_PATCH_NAME}_${BEST_TIME}.mp4"
ffmpeg -f concat -safe 0 -i ${REC_LIST} -c copy ${REC_PATH}/${REC_RESULT_NAME}
```

## 画面レイアウト
対応内容diffは[こちら](https://github.com/air-gh/aichallenge-tools/commit/a9d41f3221f07ab3a96271315fa6af6b94d7368a)。

### Zenity
- Ubuntu Desktop 22.04にはデフォルトで入っている模様
- [マニュアル](https://help.gnome.org/users/zenity/stable/info.html.ja)によると、`--info`と`--text`で情報を表示できる
- `LANG=C`にしないと、xdotoolでWindow名を見つけづらい
- [こちら](https://stackoverflow.com/questions/18234920/control-the-size-of-the-content-in-a-zenity-window)によると、[Pango Markup](https://docs.gtk.org/Pango/pango_markup.html)が使えるので、フォントサイズを大きくできる
- `¥n`で改行もできた
    - …のは勘で見つけた

例

```shell
ZENITY_FONTSIZE=32
LANG=C zenity --info --text "<span font='${ZENITY_FONTSIZE}'>${TARGET_PATCH_NAME}\nLoop: ${i}</span>" &
```

### xdotool
- `sudo apt install xdotool`でインストール
- `xdotool search --name "WindowName"`で、WindowNameという名前のWindowのIDを探す
    - RVizのウィンドウは候補が複数出てしまうため、`--onlyvisible`オプションで絞り込む
- `xdotool windowmove`と`xdotool windowsize`で、位置とサイズをいい感じに設定する
- スクリプトからいろいろ起動すると、Zenityのウィンドウが裏に入って見えなくなってしまうので、
    - `xdotool windowraise`で表に起こす
    - …のが動作しないため、`windowraise`の前に`windowfocus`でフォーカスを取るようにすると、`windowraise`が動作する
        - …のは勘で見つけた

例

```shell
AWSIM_WID=`xdotool search --name "AWSIM"`
xdotool windowmove ${AWSIM_WID} ${AWSIM_POS}
xdotool windowsize ${AWSIM_WID} ${AWSIM_SIZE}

ZENITY_WID=`xdotool search --name "Information"`
xdotool windowfocus ${ZENITY_WID}
xdotool windowraise ${ZENITY_WID}
```

# できた動画
[こちら](https://qiita.com/seigot/items/dbd43984335ed68f5bcd)に出てくるこちら！

https://www.youtube.com/watch?v=oD6F7P9Bx08

# 感想
- スクリプトにすると1行のコマンドだが、そこにたどり着くのが大変で、調査や試行錯誤で時間がかかる
    - 勘まで動員が必要
- 動作が目に見えるのが嬉しい
- 使ってもらえてもっと嬉しい！

# やり残したこと
- 録画ファイル公開の話が出てこないじゃねーか！
    - Webサーバーで、mp4ファイルの一覧をクリックすると、HTML5のVideoプレーヤーで再生する仕組みの構築、したかった…
        - 今回は[mod_autoindex](https://httpd.apache.org/docs/2.4/en/mod/mod_autoindex.html)で逃げたため、再生UIがいまいち
    - 録画ファイルが大きいので、WebサーバーでOpenMediaVaultのNFSをmountした