OpenCV For UnityのVideoWriterExampleが録画できなかったときの対処法

# はじめに
Unityの有料アセット、OpenCV For UnityのSampleのVideoWriterExampleが、録画できなくて困ったときの話です。
自分がうまくいった対処法と、関係なかったけど検討したことをその後ろにつらつら書いていきます。

## OpenCV For Unityの公式情報色々
- [OpenCV For Unity - 公式サイト](https://enoxsoftware.com/opencvforunity/)
- [OpenCV For Unity - AssetStore](https://assetstore.unity.com/packages/tools/integration/opencv-for-unity-21088)
- [OpenCV For Unity - GitHub](https://github.com/EnoxSoftware/OpenCVForUnity)
- [OpenCV For Unity - DEMO WebGL](https://enoxsoftware.github.io/OpenCVForUnity/webgl_example/index.html)

# 今回の録画できない問題の詳細

## 現象
- 録画した動画（コーデック:mjpeg / コンテナ:avi）が破損してて、PC上でもUnity上でも再生できない。具体的には、Consoleに出る録画データ情報が空、0ばっかり（CAP_PROP_FORMAT: 0, CAP_PROP_POS_MSEC: 0, CAP_PROP_FRAME_COUNT: 0）
- Recボタンを押しても1フレームしか赤くならない。


## やりたいこと
- 録画した動画が破損せず、再生できるようにしたい
- Recボタンを一回押すと赤くなって録画が始まり、もう一回押すと録画が終了するようにしたい

## 環境
- Windows 10
- Unity 2019.4.4f1
- OpenCV For Unity 2.4.0


# 対処法紹介の前に初期設定
## OpenCV For Unityの初期設定

この2つは、OpenCV For Unityに必須っぽいのでやりましょう！

- エディタ画面上 > Tools > OpenCV for Unity > Set Plugin Import Settings を押す
- Projectsで、OpenCV for Unity内のStreamingAssetsをAssets直下に移動する

…よく見ると、Assetに入ってるReadme.pdfに書いてあります。

## VideoWriter.ioの初期設定
さらに、VideoWriterExampleはもう一作業します

- [OpenCV Library](https://sourceforge.net/projects/opencvlibrary/)をダウンロードして、「\opencv\build\x64\vc14\bin\opencv_videoio_ffmpeg430_64.dll」
をUnityのプロジェクトフォルダ直下（Assets、Library、Logsとかがある場所）に置く


…これもよく見ると、Assetに入ってるReadme.pdfに書いてあります。
> If you want to use more video formats with the "Video Capture (string filename)" or
"VideoWriter" method, setup is required.
1)Download "OpenCV for Windows Version
4.3.0"(http://opencv.org/downloads.html).
2)Set PATH variable to "opencv_ffmpeg4.3.0.dll" or "opencv_ffmpeg4.3.0_64.dll".
if 32bit, "\path\to\opencv\build\x86\vc14\bin\".
if 64bit, "\path\to\opencv\build\x64\vc14\bin\".
Or
2)Copy to Project Folder.

Readme読もう（自戒）

# 解決策 - Unityの画面比を固定する
解決策は、「GameViewの画面サイズをFreeAspectではなく、何か指定の画面サイズにすること」です。
私はStandAlone（1024*768）にしました。

サンプルコードのVideoWriterExample.csのOnPostRenderの上から2つ目のif文の条件文を見ていただくと、recordingFrameRgbMat.width()とScreen.width、 recordingFrameRgbMat.height()とScreen.heightの値が一致していないと、自動でもう一度Recボタンが押されてしまい、スクリプト内で2度押しされて録画できないようになっています。

なんと、FreeAspectでは、recordingFrameRgbMatとScreenで1pixel誤差が生じることがあるようです。
FreeAspectでも、エディタ画面でGameViewの大きさをいじっていると、稀に誤差が発生せずに録画できちゃうこともあります。

よくよく考えれば、録画画面領域の確保もせずに録画なんてできるわけないのですが…
> Debug.LogError("Please fix the screen ratio of the Game View to recognize the recording area");

画面比固定しなさい！ってエラーが出るようにしてあげれば、他の人が同様のエラーに悩まされても解決できますね。
自分で例外処理を書くときは、例外処理されないための作業を促すエラー文を出すようにすると親切という学びを得ました。

ちなみにこれで、Editor上もWebGL書き出し後も正常に録画と再生ができました。

```c#:VideoWriterExample.cs
void OnPostRender()
        {
            if (isRecording)
            {
                if (frameCount >= maxframeCount ||
                    recordingFrameRgbMat.width() != Screen.width || recordingFrameRgbMat.height() != Screen.height)
                {
                    Debug.LogError("Please fix the screen ratio of the Game View to recognize the recording area");
                    OnRecButtonClick();
                    return;
                }

                frameCount++;

                // Take screen shot.
                screenCapture.ReadPixels(new UnityEngine.Rect(0, 0, Screen.width, Screen.height), 0, 0);
                screenCapture.Apply();

                Utils.texture2DToMat(screenCapture, recordingFrameRgbMat);
                Imgproc.cvtColor(recordingFrameRgbMat, recordingFrameRgbMat, Imgproc.COLOR_RGB2BGR);

                Imgproc.putText(recordingFrameRgbMat, frameCount.ToString(), new Point(recordingFrameRgbMat.cols() - 70, 30), Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(255, 255, 255), 2, Imgproc.LINE_AA, false);
                Imgproc.putText(recordingFrameRgbMat, "SavePath:", new Point(5, recordingFrameRgbMat.rows() - 30), Imgproc.FONT_HERSHEY_SIMPLEX, 0.8, new Scalar(0, 0, 255), 2, Imgproc.LINE_AA, false);
                Imgproc.putText(recordingFrameRgbMat, savePath, new Point(5, recordingFrameRgbMat.rows() - 8), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(255, 255, 255), 0, Imgproc.LINE_AA, false);

                writer.write(recordingFrameRgbMat);
            }
        }

```

# 他に試したこと
## 出力先を見直す
サンプルコードのVideoWriterExample.csはStartRecordingという関数が作ってあり、その引数で出力先を設定しています。
絶対パスで指定するならこんな感じです。

```c#:VideoWriterExample.cs
//元の命令
StartRecording(Application.persistentDataPath + "/VideoWriterExample_output.avi");

//絶対パス
StartRecording(Application.persistentDataPath + "@C:\Users\yourname\OpenCVForUnitySample\VideoWriterExample_output.avi");
```

複数動画を録画できるようにしたければ、この辺いじればできそうですね。

## コーデックとコンテナの設定を見直す
[UnityでOpenCVを利用した動画再生をしてみた](https://qiita.com/utibenkei/items/aaaa6cf5b470aa4f69d5#%E5%90%84%E3%83%97%E3%83%A9%E3%83%83%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%A7%E5%86%8D%E7%94%9F%E3%83%86%E3%82%B9%E3%83%88%E3%82%92%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F%E7%B5%90%E6%9E%9C)の著者さんが、コーデックとコンテナの組み合わせをどうすれば各プラットフォームで動くか確認して下さっています。


```c#:VideoWriterExample.cs
writer.open(savePath, VideoWriter.fourcc('M', 'J', 'P', 'G'), 30, new Size(Screen.width, Screen.height));
```

## その他参考にしたサイト
- [Unity OpenCV for Unityで生成されたブラウザ(WebGL)内の動画データをダウンロードしたいです](https://teratail.com/questions/275523)
- [【Unity】【 OpenCV for Unity】 RawImageに描画した動画を動画ファイルにして保存](https://qiita.com/wirsind55/items/1c39587b7a7cf3e3f780)
- [OpenCV For Unity VideoWriter.write() 出来ない　【解決済み】](https://qiita.com/wirsind55/items/35b411ae4d618ad09f47)
- [OpenCV for Unity VideoWriter関数で動画を保存する際のコーデックを.mp4または.movに変更したいです。](https://teratail.com/questions/277345)