Sora Unity SDK を NVDEC に対応した話
Published: 2020-06-20 07:37:42

---
title: Sora Unity SDK を NVDEC に対応した話
published: true
description: 
tags: 
//cover_image: https://direct_url_to_image.jpg
---

Sora Unity SDK は [WebRTC SFU Sora](https://sora.shiguredo.jp/) の Unity クライアントです。

Sora Unity SDK は [NVIDIA VIDEO CODEC SDK](https://developer.nvidia.com/nvidia-video-codec-sdk) を使ったエンコードとデコードに対応していて、2020.1 (2/27 リリース) でエンコードに対応して、2020.2 (4/14 リリース)でデコードに対応しました。

この記事では、2020.2 でリリースされた、NVIDIA VIDEO CODEC SDK のデコーダ（NVDEC)で具体的にどんなことをしたのかについて書きます。

全体のコードは以下の PR にあります。

- [Windows の NvDec に対応 by melpon · Pull Request #3 · shiguredo/sora-unity-sdk](https://github.com/shiguredo/sora-unity-sdk/pull/3)

C++ よりはビルド寄りの話です。

## デコーダを実装する

NVDEC を使ったデコーダを書くのは、そんなに難しくないです。難しくないというか、めちゃめちゃ簡単です。
NVIDIA VIDEO CODEC SDK はサンプルに NvDecoder.cpp というのを用意してくれています。

- [video-sdk-samples/NvDecoder.cpp at master · NVIDIA/video-sdk-samples](https://github.com/NVIDIA/video-sdk-samples/blob/master/Samples/NvCodec/NvDecoder/NvDecoder.cpp)

これを使って（ライセンスの確認は忘れずに）、以下のように書けばデコードが出来ます。

```cpp
class NvDecoderCuda {
public:
  // 初期化
  void Init() {
    cuInit(0);
    cuDeviceGet(&cu_device_, 0);
    cuCtxCreate(&cu_context_, 0, cu_device_);
    decoder_.reset(new NvDecoder(cu_context_, false, cudaVideoCodec_H264));
  }

  // デコード
  void Decode(const uint8_t* ptr, int size, uint8_t**& frames, int& frame_count) {
    decoder_->Decode(ptr, size, &frames, &frame_count);
  }

private:
  CUdevice cu_device_;
  CUcontext cu_context_;
  std::unique_ptr<NvDecoder> decoder_;
};
```

これであとは `Decode()` 関数にエンコードされたフレームデータへのポインタを渡せばデコードされたデータが `frames` に設定されます。

なおデコードしたデータは NV12 形式なので、必要に応じて ARGB8888 などに変換してやる必要があります。

## CUDA を使ったビルドに対応する

NVDEC を利用するには CUDA 環境が必須です。さりげなく上記のコードでも CUDA を初期化しています。

そして CUDA のソースコードは `nvcc` コンパイラでコンパイルしてやる必要があるため、ビルド環境に CUDA を使ったビルドを含めてやる必要があります。

幸いなことに、CMake はバージョン 3.8 以上で `enable_language(CUDA)` に対応しています。これを使うと `nvcc` コンパイラを自動で探してきてコンパイルしてくれるようになります。

```cmake
enable_language(CUDA)

set_source_files_properties(
    NvCodec/NvCodec/NvDecoder/NvDecoder.cpp
    src/hwenc_nvcodec/nvcodec_video_decoder_cuda.cpp
  PROPERTIES
    # ファイル拡張子は .cpp だが C++ コンパイラではなく CUDA コンパイラを利用する
    LANGUAGE CUDA
    CUDA_SEPARABLE_COMPILATION ON
)
```

こんな風に書けば、これらのソースコードを `nvcc` でコンパイルしてリンクして実行出来るようになります。

### CUDA と WebRTC を混ぜない

CUDA を扱う時にはちょっと気をつける点があって、それは CUDA と WebRTC を混ぜてはいけないということです。
つまり CUDA のソースを書く時に WebRTC の関数を一切使ってはいけないし `#include` してもいけないということです。

実際に WebRTC のソースコードを `#include` してみるとコンパイルエラーになります。詳細は見ていないですが、多分コンパイラごとの分岐で変な場所に行ってエラーになっているんじゃないかと思います。
なので CUDA を利用する処理は別の .cpp ファイルにした上で、CUDA 側は WebRTC を含むヘッダーファイルを一切 `#include` してはいけません。

実際今回の対応では `webrtc::VideoDecoder` を実装した `nvcodec_video_decoder.cpp` ファイルと、その内部で CUDA を使ったデコードのみを対応する `nvcodec_video_decoder_cuda.cpp` に分けています。
これで無事ビルドが出来るようになって、いい感じに Windows で NVDEC が動くようになりました。

## GitHub Action に対応する

Sora Unity SDK は GitHub Actions で CI を行ったりリリースを作ったり出来るようになっています。
なのでこの CUDA を使ったビルドも GitHub Actions でビルドが出来るのが望ましいでしょう。

残念なことに、上記の対応では GitHub Actions でビルドが出来ませんでした。
なぜかというと、GitHub Actions の実行環境には CUDA 対応のビデオカードが入っていないため、CUDA Toolkit のインストーラを実行するとドライバのインストールでエラーになってしまうからです。

### nvcc だけ取り出す

理論的には、nvcc コンパイラと cuda.lib と cuda.h さえあればビルドできるはずです。
いろいろ調べてみると、CUDA Toolkit のインストーラで local タイプ（全部入りのダウンロード）をダウンロードして unzip してやると、インストールするそれぞれのコンポーネントをそのまま見ることができることが分かりました。

![](https://dev-to-uploads.s3.amazonaws.com/i/aqztyd3ie9d69faacgb5.png)

![](https://dev-to-uploads.s3.amazonaws.com/i/bbztp3oqxv98or0bjpxx.png)

この中に入っている `nvcc` というディレクトリが欲しかったコンパイラです。
わざわざインストーラを実行しなくても、この nvcc ディレクトリを持ってきて利用すれば動くはずです。

### CMake を修正する

CMake で `enable_languages(CUDA)` をすると、自動で nvcc コンパイラを探してくれます。
じゃあどうやって探しているかというと、Windows では CUDA の Visual Studio Integration を使っています。
この CUDA の VS Integration は CUDA Toolkit のインストール時に選択してインストールするものですが、さっき書いたように GitHub Actions では CUDA Toolkit のインストーラは動きません。

そのため、GitHub Actions からの利用を考えた場合 `enable_language(CUDA)` は使えません。

そこでどうするかというと、[FindCUDA](https://cmake.org/cmake/help/latest/module/FindCUDA.html) を利用します。
これは CUDA の言語サポートが実装されたため deprecated になっている機能ですが、これは VS Integration を見たりしないので安心して使えます。

以下のように利用します。

```cmake
# nvcc コンパイラの配置場所を設定する
set(CUDA_TOOLKIT_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/_install/cuda/nvcc)

# FindCUDA を利用する
find_package(CUDA REQUIRED)

set_source_files_properties(
    NvCodec/NvCodec/NvDecoder/NvDecoder.cpp
    src/hwenc_nvcodec/nvcodec_video_decoder_cuda.cpp
  PROPERTIES
    CUDA_SOURCE_PROPERTY_FORMAT OBJ
)
cuda_compile(CUDA_FILES
    NvCodec/NvCodec/NvDecoder/NvDecoder.cpp
    src/hwenc_nvcodec/nvcodec_video_decoder_cuda.cpp
  OPTIONS
    # Visual Studio C++ コンパイラに渡す追加のコンパイラオプション
    -Xcompiler /utf-8
    -Xcompiler /I${CMAKE_CURRENT_SOURCE_DIR}/NvCodec/include
    -Xcompiler /I${CMAKE_CURRENT_SOURCE_DIR}/NvCodec/NvCodec
)
target_sources(SoraUnitySdk PRIVATE ${CUDA_FILES})
```

これに加えて GitHub Actions の[キャッシュ機構](https://help.github.com/ja/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows)も利用して、毎回 2.5 GB ある CUDA Toolkit インストーラをダウンロードしてしまうことが無いようにしました。

こうすることで、無事 CUDA のインストールできない環境、つまり GitHub Actions でもビルド出来るようになりました。

## 感想

見て分かる通り、デコーダの実装はすぐに終わったのですが、ビルド環境の整備で苦労しました。
GitHub Actions 対応は、CUDA Toolkit がインストールできない時点で諦めようかと思っていたんですが、何とか動かせて良かったです。