エンコーダーのゆるい性能評価

# はじめに

[前回の記事](https://qiita.com/nakakura/items/4b2c1c312236ea96824b)でビルドしたエンコーダの評価を行った。

下記で使用するツールや計測データはすべて[github上](https://github.com/nakakura/encoder_evaluation)にある

# 前提と目的

ネットワーク上で映像を転送する際は、通常転送量を抑えるためにエンコードしてから転送する。一般的に映像パケットはRTPというフォーマットで転送される。
受信側ではRTPでビデオの再生位置を把握した上で、デコードしてユーザに提示する。

![flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/ea120501-7d46-8c0b-7758-90d0a48aee8b.png)


この時間が短ければ短いほどカメラに入った映像がユーザに提示されるまでの時間が短くなる。
gStreamerではエンコーダ・デコーダを自分で選択できるので、どれを使うのがいいか確かめるために性能を評価する。

# 計測方法

## やりかた

映像がカメラに入った瞬間と、画面に表示された瞬間の時間差を計るために以下のようにツールを挟んだ。

![flow2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/30994bc3-59a3-d424-b70b-5c297b70fd22.png)

まずカメラから映像が入ったあと、エンコーダで処理される前に赤色のマスキングを行う。
と同時に、シグナルを相手側へと送信する。
計測装置(Log Tool)は、ユーザ側のディスプレイを監視し、赤色の映像が表示される瞬間の時刻を取得する。
赤色の映像が表示された時刻とシグナルを受信した時刻の差を、エンコーダとデコーダ(とRTPパケットの生成)にかかった時間として扱う。

具体的には、こういう映像に

![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/ba601487-fd0d-c502-b591-fd1db8421166.png)


こういうマスクをかけて、赤色が表示される瞬間を計測する

![mask.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/89b8f239-140d-f052-6ff5-aaa3dded4955.png)

計測が終わったら戻して2周めに入る
![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/ca9aae23-821b-d76d-d36d-b09b29ca1f5b.png)


## 環境
### ハードウェア

送信側端末はCPU: Corei9-9880H, Memory: 64GB, GPU: GeForce GTX1650 4GBの端末にUbuntu19.10を入れたもの、
受信側の端末にはCPU: Corei7-8750H6, Memory: 32GB, GPU: GeForce RTX2080 8GBの端末にWindows10を入れたものを利用する。
両端末の間はswitchを経由してLANケーブルで接続する。

カメラは2種類用意し、[See3Cam(cu135)](https://www.e-consystems.com/jp/See3CAM-USB-3-Camera-jp.asp)と[Zed Mini](https://www.stereolabs.com/zed-mini/)でそれぞれ640*480, 2560*720の映像を60FPSで取得した。

厳密には部屋の明るさや撮影対象がどの程度動くかで処理時間が変わってくるが、今回は自宅の部屋で目覚まし時計を撮影している(なのでほとんど被写体が動いていない)。

### ソフトウェア

利用したエンコーダとデコーダは以下の通り。

- エンコーダ(in Ubuntu 19.10)
    - x264enc
    - openh264enc
    - nvh264enc
    - nvh265enc
- デコーダ(in Windows 10)
    - avdec_h264
    - openh264dec
    - nvh264dec
    - nvh265dec

時刻の計測は[Date.Ticks](https://docs.microsoft.com/ja-jp/dotnet/api/system.datetime.ticks?view=netframework-4.8)を取得したので、精度は100ナノ秒単位である。

フィルタを入れるプログラムはgStreamerのpixoverlayをちょっと改造してパイプラインに差し込んだ([github](https://github.com/nakakura/encoder_evaluation/blob/master/tools/filter/gstgdkpixbufoverlay.c))。

# 計測結果

OpenH264はデフォルトの設定([no optionと記載されているもの](https://github.com/nakakura/encoder_evaluation/blob/master/tools/gstreamer_scripts/sender/openh264/see3cam_noop.sh))では映像が汚かったので、エンコーダにオプションを[このように](https://github.com/nakakura/encoder_evaluation/blob/master/tools/gstreamer_scripts/sender/openh264/see3cam.sh)設定した。
nvh264encも設定を2種類変えている。nvh264(ps3)と書かれているものが[こちら](https://github.com/nakakura/encoder_evaluation/blob/master/tools/gstreamer_scripts/sender/nvh264/see3cam.sh)で、nvh264(ps5)と書かれているものが[こちら](https://github.com/nakakura/encoder_evaluation/blob/master/tools/gstreamer_scripts/sender/nvh264/see3cam_5.sh)。
nvh265encも同様([ps3](https://github.com/nakakura/encoder_evaluation/blob/master/tools/gstreamer_scripts/sender/nvh265/see3cam.sh), [ps5](https://github.com/nakakura/encoder_evaluation/blob/master/tools/gstreamer_scripts/sender/nvh265/see3cam_5.sh))。

この設定でそれぞれ100回ずつ処理時間を計測した。結果は以下の通りである。

## 640*480, 60FPS

| encoder   | x264enc          | openh264enc      | openh264enc      | openh264enc | openh264enc(no option) | nvh264enc(ps3)       | nvh264enc(ps3)        | nvh264enc(ps5) | nvh265(ps3)           | nvh265(ps5)  |
| -------------   | -------------          | -------------      | -------------      | ------------- | -------------| -------------       | -------------        | ------------- | -------------           | -------------  |
| decoder   | avdec_h264       | avdec_h264       | openh264dec      | nvh264dec   | avdec_h264             | avdec_h264       | nvh264dec        | nvh264dec           | nvh265           | nvh265           |
| counter   | process time(ms) | process time(ms) | process time(ms) |       process time(ms)      | process time(ms)       | process time(ms) | process time(ns) | process time(ms)    | process time(ms) | process time(ms) |
| avg       | 20.1             | 24.6             | 23.7             | 26.7        | 22.6                   | 19.3             | 14.4             | 15.5                | 43.5             | 39.0             |
| med       | 20.9             | 21.1             | 22.9             | 28.4        | 25.0                   | 19.0             | 15.0             | 15.0                | 43.0             | 38.9             |
| stdev | 4.7              | 8.8              | 6.9              | 11.9        | 6.1                    | 3.1              | 3.3              | 3.3                 | 6.7              | 5.5              |
| min       | 11.0             | 13.0             | 3.0              | 9.0         | 9.9                    | 12.0             | 7.0              | 8.9                 | 28.9             | 24.9             |
| max       | 28.0             | 46.4             | 40.9             | 50.9        | 29.9                   | 24.1             | 20.9             | 23.1                | 61.0             | 52.9             |

CDFに落としたものが下のグラフである。横軸が処理時間で、縦軸がその時間までに処理が終わっている確率である。例えばnvh264enc(ps3)でエンコードしてnvh264decでデコードする場合、処理時間が10msを下回ったものが20%程度、ほぼ全ての試行で処理が20msを下回っていることがわかる。一方でx264encでエンコードしavdec_h264でデコードしたものは、処理が20msを下回ったのが50%程度、ほぼ全ての試行で27msを下回っている。
![cdf.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/3384ed2a-7409-1dae-891a-527122027862.jpeg)


## 2560*720, 60FPS, Windows側ではVisual Studio2019でビルドされたものを使用

OpenH264encのデフォルト状態は映像が汚くてどうせ使わないので計測しなかった。nvh265のopt5も未計測。

| encoder   | x264enc          | openh264enc      | openh264enc      | openh264enc      | nvh264enc        | nvh264enc        | nvh264enc(option 5) | nvh265           |
|-------------| -------------          | -------------      | -------------      | -------------      | -------------        | -------------        | ------------- | -------------           |
| decoder   | avdec_h264       | avdec_h264       | openh264dec      | nvh264dec        | avdec_h264       | nvh264dec        | nvh264dec           | nvh265           |
| counter   | process time(ms) | process time(ms) | process time(ms) | process time(ms) | process time(ms) | process time(ms) | process time(ms)    | process time(ms) |
| avg       | 20.7             | 23.3             | 23.6             | 19.6             | 21.0               | 15.8             | 19.3                | 31.0               |
| med       | 19.0              | 22.9             | 24.0               | 19.9             | 20.9             | 16.0               | 19.0                  | 30.9             |
| stdev | 5.4              | 3.1              | 2.4              | 2.2              | 2.2              | 2.2              | 2.4                 | 3.2              |
| min       | 12.0               | 18.0               | 18.9             | 15.0               | 16.0               | 12.0               | 14.9                | 24.9             |
| max       | 38.9             | 39.1             | 29.9             | 23.1             | 26.1             | 20.0               | 24.9                | 49.9             |

CDF

![cdf_zed.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/4b0b5edd-e32f-caf7-5aae-054b1ad7f473.jpeg)


# 考察

エンコーダの影響が支配的だが、同一のエンコーダの場合デコーダでも差が出ている。今回の計測結果ではnvenc/nvdecが優秀。全体的に処理が早く、標準偏差も3ms程度しかなく一定の速度が出ている。
映像の品質は主観評価になるが、nvh265がきれいな気がする程度で大差はない(付録に画像を添付)。
今回は消費帯域を計測していないので、これ以上の評価をしようとすると計測環境を変えて多数の試行が必要であろう。

大抵このような処理時間の計測結果は正規分布に沿うので、CDFを見るとS字カーブを描くのが一般的である。大体のグラフでそうなっているが、oh264encを使ったものは上の方でグラフが崩れている。何らかの事象で処理が遅れていることがわかる。今回は原因の分析までは行っていないので、エンコーダのせいなのか、そのときPCでなにか重い処理が走ったのかは不明である(作業はしていなかったけど、常駐プロセスが悪さをしていないとは言い切れない)。

映像サイズが大きく変わっても処理時間があまり変わっていないのは、GPU処理のためビデオメモリに乗りきるサイズだと並列で処理できるため時間が変わらないんじゃないかなぁ(要調査)。

前回の記事でWindowsではVisual Studio, MSYS両方でビルドを行ったので、それらも一応比較する。

| Compiler |  Visual Studio 2019 | Visual Studio 2019 | Visual Studio 2019 | MSYS             | MSYS             | MSYS             |
|-------------|---------------------|--------------------|--------------------|------------------|------------------|------------------|
| encoder     | x264enc             | openh264enc        | nvh264enc          | x264enc          | openh264enc      | nvh264enc        |
| decoder     | avdec_h264          | openh264dec        | nvh264dec          | avdec_h264       | openh264dec      | nvh264dec        |
| counter     | process time(ms)    | process time(ms)   | process time(ms)   | process time(ms) | process time(ms) | process time(ms) |
| avg         | 20.7                | 19.6               | 15.8               | 17.2             | 24.0               | 16.1             |
| med         | 19                  | 19.9               | 16 .0                | 17.9             | 23.9             | 16 .0              |
| stdev(ms)   | 5.4                 | 2.2                | 2.2                | 3.1              | 2.1              | 2.0               |
| min         | 12.0                  | 15.0                 | 12.0                 | 11.9             | 18.0               | 12.0               |
| max         | 38.9                | 23.1               | 20.0                 | 30.9             | 28.0               | 19.9             |

正直なところ有意差がないと考えられる。そもそもデコーダ側が及ぼす影響が小さいので、デコーダ側で最適化の程度が多少変わってもそう大きな影響が出るとは考えづらく、想定の範囲内と言える。

# 付録

実際に転送された映像の画質

x264enc

![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/abc585c1-68ea-2f86-a410-f4e142c99645.png)


openh264(オプションなし)

![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/a792ba34-864a-5df7-0f3d-5cba7f61d15a.png)


openh264(オプションあり)

![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/08e6f31a-9bed-763b-df88-776a14e61c7b.png)


nvh264(ps3)
![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/dba44874-d556-ce2a-88bd-9fba8326285d.png)


nvh265(ps3)
![capture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104451/cf806773-ffd9-c61e-979b-48043436332c.png)