<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 合同会社Wandbox</title>
    <description>The latest articles on DEV Community by 合同会社Wandbox (@wandbox).</description>
    <link>https://dev.to/wandbox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F2519%2Fba61c90b-a561-405a-af5e-3271b9458232.png</url>
      <title>DEV Community: 合同会社Wandbox</title>
      <link>https://dev.to/wandbox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wandbox"/>
    <language>en</language>
    <item>
      <title>WebRTC Load Testing Tool Zakuro を作った話</title>
      <dc:creator>melpon</dc:creator>
      <pubDate>Thu, 24 Sep 2020 06:36:11 +0000</pubDate>
      <link>https://dev.to/wandbox/webrtc-load-testing-tool-zakuro-p61</link>
      <guid>https://dev.to/wandbox/webrtc-load-testing-tool-zakuro-p61</guid>
      <description>&lt;p&gt;先日、&lt;a href="https://github.com/shiguredo/zakuro" rel="noopener noreferrer"&gt;WebRTC Load Testing Tool Zakuro&lt;/a&gt; がリリースされました。&lt;/p&gt;

&lt;p&gt;Zakuro は WebRTC SFU Sora に対して負荷を掛けるためのクライアントで、自分（正確には合同会社Wandbox）が時雨堂から仕事として請けて開発したものになります。&lt;/p&gt;

&lt;p&gt;ここでは、この Zakuro を実装するためにどんなことをしたのかを紹介します。&lt;/p&gt;

&lt;p&gt;Zakuro の概要について知りたい場合は &lt;a href="https://twitter.com/voluntas" rel="noopener noreferrer"&gt;@voluntas&lt;/a&gt; さんの書いている&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://link.medium.com/1Ro0K2iAA9" rel="noopener noreferrer"&gt;WebRTC Load Testing Tool Zakuro 開発中&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://link.medium.com/fra3c4qmF9" rel="noopener noreferrer"&gt;WebRTC Load Testing Tool Zakuro 公開しました&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;あたりを参照してください。&lt;/p&gt;

&lt;h2&gt;
  
  
  Momo から実装をコピーしてくる
&lt;/h2&gt;

&lt;p&gt;Zakuro は大体 Momo と同じような機能を備えているので、Momo から以下のような実装をコピペしてきました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Momo のビルドシステム&lt;/li&gt;
&lt;li&gt;引数パーサー&lt;/li&gt;
&lt;li&gt;Sora クライアント、Sora API サーバー&lt;/li&gt;
&lt;li&gt;WebSocket、SSL などの通信周り&lt;/li&gt;
&lt;li&gt;WebRTC の PeerConnection を確立してハンドリングする部分&lt;/li&gt;
&lt;li&gt;カメラからのキャプチャ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;その後、Zakuro を動かすために不要な部分、例えば Windows 専用のコード（Linux で動けば良い）、SDL を利用しているコード（受信側は不要）、ハードウェアエンコーダのコード（ハードウェアエンコーダは使わない）などを削除していきました。&lt;/p&gt;

&lt;p&gt;これでひとまずビルドが通り、Zakuro でカメラキャプチャした映像が Sora Labo などで見れるようになりました。&lt;/p&gt;

&lt;h2&gt;
  
  
  複数クライアントに対応する
&lt;/h2&gt;

&lt;p&gt;Momo は 1 プロセス 1 クライアントで接続しますが、Zakuro は大量のクライアントを接続して負荷を掛けるツールなので、複数クライアントに対応する必要があります。&lt;br&gt;
これは元の Momo のコードが整理されていたので、&lt;code&gt;RTCManager&lt;/code&gt; と &lt;code&gt;SoraClient&lt;/code&gt; をクライアントの数だけ生成すれば動きました。&lt;/p&gt;

&lt;p&gt;こんなコードになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VirtualClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;public:&lt;/span&gt;
  &lt;span class="n"&gt;VirtualClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;boost&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;asio&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;io_context&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;ioc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;rtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;scoped_refptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ScalableVideoTrackSource&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;capturer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;RTCManagerConfig&lt;/span&gt; &lt;span class="n"&gt;rtcm_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;SoraClientConfig&lt;/span&gt; &lt;span class="n"&gt;sorac_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rtc_manager_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RTCManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rtcm_config&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capturer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;sora_client_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;SoraClient&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ioc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rtc_manager_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sorac_config&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sora_client_&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;sora_client_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;unique_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RTCManager&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;rtc_manager_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;shared_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SoraClient&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sora_client_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;このように実装して、&lt;code&gt;--vcs&lt;/code&gt; オプションで指定された数だけクライアントを生成するだけで Sora 側に負荷を掛けれるようになりました。&lt;/p&gt;

&lt;p&gt;ただし、このまま一気に大量のクライアントを接続するのは Sora に接続時の負荷が掛かり過ぎるし、現実にも則していないので、徐々に接続するための &lt;code&gt;--hatch-rate&lt;/code&gt; オプションも実装しました。&lt;/p&gt;

&lt;h2&gt;
  
  
  Safari みたいな Fake 映像/音声を実装する
&lt;/h2&gt;

&lt;p&gt;Safari には Fake 映像/音声を生成するためのデバイスが用意されていて、そのデバイスを使うとこんな表示になります。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fubj26nq2fmonihnixsn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fubj26nq2fmonihnixsn9.png" alt="Safari の Fake 映像"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;スクリーンショットだと分からないですが、複数のサイン波を組み合わせた音も流れています。&lt;/p&gt;

&lt;p&gt;この Fake 映像/音声を Zakuro にも移植しました。&lt;/p&gt;

&lt;h3&gt;
  
  
  Fake 映像
&lt;/h3&gt;

&lt;p&gt;Safari の場合、Fake 映像を作るためにグラフィックスライブラリである &lt;a href="https://cairographics.org/" rel="noopener noreferrer"&gt;cairo&lt;/a&gt; を利用しています。&lt;br&gt;
cairo は LGPL または MPL のデュアルライセンスで、まあ問題なさそうってことで当初はこれを使おうかと思っていました。&lt;/p&gt;

&lt;p&gt;ただ、もう少し緩いライセンスのライブラリは無いかなと調べてみると &lt;a href="https://blend2d.com/" rel="noopener noreferrer"&gt;Blend2D&lt;/a&gt; というのを見つけました。&lt;br&gt;
これは Zlib ライセンスで、JIT コンパイルする上にマルチスレッド使えるとかいうなかなか凄いライブラリのようです。&lt;/p&gt;

&lt;p&gt;どっちを使っても Fake 映像を作るには問題ないんですが、Blend2D の方が面白そうということで、Blend2D を使うことにしました。&lt;/p&gt;

&lt;p&gt;実際使ってみた感じ、ほぼ問題なく使えて Fake 映像が実装できてしまったのであまり書くことは無いんですが、ちょっと嵌った点としては以下の2点ぐらいです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BLContext&lt;/code&gt; の rotation や translate がどういう順番で適用されるかが分からなくてちょっと時間を使いました。実験コード書いてみた感じ、最後に書いたのから順番に適用されていくようです（OpenGL と同じ）。&lt;/li&gt;
&lt;li&gt;点線がどういう風に書いても反映されなくて、おかしいなーって調べてみたら、点線は未実装でした (&lt;a href="https://github.com/blend2d/blend2d/issues/48" rel="noopener noreferrer"&gt;#48&lt;/a&gt;)。なので点線を表示するのは諦めています。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;あとはこの生成した画像をあたかもカメラから取り込んだかのようにエンコーダに送ってやると、映像がエンコードされて Sora に送信されます。&lt;/p&gt;

&lt;p&gt;最終的な Fake 映像の実装は &lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/fake_video_capturer.cpp" rel="noopener noreferrer"&gt;zakuro/fake_video_capturer.cpp at develop · shiguredo/zakuro&lt;/a&gt; にあります。&lt;/p&gt;

&lt;p&gt;これで以下のような Fake 映像になりました。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2rwouclbpwticexm574d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2rwouclbpwticexm574d.png" alt="Zakuro の Fake 映像"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Fake 音声
&lt;/h3&gt;

&lt;p&gt;Safari の Fake 音声を調べると、プログラム上で以下の4種類のサイン波を生成していることが分かります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hum 音。150 Hz で常時鳴り続けてる。&lt;/li&gt;
&lt;li&gt;noise 音。3000 Hz で常時鳴り続けてる。&lt;/li&gt;
&lt;li&gt;bip 音。1500 Hz で、bop 音の1秒後に一瞬だけ鳴る。&lt;/li&gt;
&lt;li&gt;bop 音。500 Hz で、bip 音の1秒後に一瞬だけ鳴る。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Safari の Fake 音声の場合、ノイズキャンセリング機能を ON にしてると noise 音が消えるという実装が入ってましたが、Zakuro では実装していません。&lt;/p&gt;

&lt;p&gt;Fake 音声は、これらのサイン波の合成をプログラムで作ってやるだけなので、作るのはすごく簡単でした。&lt;/p&gt;

&lt;p&gt;大変だったのは、このデータを渡す部分です。&lt;br&gt;
Fake 音声を生成して録音したデータとしてエンコードしてもらうには、AudioDeviceModule (ADM) を自前で実装する必要があります。&lt;br&gt;
&lt;a href="https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/modules/audio_device/include/audio_device.h" rel="noopener noreferrer"&gt;audio_device.h&lt;/a&gt; を見れば分かるように、ADM が要求している関数は凄い大量にあり、今のところ 58 個の純粋仮装関数を実装する必要があります。&lt;br&gt;
大抵は無視するか何もしないで 0 を返せばいいだけなんですが、中にはちょっとした処理を書かないといけない部分もあってなかなか大変でした。&lt;/p&gt;

&lt;p&gt;最終的な Fake 音声の実装は &lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/rtc/zakuro_audio_device_module.cpp" rel="noopener noreferrer"&gt;zakuro/zakuro_audio_device_module.cpp at develop · shiguredo/zakuro&lt;/a&gt; にあります。&lt;/p&gt;
&lt;h2&gt;
  
  
  映像と音声の受信を無視する
&lt;/h2&gt;

&lt;p&gt;Zakuro は Sora クライアントとして振る舞うので、映像と音声の受信も出来る必要があります。&lt;br&gt;
しかし、これらの映像を表示したり、音声を再生したりといったことをすると、その環境でのデバイスが必要になってしまうため、動かせる場所が限定されてしまいます。&lt;br&gt;
そのため、映像データと音声データを受信した上で破棄する処理が必要になります。&lt;/p&gt;

&lt;p&gt;映像に関しては、デコーダで CPU を使うのが勿体ないので、何もしないデコーダを作りました。こんな感じになっています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NopVideoDecoder&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VideoDecoder&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;public:&lt;/span&gt;
  &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;InitDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VideoCodec&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;codec_settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;number_of_cores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;WEBRTC_VIDEO_CODEC_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EncodedImage&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;input_image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;missing_frames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;render_time_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback_&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;WEBRTC_VIDEO_CODEC_UNINITIALIZED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 適当に小さいフレームをデコーダに渡す&lt;/span&gt;
    &lt;span class="n"&gt;rtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;scoped_refptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;I420Buffer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i420_buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;I420Buffer&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VideoFrame&lt;/span&gt; &lt;span class="n"&gt;decoded_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VideoFrame&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_video_frame_buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i420_buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_timestamp_rtp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;callback_&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Decoded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded_image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;absl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;nullopt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;absl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;nullopt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;WEBRTC_VIDEO_CODEC_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;RegisterDecodeCompleteCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DecodedImageCallback&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;callback_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;WEBRTC_VIDEO_CODEC_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;WEBRTC_VIDEO_CODEC_OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;PrefersLateDecoding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ImplementationName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"NOP Decoder"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DecodedImageCallback&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;callback_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;何もしないとか言いつつ小さいフレームを生成して渡しているのは、WebRTC はいろんな場所の統計情報を使って処理を変えているため、ここで結果を作って渡してやらないと、どこかで不整合が起きるんじゃないかと思ったからです。&lt;/p&gt;

&lt;p&gt;あとはこの NopVideoDecoder を生成する factory を実装して…&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NopVideoDecoderFactory&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VideoDecoderFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;public:&lt;/span&gt;
  &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SdpVideoFormat&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetSupportedFormats&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SdpVideoFormat&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;supported_codecs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;supported_codecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SdpVideoFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cricket&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;kVp8CodecName&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SdpVideoFormat&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SupportedVP9Codecs&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;supported_codecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push_back&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;supported_codecs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;unique_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VideoDecoder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CreateVideoDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SdpVideoFormat&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;unique_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;VideoDecoder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;absl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;make_unique&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NopVideoDecoder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cricket::MediaEngineDependencies::video_decoder_factory&lt;/code&gt; に &lt;code&gt;NopVideoDecoderFactory&lt;/code&gt; を設定するだけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;media_dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;video_decoder_factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NopVideoDecoderFactory&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで映像データを受信して無視するようになりました。&lt;/p&gt;

&lt;p&gt;音声に関しては、せっかく Fake 音声で ADM を実装したので ADM で音声データを無視するようにしました。&lt;br&gt;
これだと音声のデコード処理は走ってしまいますが、映像データに比べれば大したことは無いと思うのでこのままにしています。&lt;br&gt;
これで音声データも受信して無視するようになりました。&lt;/p&gt;
&lt;h2&gt;
  
  
  フォントを Zakuro バイナリに埋め込む
&lt;/h2&gt;

&lt;p&gt;Blend2D は ttf 形式のフォントを読んで利用できるので、最初は macOS や Ubuntu の標準フォントのパスを適当に指定して表示していました。&lt;br&gt;
ただ、これだと CentOS や他の OS 向けにビルドを追加する際に毎回フォントパスを探すことになってしまいます。&lt;br&gt;
流石にそれは大変そうなので、Zakuro にフリーフォントを同封することにしました。&lt;/p&gt;

&lt;p&gt;利用するフォントは &lt;a href="https://fonts.google.com/specimen/Kosugi" rel="noopener noreferrer"&gt;Kosugi&lt;/a&gt; です。&lt;br&gt;
&lt;a href="https://fonts.google.com/" rel="noopener noreferrer"&gt;Google Fonts&lt;/a&gt; の中から日本語が扱えて Apache ライセンスのものを探しました。&lt;/p&gt;

&lt;p&gt;で、この Kosugi フォントを同封することにはしたのですが、Zakuro は今まで単体のバイナリで動くようになっていたので、出来れば変わらず単体で動かしたいところです。&lt;br&gt;
なので Zakuro バイナリにフォントデータを埋め込むことにしました。&lt;/p&gt;

&lt;p&gt;一番最初に考えたのは、フォントデータを C のソースに変換することです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 以下のコードを Kosugi.ttf ファイルから自動生成する&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;KOSUGI_TTF&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x0f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;このようなコードを自動生成してコンパイルすれば、フォントデータを Zakuro バイナリに埋め込めます。&lt;/p&gt;

&lt;p&gt;ただし、これはコードを自動生成するためのツールが必要になってきます。&lt;br&gt;
公式に対応する予定は無いですが、Zakuro は一応 macOS でもビルド可能になっているため、macOS, Ubuntu, CentOS 上で動くコードの自動生成ツールが必要になります。&lt;br&gt;
このようなツールを作るのも探すのも手間だし、そのツールのメンテナンスも必要になってきて大変なので、この方法は諦めることにしました。&lt;/p&gt;

&lt;p&gt;もう少し調べてみると、Linux の場合、ld コマンドや objdump コマンドで、バイナリをそのまま .o ファイルに変換できることが分かりました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ld &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; binary Kosugi.ttf &lt;span class="nt"&gt;-o&lt;/span&gt; Kosugi.o
&lt;span class="nv"&gt;$ &lt;/span&gt;nm Kosugi.o
00000000001d4648 D _binary_Kosugi_ttf_end
00000000001d4648 A _binary_Kosugi_ttf_size
0000000000000000 D _binary_Kosugi_ttf_start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;macOS の場合は、リンク時に &lt;code&gt;-sectcreate &amp;lt;segment名&amp;gt; &amp;lt;section名&amp;gt; &amp;lt;対象ファイル&amp;gt;&lt;/code&gt; という指定をすると、そのファイルを指定したセクションに埋め込めるようです。&lt;/p&gt;

&lt;p&gt;これらを利用すれば Zakuro バイナリにフォントデータを埋め込んでビルドできるということが分かりました。あとは CMake にバイナリを埋め込むコードを書いて、&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;TARGET_OS STREQUAL &lt;span class="s2"&gt;"macos"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# バイナリの組み込み&lt;/span&gt;
  &lt;span class="nb"&gt;target_link_options&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;zakuro PRIVATE -sectcreate __DATA __kosugi_ttf Kosugi.ttf&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;elseif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;TARGET_OS STREQUAL &lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# バイナリの組み込み&lt;/span&gt;
  &lt;span class="nb"&gt;add_custom_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;OUTPUT kosugi_ttf.o
    COMMAND ld
    ARGS -r -b binary -o &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_BINARY_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/kosugi_ttf.o &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/Kosugi.ttf
    MAIN_DEPENDENCY &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/Kosugi.ttf&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;set_source_files_properties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;kosugi_ttf.o
    PROPERTIES
      EXTERNAL_OBJECT true
      GENERATED true
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;target_sources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;zakuro PRIVATE kosugi_ttf.o&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;endif&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;macOS と Linux で共通してバイナリを返すクラスを作ってやるだけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#ifndef EMBEDDED_BINARY_H_
#define EMBEDDED_BINARY_H_
&lt;/span&gt;
&lt;span class="cp"&gt;#ifdef __APPLE__
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;mach-o/getsect.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;mach-o/ldsyms.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#endif
&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;EmbeddedBinaryContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmbeddedBinary&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;public:&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;EmbeddedBinaryContent&lt;/span&gt; &lt;span class="n"&gt;kosugi_ttf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#ifdef __APPLE__
&lt;/span&gt;    &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;getsectiondata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;_mh_execute_header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"__DATA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"__kosugi_ttf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;    &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;_binary_Kosugi_ttf_start&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;_binary_Kosugi_ttf_end&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_binary_Kosugi_ttf_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;_binary_Kosugi_ttf_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;_binary_Kosugi_Regular_ttf_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="n"&gt;EmbeddedBinaryContent&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;こうすることで、無事 Zakuro バイナリにフォントを埋め込んで利用できるようになりました。&lt;/p&gt;

&lt;h2&gt;
  
  
  y4m ファイル、wav ファイルからのキャプチャを用意する
&lt;/h2&gt;

&lt;p&gt;Zakuro は Fake 映像/音声だけではなく、指定した y4m ファイルや wav ファイルを流すことができます。&lt;/p&gt;

&lt;p&gt;y4m ファイルは、YUV 形式の生データが並んでいる動画ファイルです。&lt;br&gt;
これのパーサは探せばいくつかあったんですが、欲しい要件を満たすのが無かったのと、フォーマットを見てみると割と簡単だったので自前でパーサを書きました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/y4m_reader.h" rel="noopener noreferrer"&gt;zakuro/y4m_reader.h at develop · shiguredo/zakuro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/y4m_reader.cpp" rel="noopener noreferrer"&gt;zakuro/y4m_reader.cpp at develop · shiguredo/zakuro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;欲しかった機能は、&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;YUV420 形式のデータが取得できること&lt;/li&gt;
&lt;li&gt;y4m は相当でかいファイルになることが予想されるので、メモリ上に全データを読まずにロードできること&lt;/li&gt;
&lt;li&gt;WebRTC に送信する FPS と動画の FPS が一致しているとは限らないので、「何ミリ秒時点のフレームが欲しい」という要求をすればそこのフレームが取得できること&lt;/li&gt;
&lt;li&gt;ファイルの終端を超えた時間を要求されたら、ループして最初から再生できること&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;あたりです。&lt;code&gt;y4m_reader.cpp&lt;/code&gt; ではそのあたりに対応しています。&lt;/p&gt;

&lt;p&gt;wav ファイルも、用途を限定すれば難しいフォーマットじゃないので、これも自前のパーサを書きました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/wav_reader.h" rel="noopener noreferrer"&gt;zakuro/wav_reader.h at develop · shiguredo/zakuro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/wav_reader.cpp" rel="noopener noreferrer"&gt;zakuro/wav_reader.cpp at develop · shiguredo/zakuro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これはそんな大したサイズにならないだろうと思ったので、全データをメモリ上に乗せています。&lt;/p&gt;

&lt;p&gt;これらを使って Fake 映像/音声の代わりに送信することで、無事 y4m と wav ファイルの映像/音声を流すことが出来るようになりました。&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenH264 に対応する
&lt;/h2&gt;

&lt;p&gt;Zakuro は VP8/VP9 に対応しているので H.264 にも対応したいところですが、基本的に H.264 を利用するにはライセンス費用を払う必要があります。&lt;br&gt;
ただし、Cisco がビルドして公開している OpenH264 バイナリを利用すれば、Cisco がライセンス費用を肩代わりしてくれます。&lt;br&gt;
なので Cisco が公開している OpenH264 バイナリを使って H.264 に対応しました。&lt;/p&gt;

&lt;p&gt;Zakuro に &lt;code&gt;--openh264 &amp;lt;libopenh264.so の絶対パス&amp;gt;&lt;/code&gt; を指定すれば、H.264 で映像を流せるようになります。&lt;/p&gt;

&lt;p&gt;実装としては、WebRTC は OpenH264 を使ったエンコード機能を既に持っているので、これをコピーしてきて、OpenH264 の呼び出しを動的なものに書き換えただけです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/rtc/dynamic_h264_video_encoder.h" rel="noopener noreferrer"&gt;zakuro/dynamic_h264_video_encoder.h at develop · shiguredo/zakuro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/zakuro/blob/develop/src/rtc/dynamic_h264_video_encoder.cpp" rel="noopener noreferrer"&gt;zakuro/dynamic_h264_video_encoder.cpp at develop · shiguredo/zakuro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これで無事、Cisco の公開している OpenH264 バイナリを使った H.264 エンコードに対応できました。&lt;/p&gt;

&lt;h3&gt;
  
  
  WebRTC の利用する OpenH264 について
&lt;/h3&gt;

&lt;p&gt;上記で WebRTC のソースを利用しているのを見れば分かるように、WebRTC ライブラリはデフォルトで OpenH264 に対応しています。&lt;br&gt;
しかしこれは、OpenH264 のソースを組み込んでビルドしたもので、「Cisco がビルドして公開している OpenH264 バイナリ」を利用したものではありません。&lt;br&gt;
そのため WebRTC ライブラリにデフォルトで入っている H.264 エンコーダを利用するとライセンス費用が発生してしまいます（ブラウザ経由で使う分には Google とか Mozilla が何とかしてくれるので問題ないです）。&lt;/p&gt;

&lt;p&gt;そのあたりの問題があるため、Zakuro が依存している &lt;a href="https://github.com/shiguredo-webrtc-build/webrtc-build" rel="noopener noreferrer"&gt;shiguredo-webrtc-build/webrtc-build&lt;/a&gt; のビルド済みバイナリでは、全て OpenH264 を無効にして提供しています。&lt;/p&gt;

&lt;p&gt;その上で、Zakuro では Cisco がビルドして公開している OpenH264 バイナリを利用して H.264 エンコードを復活させたということになります。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;このような機能を入れて、無事 Zakuro はリリースされました。&lt;br&gt;
いろいろ面白いことができて楽しかったです。&lt;/p&gt;

&lt;p&gt;今後もいろいろと改善/開発をやっていく予定なので、Sora に負荷を掛けてみたいということがあれば使ってみてください。&lt;/p&gt;

</description>
      <category>webrtc</category>
    </item>
    <item>
      <title>Sora Unity SDK を iOS 対応した話</title>
      <dc:creator>melpon</dc:creator>
      <pubDate>Mon, 17 Aug 2020 03:54:20 +0000</pubDate>
      <link>https://dev.to/wandbox/sora-unity-sdk-ios-356m</link>
      <guid>https://dev.to/wandbox/sora-unity-sdk-ios-356m</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/shiguredo/sora-unity-sdk" rel="noopener noreferrer"&gt;Sora Unity SDK&lt;/a&gt; は &lt;a href="https://sora.shiguredo.jp/" rel="noopener noreferrer"&gt;WebRTC SFU Sora&lt;/a&gt; の Unity クライアントです。&lt;br&gt;
Sora Unity SDK は既に Windows と macOS と &lt;a href="https://dev.to/wandbox/sora-unity-sdk-android-1638"&gt;Android&lt;/a&gt; で動くようになっているのですが、今回新しく iOS に対応しました。&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1293518685143879681-105" src="https://platform.twitter.com/embed/Tweet.html?id=1293518685143879681"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1293518685143879681-105');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1293518685143879681&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;この記事では、この対応のために具体的にどんなことをしたのかについて書きます。&lt;/p&gt;

&lt;p&gt;iOS 対応の PR はこのあたりです。全体を見たい時に確認してください。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS 用 WebRTC を作る PR

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo-webrtc-build/webrtc-build/pull/6" rel="noopener noreferrer"&gt;iOS ビルドに対応 by melpon · Pull Request #6 · shiguredo-webrtc-build/webrtc-build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo-webrtc-build/webrtc-build/pull/7" rel="noopener noreferrer"&gt;iOS 用パッチを適用する by melpon · Pull Request #7 · shiguredo-webrtc-build/webrtc-build&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;それを使って Sora Unity SDK 本体を iOS に対応する PR

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/sora-unity-sdk/pull/9" rel="noopener noreferrer"&gt;iOS に対応 by melpon · Pull Request #9 · shiguredo/sora-unity-sdk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Sora Unity SDK のサンプル更新

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/sora-unity-sdk-samples/pull/6" rel="noopener noreferrer"&gt;Sora Unity SDK 2020.7 を利用する by torikizi · Pull Request #6 · shiguredo/sora-unity-sdk-samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  WebRTC の iOS ビルドを作る
&lt;/h2&gt;

&lt;p&gt;Sora Unity SDK を iOS で動かせるようにするには、まず iOS 用の WebRTC ライブラリが必要です。&lt;/p&gt;

&lt;p&gt;iOS 用の WebRTC ライブラリは、時雨堂の &lt;a href="https://github.com/shiguredo/shiguredo-webrtc-build" rel="noopener noreferrer"&gt;shiguredo/shiguredo-webrtc-build&lt;/a&gt; を置き換える目的もあるので WebRTC.framework を生成しています。&lt;/p&gt;

&lt;p&gt;WebRTC.framework は WebRTC のソースに入っている &lt;code&gt;tools_webrtc/ios/build_ios_libs.sh&lt;/code&gt; を叩くことで生成できます。こんな感じに利用します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  ./tools_webrtc/ios/build_ios_libs.sh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc &lt;span class="nt"&gt;--build_config&lt;/span&gt; release &lt;span class="nt"&gt;--arch&lt;/span&gt; &lt;span class="nv"&gt;$TARGET_ARCHS&lt;/span&gt; &lt;span class="nt"&gt;--bitcode&lt;/span&gt; &lt;span class="nt"&gt;--extra-gn-args&lt;/span&gt; &lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    rtc_libvpx_build_vp9=true &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    rtc_include_tests=false &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    rtc_build_examples=false &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    rtc_use_h264=false &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    use_rtti=true &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
    libcxx_abi_unstable=false &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  "&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで WebRTC.framework が生成できます。&lt;br&gt;
ただし、Unity ではこのライブラリは利用しません。代わりにいつものように自前で GN と ninja を叩いて libwebrtc.a を生成して利用します。&lt;/p&gt;

&lt;p&gt;これは他のライブラリと同じような感じでビルドするだけですが、iOS はユニバーサルライブラリが利用可能なので、x86_64 と ARM64 用のライブラリを 1 個の libwebrtc.a にまとめるところが違います。&lt;/p&gt;

&lt;p&gt;こんな感じのコードになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="c"&gt;# x86_64 用&lt;/span&gt;
    gn gen &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/x64_libs &lt;span class="nt"&gt;--args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"
      ... # いろいろ引数を入れる
    "&lt;/span&gt;
    ninja &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/x64_libs
    &lt;span class="c"&gt;# ar でまとめて x86_64 用の libwebrtc.a を生成&lt;/span&gt;
    &lt;span class="nb"&gt;pushd&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/x64_libs/obj
      ar &lt;span class="nt"&gt;-rc&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/x64_libs/libwebrtc.a &lt;span class="sb"&gt;`&lt;/span&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.o'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
    &lt;span class="nb"&gt;popd&lt;/span&gt;

    &lt;span class="c"&gt;# ARM64 用&lt;/span&gt;
    gn gen &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/arm64_libs &lt;span class="nt"&gt;--args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"
      ... # いろいろ引数を入れる
    "&lt;/span&gt;
    ninja &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/arm64_libs
    &lt;span class="c"&gt;# ar でまとめて ARM64 用の libwebrtc.a を生成&lt;/span&gt;
    &lt;span class="nb"&gt;pushd&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/arm64_libs/obj
      ar &lt;span class="nt"&gt;-rc&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/arm64_libs/libwebrtc.a &lt;span class="sb"&gt;`&lt;/span&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.o'&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
    &lt;span class="nb"&gt;popd&lt;/span&gt;

    &lt;span class="c"&gt;# x86_64 と ARM64 の libwebrtc.a をまとめてユニバーサルライブラリにする&lt;/span&gt;
    lipo &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/x64_libs/libwebrtc.a &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/arm64_libs/libwebrtc.a &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-create&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-output&lt;/span&gt; &lt;span class="nv"&gt;$BUILD_DIR&lt;/span&gt;/webrtc/libwebrtc.a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;また、iOS 用の WebRTC は、デフォルトの実装では受信専用の場合でも録音の権限を要求してしまうため、不要な権限の要求ということで Apple の審査に落ちることがあるようです。&lt;br&gt;
shiguredo/shiguredo-webrtc-build ではその問題を解決するためのパッチも用意されているので、そのパッチも当てています。&lt;/p&gt;

&lt;p&gt;詳細は &lt;a href="https://github.com/shiguredo/shiguredo-webrtc-build/blob/develop/docs/patch_shiguredo_ios.md" rel="noopener noreferrer"&gt;shiguredo-webrtc-build/patch_shiguredo_ios.md at develop · shiguredo/shiguredo-webrtc-build&lt;/a&gt; を参照して下さい。&lt;/p&gt;

&lt;p&gt;このパッチを当てているため、Sora Unity SDK の iOS 版は受信専用の場合にマイクの権限を要求しません。&lt;/p&gt;
&lt;h2&gt;
  
  
  Sora Unity SDK を iOS ビルドする
&lt;/h2&gt;

&lt;p&gt;これは普通に CMake に設定を追加するだけです。&lt;/p&gt;

&lt;p&gt;CMake は 3.14 以上なら iOS ビルドに&lt;a href="https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-ios-tvos-or-watchos" rel="noopener noreferrer"&gt;対応している&lt;/a&gt;ため、ちょっと設定をいくつか入れてやるだけです。&lt;/p&gt;
&lt;h2&gt;
  
  
  Sora Unity SDK のコードを iOS に対応する
&lt;/h2&gt;

&lt;p&gt;iOS 専用の処理をいくつか入れてやる必要があります。&lt;/p&gt;

&lt;p&gt;ただし、大体のコードは macOS と共通なので、そこまで変更する量は多くありません。&lt;/p&gt;
&lt;h3&gt;
  
  
  プラグインの登録を行う
&lt;/h3&gt;

&lt;p&gt;Windows, macOS, Android では、ライブラリのロード時に &lt;code&gt;UnityPluginLoad&lt;/code&gt; が、アンロード時に &lt;code&gt;UnityPluginUnload&lt;/code&gt; が呼ばれるという仕組みがあり、この時に &lt;code&gt;IUnityInterfaces&lt;/code&gt; を渡してもらうことで、Unity の機能をネイティブで触れるようになっています。&lt;/p&gt;

&lt;p&gt;しかし iOS は静的ライブラリなので、この関数をエクスポートしていても呼ばれることはありません。&lt;br&gt;
&lt;code&gt;IUnityInterfaces&lt;/code&gt; を渡してもらうには、&lt;code&gt;UnityRegisterRenderingPluginV5&lt;/code&gt; 関数を使ってロード時、アンロード時の関数を明示的に登録してやる必要があります。&lt;/p&gt;

&lt;p&gt;調べた感じだと、この関数は本来、Unity の iOS 用ビルドをして生成された &lt;code&gt;UnityAppController:shouldAttachRenderDelegate&lt;/code&gt; を実装して、その中で呼ぶのが良さそうなのですが、ここを弄るのは大変そうだったので、&lt;code&gt;Sora&lt;/code&gt; オブジェクトを生成する時に一度だけ &lt;code&gt;UnityRegisterRenderingPluginV5&lt;/code&gt; 関数を呼ぶようにしました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#if defined(SORA_UNITY_SDK_IOS)
&lt;/span&gt;&lt;span class="c1"&gt;// PlaybackEngines/iOSSupport/Trampoline/Classes/Unity/UnityInterface.h から必要な定義だけ拾ってきた&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="nf"&gt;void&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UnityPluginLoadFunc&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;IUnityInterfaces&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;unityInterfaces&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="nf"&gt;void&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;UnityPluginUnloadFunc&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;UnityRegisterRenderingPluginV5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UnityPluginLoadFunc&lt;/span&gt; &lt;span class="n"&gt;loadPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="n"&gt;UnityPluginUnloadFunc&lt;/span&gt; &lt;span class="n"&gt;unloadPlugin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;g_ios_plugin_registered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;UNITY_INTERFACE_API&lt;/span&gt; &lt;span class="nf"&gt;SoraUnitySdk_UnityPluginLoad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IUnityInterfaces&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ifs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;UNITY_INTERFACE_API&lt;/span&gt; &lt;span class="nf"&gt;SoraUnitySdk_UnityPluginUnload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sora_create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if defined(SORA_UNITY_SDK_IOS)
&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;g_ios_plugin_registered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;UnityRegisterRenderingPluginV5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;SoraUnitySdk_UnityPluginLoad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;SoraUnitySdk_UnityPluginUnload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;g_ios_plugin_registered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで問題なく動いているので多分大丈夫だと思います。&lt;/p&gt;

&lt;p&gt;ロード時、アンロード時の関数名を &lt;code&gt;UnityPluginLoad&lt;/code&gt; ではなく &lt;code&gt;SoraUnitySdk_UnityPluginLoad&lt;/code&gt; などとしているのは、iOS が静的ライブラリであるため、他のライブラリをリンクした際に &lt;code&gt;UnityPluginLoad&lt;/code&gt; という関数名が衝突する可能性があるからです。&lt;/p&gt;

&lt;h3&gt;
  
  
  C#, C++ 間のインターフェースから bool を除ける
&lt;/h3&gt;

&lt;p&gt;Sora Unity SDK を iOS の実機で動かしてみると、特定の C++ の関数を呼び出した時に、変な値が C++ に渡って落ちるという現象が起きました。&lt;br&gt;
調査してみると面白いことが分かったので書いておきます。&lt;/p&gt;

&lt;p&gt;C# から C++ のコードを呼び出すために P/Invoke を利用するわけですが、Unity iOS の場合はここはいい感じに IL2CPP で C++ に変換して呼んでくれます。&lt;/p&gt;

&lt;p&gt;C# 上で、&lt;code&gt;sora_connect&lt;/code&gt; 関数は 以下の extern 宣言になっています（引数が多いのは気にしなくて良いです）。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="cp"&gt;#if UNITY_IOS &amp;amp;&amp;amp; !UNITY_EDITOR
&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SoraUnitySdk"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;sora_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;unity_version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;signaling_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;channel_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;multistream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;capturer_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;unity_camera_texture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;video_capturer_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;video_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;video_height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;video_codec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;video_bitrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;unity_audio_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;unity_audio_output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;audio_recording_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;audio_playout_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;audio_codec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;audio_bitrate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この関数を呼ぶコードを書いて、Unity から iOS 用にビルドすると以下のような C++ コードが生成されます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;returnValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;reinterpret_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PInvokeFunc&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sora_connect&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;
  &lt;span class="n"&gt;___p0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____unity_version1_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____signaling_url2_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____channel_id3_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____metadata4_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____role5_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;___multistream6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;___capturer_type7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;___unity_camera_texture8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;____video_capturer_device9_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;___video_width10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;___video_height11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____video_codec12_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;___video_bitrate13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;___unity_audio_input14&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;___unity_audio_output15&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;____audio_recording_device16_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____audio_playout_device17_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;____audio_codec18_marshaled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;___audio_bitrate19&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PInvokeFunc&lt;/code&gt; は以下の typedef になっています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="nf"&gt;int32_t&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DEFAULT_CALL&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;PInvokeFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kt"&gt;intptr_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;intptr_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここでよく見ると、&lt;code&gt;sora_connect&lt;/code&gt; 関数にあった bool 型の引数が int32_t 型になっていることが分かります。&lt;/p&gt;

&lt;p&gt;C#(というか生成した C++ コード)側では、int32_t 型であるため4バイトのデータとしてスタックに積んで関数を呼び出しているのに、C++ 側では1バイトのデータとしてスタックから取り出している処理になってしまうわけです。&lt;br&gt;
そのため C++ 側の引数が変な値になってしまって落ちていました。&lt;/p&gt;

&lt;p&gt;実のところ、アライメントのおかげで bool を使ったら即落ちるという訳でもないのですが、今回に関しては bool が 2 個連続で並んでる引数 &lt;code&gt;bool unity_audio_input&lt;/code&gt; と &lt;code&gt;bool unity_audio_output&lt;/code&gt; があったので、ここで引数がずれてしまって落ちました。&lt;/p&gt;

&lt;p&gt;とにかく Unity の IL2CPP が bool を int32_t として扱うというのは変えようが無さそうなので、諦めて C++ のインターフェースとして bool を使うのはやめるようにしました。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;typedef int32_t unity_bool_t;&lt;/code&gt; と定義して、bool の部分を unity_bool_t に置き換えていったら、無事 &lt;code&gt;sora_connect&lt;/code&gt; を呼んでも落ちなくなりました。&lt;/p&gt;
&lt;h3&gt;
  
  
  ライブラリのリンク順序を入れ替える
&lt;/h3&gt;

&lt;p&gt;上記の対応で Sora Unity SDK の iOS 版は動くだろうと思ってたんですが、まだ問題がありました。&lt;br&gt;
H.264 ならハードウェアエンコーダを使って無事動くのに、VP8, VP9 だと動かないという現象が起きました。&lt;/p&gt;

&lt;p&gt;詳細にエラーを追っていくと、VP8, VP9 ソフトウェアエンコーダである libvpx のバージョンが古い、というエラーが出ていました。&lt;/p&gt;

&lt;p&gt;libvpx は libwebrtc.a に同封されていて、古いわけがありません。&lt;br&gt;
いろいろ調べた結果、Unity の iOS ビルドで必ず必要になる libiPhone-lib.a の中に、めちゃめちゃ古い libvpx が含まれていて、それが利用されていることが分かりました。&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1290223856221736960-943" src="https://platform.twitter.com/embed/Tweet.html?id=1290223856221736960"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1290223856221736960-943');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1290223856221736960&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;ググってみると、３年前に同じようなことで困ってる人が見つかりましたが、これは未回答のようでした。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://answers.unity.com/questions/1296768/unity-statically-links-libvpx-any-way-to-prevent-t.html" rel="noopener noreferrer"&gt;Unity statically links libvpx, any way to prevent this in a build? - Unity Answers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;なので頑張って調べた所、Xcode 用のプロジェクトを生成した後、&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc1z6oez36hgrr87sbgtw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc1z6oez36hgrr87sbgtw.png" alt="libiPhone-lib.a 入れ替え前"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;この libiPhone-lib.a を、&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9bqm91qk5dlfgy6d9gn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F9bqm91qk5dlfgy6d9gn2.png" alt="libiPhone-lib.a 入れ替え後"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;このように libwebrtc.a より後ろにしてやると、無事 libwebrtc.a の libvpx が使われるようになることが分かりました。&lt;/p&gt;

&lt;p&gt;ほんとにこんなのでいいのか？って思ったんですが、&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1290243855032266753-16" src="https://platform.twitter.com/embed/Tweet.html?id=1290243855032266753"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1290243855032266753-16');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1290243855032266753&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;ということらしいし、リンク先のをよく見てリンクのルールを考えると確かに問題無さそうなので安心です。&lt;/p&gt;

&lt;p&gt;あとはこれをプログラム上で実現するだけです。Unity なら以下のようなコードを書くだけで実現できました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SoraUnitySdkPostProcessor&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;PostProcessBuildAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnPostprocessBuild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildTarget&lt;/span&gt; &lt;span class="n"&gt;buildTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pathToBuiltProject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;projPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathToBuiltProject&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/Unity-iPhone.xcodeproj/project.pbxproj"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;PBXProject&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PBXProject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// 2020.3 より前か後かで処理が変わる&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_2019_3_OR_NEWER
&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;guid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUnityFrameworkTargetGuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;guid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TargetGuidByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unity-iPhone"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
        &lt;span class="c1"&gt;// ライブラリを追加したりとかのいろいろな設定&lt;/span&gt;

        &lt;span class="c1"&gt;// libiPhone-lib.a 削除して追加するとリンク順序が一番後ろに行く&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;fileGuid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindFileGuidByProjectPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Libraries/libiPhone-lib.a"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveFileFromBuild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileGuid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFileToBuild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fileGuid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;proj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteToFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで無事、VP8, VP9 での送受信に成功しました。&lt;/p&gt;

&lt;h3&gt;
  
  
  いろいろやる
&lt;/h3&gt;

&lt;p&gt;その他、ADM をワーカースレッドで作るようにしたり、カメラの回転に対応したり、マイク権限を要求しないパッチでうまく再生できないのを回避するためのコードを追加したり、Metal の一部機能が iOS で使えないので除けたりといったことをしました。&lt;/p&gt;

&lt;p&gt;ただ詳細に説明するほど面白いことは無かったので省略します。&lt;/p&gt;

&lt;h2&gt;
  
  
  感想
&lt;/h2&gt;

&lt;p&gt;当初の想定では、既に macOS で Metal にも対応してたし、ビデオエンコーダ/デコーダも Objective-C のコードをちょっと呼ぶだけというのは分かってたので、ほぼ何もしなくても動くんじゃないかと思っていました。&lt;/p&gt;

&lt;p&gt;が、上記の通り、実際にやってみると結構いろいろと罠を踏んで大変でした。でも無事動いて良かったです。&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>ios</category>
      <category>unity3d</category>
    </item>
    <item>
      <title>Sora Unity SDK を NVDEC に対応した話</title>
      <dc:creator>melpon</dc:creator>
      <pubDate>Sat, 20 Jun 2020 07:37:42 +0000</pubDate>
      <link>https://dev.to/wandbox/sora-unity-sdk-nvdec-2ieg</link>
      <guid>https://dev.to/wandbox/sora-unity-sdk-nvdec-2ieg</guid>
      <description>&lt;p&gt;Sora Unity SDK は &lt;a href="https://sora.shiguredo.jp/"&gt;WebRTC SFU Sora&lt;/a&gt; の Unity クライアントです。&lt;/p&gt;

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

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

&lt;p&gt;全体のコードは以下の PR にあります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/sora-unity-sdk/pull/3"&gt;Windows の NvDec に対応 by melpon · Pull Request #3 · shiguredo/sora-unity-sdk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C++ よりはビルド寄りの話です。&lt;/p&gt;

&lt;h2&gt;
  
  
  デコーダを実装する
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/NVIDIA/video-sdk-samples/blob/master/Samples/NvCodec/NvDecoder/NvDecoder.cpp"&gt;video-sdk-samples/NvDecoder.cpp at master · NVIDIA/video-sdk-samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight"&gt;&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NvDecoderCuda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
  &lt;span class="c1"&gt;// 初期化&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cuInit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cuDeviceGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cu_device_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;cuCtxCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cu_context_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cu_device_&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;decoder_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;NvDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cu_context_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cudaVideoCodec_H264&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// デコード&lt;/span&gt;
  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;**&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;frame_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;decoder_&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;frame_count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nl"&gt;private:&lt;/span&gt;
  &lt;span class="n"&gt;CUdevice&lt;/span&gt; &lt;span class="n"&gt;cu_device_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;CUcontext&lt;/span&gt; &lt;span class="n"&gt;cu_context_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;unique_ptr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NvDecoder&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;decoder_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

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

&lt;h2&gt;
  
  
  CUDA を使ったビルドに対応する
&lt;/h2&gt;

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

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

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

&lt;div class="highlight"&gt;&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="nb"&gt;enable_language&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;CUDA&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;set_source_files_properties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    NvCodec/NvCodec/NvDecoder/NvDecoder.cpp
    src/hwenc_nvcodec/nvcodec_video_decoder_cuda.cpp
  PROPERTIES
    &lt;span class="c1"&gt;# ファイル拡張子は .cpp だが C++ コンパイラではなく CUDA コンパイラを利用する&lt;/span&gt;
    LANGUAGE CUDA
    CUDA_SEPARABLE_COMPILATION ON
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

&lt;h3&gt;
  
  
  CUDA と WebRTC を混ぜない
&lt;/h3&gt;

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

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

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

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

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

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

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jlARbOF4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aqztyd3ie9d69faacgb5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jlARbOF4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/aqztyd3ie9d69faacgb5.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Buf02UH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bbztp3oqxv98or0bjpxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Buf02UH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bbztp3oqxv98or0bjpxx.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;この中に入っている &lt;code&gt;nvcc&lt;/code&gt; というディレクトリが欲しかったコンパイラです。&lt;br&gt;
わざわざインストーラを実行しなくても、この nvcc ディレクトリを持ってきて利用すれば動くはずです。&lt;/p&gt;
&lt;h3&gt;
  
  
  CMake を修正する
&lt;/h3&gt;

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

&lt;p&gt;そのため、GitHub Actions からの利用を考えた場合 &lt;code&gt;enable_language(CUDA)&lt;/code&gt; は使えません。&lt;/p&gt;

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

&lt;p&gt;以下のように利用します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight cmake"&gt;&lt;code&gt;&lt;span class="c1"&gt;# nvcc コンパイラの配置場所を設定する&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;CUDA_TOOLKIT_ROOT_DIR &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/_install/cuda/nvcc&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# FindCUDA を利用する&lt;/span&gt;
&lt;span class="nb"&gt;find_package&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;CUDA REQUIRED&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;set_source_files_properties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    NvCodec/NvCodec/NvDecoder/NvDecoder.cpp
    src/hwenc_nvcodec/nvcodec_video_decoder_cuda.cpp
  PROPERTIES
    CUDA_SOURCE_PROPERTY_FORMAT OBJ
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;cuda_compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;CUDA_FILES
    NvCodec/NvCodec/NvDecoder/NvDecoder.cpp
    src/hwenc_nvcodec/nvcodec_video_decoder_cuda.cpp
  OPTIONS
    &lt;span class="c1"&gt;# Visual Studio C++ コンパイラに渡す追加のコンパイラオプション&lt;/span&gt;
    -Xcompiler /utf-8
    -Xcompiler /I&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/NvCodec/include
    -Xcompiler /I&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CMAKE_CURRENT_SOURCE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/NvCodec/NvCodec
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;target_sources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;SoraUnitySdk PRIVATE &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CUDA_FILES&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



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

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

&lt;h2&gt;
  
  
  感想
&lt;/h2&gt;

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

</description>
    </item>
    <item>
      <title>Sora Unity SDK を Android 対応した話</title>
      <dc:creator>melpon</dc:creator>
      <pubDate>Thu, 11 Jun 2020 21:27:05 +0000</pubDate>
      <link>https://dev.to/wandbox/sora-unity-sdk-android-1638</link>
      <guid>https://dev.to/wandbox/sora-unity-sdk-android-1638</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/shiguredo/sora-unity-sdk"&gt;Sora Unity SDK&lt;/a&gt; は &lt;a href="https://sora.shiguredo.jp/"&gt;WebRTC SFU Sora&lt;/a&gt; の Unity クライアントです。&lt;br&gt;
Sora Unity SDK は既に Windows と macOS で動くようになっているのですが、今回新しく Android に対応しました。&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9cmwg4vg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/EZueloLUwAAHNLg.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--6WWGtQyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/439386851304947712/DQmJRMLr_normal.png" alt="std::めるぽん profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        std::めるぽん
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @melponn
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      無事 Sora Unity SDK を Android に対応した（画像は Sora Labo と繋いでるところ）。 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      05:51 AM - 05 Jun 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1268782398319149058" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1268782398319149058" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1268782398319149058" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;



&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--UAcKorAH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/858198338444836864/OFlImt8f_normal.jpg" alt="V profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        V
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        &lt;a class="mentioned-user" href="https://dev.to/voluntas"&gt;@voluntas&lt;/a&gt;
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      &lt;a href="https://t.co/dhak74Keh2"&gt;github.com/shiguredo/sora…&lt;/a&gt; Sora Unity SDK  2020.4 をリリースしました。こちら KDDI テクノロジー様からの優先実装依頼として Android 版への対応を行いました！ OSS へのご理解本当にありがとうございます！！！！
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      07:33 AM - 11 Jun 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1270982412902649858" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1270982412902649858" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1270982412902649858" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;この記事では、この対応のために具体的にどんなことをしたのかについて書きます。&lt;/p&gt;

&lt;p&gt;Android 対応の PR はこれです。全体を見たい時に確認してください。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo/sora-unity-sdk/pull/4"&gt;Android 対応 by melpon · Pull Request #4 · shiguredo/sora-unity-sdk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  WebRTC の Android ビルド
&lt;/h2&gt;

&lt;p&gt;Sora Unity SDK を Android で動かせるようにするには、まず Android 用の WebRTC ライブラリが必要です。&lt;/p&gt;

&lt;p&gt;WebRTC は既に Android で動作するようになっているので、これをビルドするだけで良いです。&lt;br&gt;
ただし、Android のライブラリは AAR になっているのに対して、ネイティブの実行に必要なのは &lt;code&gt;libwebrtc.a&lt;/code&gt; と jar ファイルなので、そのあたりをうまくやる必要があります。&lt;/p&gt;

&lt;p&gt;WebRTC のビルドは Sora Unity SDK ではなく、&lt;a href="https://github.com/shiguredo-webrtc-build/webrtc-build"&gt;shiguredo-webrtc-build/webrtc-build&lt;/a&gt; というリポジトリで管理していて、ここでネイティブの Android 向けの WebRTC に対応しました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shiguredo-webrtc-build/webrtc-build/pull/1"&gt;ネイティブ用 Android 対応 by melpon · Pull Request #1 · shiguredo-webrtc-build/webrtc-build&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;このビルド済みバイナリを Sora Unity SDK でダウンロードして利用する形になります。&lt;/p&gt;
&lt;h2&gt;
  
  
  Android のカメラに対応する
&lt;/h2&gt;

&lt;p&gt;WebRTC ライブラリには既に Android のカメラを利用してキャプチャする機能が既にあるので、これを Sora Unity SDK から利用するだけです。&lt;/p&gt;

&lt;p&gt;ただし、このカメラキャプチャの機能は Java で書かれています。Sora Unity SDK は C++ なので直接扱うのは出来ません。そこでどうするかというと、まあ JNI (Java Native Interface) ですね。&lt;/p&gt;
&lt;h3&gt;
  
  
  JNI を使ってカメラの映像をキャプチャする
&lt;/h3&gt;

&lt;p&gt;Android 用 WebRTC を使って Java でカメラを利用するには、以下のようなコードを書きます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Android のいつものコンテキスト&lt;/span&gt;
&lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UnityPlayer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;currentActivity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getApplicationContext&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// キャプチャ処理をやったりするスレッド&lt;/span&gt;
&lt;span class="nc"&gt;SurfaceTextureHelper&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SurfaceTextureHelper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"VideoCapturerThread"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// デバイスからキャプチャラを作る&lt;/span&gt;
&lt;span class="nc"&gt;Camera2Enumerator&lt;/span&gt; &lt;span class="n"&gt;enumerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Camera2Enumerator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;CameraVideoCapturer&lt;/span&gt; &lt;span class="n"&gt;videoCapturer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enumerator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createCapturer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enumerator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDeviceNames&lt;/span&gt;&lt;span class="o"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="cm"&gt;/* eventsHandler */&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// キャプチャ開始&lt;/span&gt;
&lt;span class="n"&gt;videoCapturer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initialize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capturerObserver&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;videoCapturer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;startCapture&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで毎フレーム &lt;code&gt;capturerObserver&lt;/code&gt; オブジェクトにコールバックがやってきます。&lt;/p&gt;

&lt;p&gt;これを C++ で再現すればうまく動くので、頑張って書くだけです。&lt;br&gt;
例えば &lt;code&gt;UnityPlayer.currentActivity.getApplicationContext()&lt;/code&gt; に相当する処理は以下のように書きます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ScopedJavaLocalRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jobject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetAndroidApplicationContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JNIEnv&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Context context = UnityPlayer.currentActivity.getApplicationContext()&lt;/span&gt;
  &lt;span class="c1"&gt;// を頑張って C++ から呼んでるだけ&lt;/span&gt;
  &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ScopedJavaLocalRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jclass&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;upcls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;GetClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"com/unity3d/player/UnityPlayer"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;jfieldID&lt;/span&gt; &lt;span class="n"&gt;actid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetStaticFieldID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upcls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"currentActivity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Landroid/app/Activity;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ScopedJavaLocalRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jobject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetStaticObjectField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upcls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;actid&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ScopedJavaLocalRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jclass&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;actcls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetObjectClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
  &lt;span class="n"&gt;jmethodID&lt;/span&gt; &lt;span class="n"&gt;ctxid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GetMethodID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actcls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"getApplicationContext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"()Landroid/content/Context;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;webrtc&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ScopedJavaLocalRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;jobject&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;CallObjectMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;ctxid&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;こんな感じのコードを大量に書いて何とかします。&lt;/p&gt;

&lt;p&gt;問題は Java から C++ へのコールバックを受け取る部分ですが、これは大変ありがたいことに、C++ のキャプチャコールバックを受け取るクラスである &lt;code&gt;webrtc::jni::AndroidVideoTrackSource&lt;/code&gt; を Java の &lt;code&gt;CapturerObserver&lt;/code&gt; に変換するための仕組みを WebRTC 側が&lt;a href="https://source.chromium.org/chromium/chromium/src/+/master:third_party/webrtc/sdk/android/src/jni/native_capturer_observer.cc;l=21-26"&gt;用意してくれている&lt;/a&gt;ので、それを利用するだけで C++ 側にコールバックが渡ってくれるようになります。&lt;/p&gt;

&lt;h3&gt;
  
  
  JAR ファイルを設定する
&lt;/h3&gt;

&lt;p&gt;Java のコードを利用するということは、class ファイルを Unity のビルド時に含んでやる必要があるということです。&lt;br&gt;
幸いなことに Unity には JAR ファイルを Android に組み込む機能が入っている(&lt;a href="https://docs.unity3d.com/ja/2020.1/Manual/AndroidJARPlugins.html"&gt;JAR プラグイン - Unity マニュアル&lt;/a&gt;)ため、上記の「WebRTC の Android ビルド」で作った Android 用 WebRTC に入っている jar ファイルをそのまま持ってくるだけで良いです。&lt;/p&gt;
&lt;h3&gt;
  
  
  Java から C++ にコールバックを渡るようにする
&lt;/h3&gt;

&lt;p&gt;この状態で普通に &lt;code&gt;libSoraUnitySdk.so&lt;/code&gt; を作って実機で動かしてみたのですが、すぐにクラッシュしました。&lt;br&gt;
ログを追いかけてみると、Java 側から &lt;code&gt;Java_org_webrtc_Histogram_nativeAddSample&lt;/code&gt; みたいな C++ の関数を呼ぼうとして、その関数が見つからなくてクラッシュしているようでした。&lt;/p&gt;

&lt;p&gt;この関数は &lt;code&gt;libwebrtc.a&lt;/code&gt; には存在しているのですが、それをリンクした &lt;code&gt;libSoraUnitySdk.so&lt;/code&gt; には含まれていませんでした。&lt;br&gt;
それも当然で、C++ のコードからは &lt;code&gt;Java_org_webrtc_Histogram_nativeAddSample&lt;/code&gt; 関数を一切呼んでいないため、リンク時にこの関数が取り除かれてしまうのです。&lt;br&gt;
なのでこの Java から呼び出している関数に関しては &lt;code&gt;libSoraUnitySdk.so&lt;/code&gt; に含めてやるようにしました。&lt;/p&gt;

&lt;p&gt;具体的には、リンカーに渡すオプションとして &lt;code&gt;-Wl,--undefined=Java_org_webrtc_Histogram_nativeAddSample&lt;/code&gt; を追加しました。&lt;br&gt;
&lt;code&gt;--undefined&lt;/code&gt; を使うことで、未使用の関数であってもライブラリに含められます。&lt;/p&gt;

&lt;p&gt;ただし Java から呼び出す C++ 関数はこれだけではないため、これらの関数の一覧を列挙してオプションを作ってやる必要があります。&lt;br&gt;
こういうシンボルを見るためのツールが Android NDK にあるので、それを利用して libwebrtc.a の関数を列挙し、&lt;code&gt;Java_org_webrtc_&lt;/code&gt; から始まる関数を取り出してオプションに加工しています。&lt;/p&gt;

&lt;p&gt;具体的には以下のようなコードになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Android 側からのコールバックする関数は消してはいけないので、&lt;/span&gt;
&lt;span class="c"&gt;# libwebrtc.a の中から消してはいけない関数の一覧を作っておく&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;$INSTALL_DIR&lt;/span&gt;/android/webrtc.ldflags &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="c"&gt;# readelf を使って libwebrtc.a の関数一覧を列挙して、その中から Java_org_webrtc_ を含む関数を取り出し、&lt;/span&gt;
  &lt;span class="c"&gt;# -Wl,--undefined=&amp;lt;関数名&amp;gt; に加工する。&lt;/span&gt;
  &lt;span class="c"&gt;# （-Wl,--undefined はアプリケーションから参照されていなくても関数を削除しないためのフラグ）&lt;/span&gt;
  &lt;span class="nv"&gt;_READELF&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$INSTALL_DIR&lt;/span&gt;/android-ndk/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android-readelf
  &lt;span class="nv"&gt;_LIBWEBRTC_A&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$INSTALL_DIR&lt;/span&gt;/android/webrtc/lib/arm64-v8a/libwebrtc.a
  &lt;span class="nv"&gt;$_READELF&lt;/span&gt; &lt;span class="nt"&gt;-Ws&lt;/span&gt; &lt;span class="nv"&gt;$_LIBWEBRTC_A&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    | &lt;span class="nb"&gt;grep &lt;/span&gt;Java_org_webrtc_ &lt;span class="se"&gt;\&lt;/span&gt;
    | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;a b c d e f g h&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-Wl&lt;/span&gt;,--undefined&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$INSTALL_DIR&lt;/span&gt;/android/webrtc.ldflags
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで動かすと、無事カメラ映像のキャプチャが出来るようになりました。&lt;/p&gt;

&lt;h2&gt;
  
  
  Unity カメラのキャプチャに対応する
&lt;/h2&gt;

&lt;p&gt;Sora Unity SDK には、Unity カメラが写している映像をキャプチャする機能が用意されています。&lt;br&gt;
つまりこういうことです。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UeRKGc5h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.gyazo.com/3368cdbcb9aa32625e8463379988cfe8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UeRKGc5h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://i.gyazo.com/3368cdbcb9aa32625e8463379988cfe8.gif" alt="" width="668" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://torikizi.hatenablog.jp/entry/2020/06/09/173201"&gt;Sora Unity SDK サンプル集を動かしてみたfor Android - torikiziのブログ&lt;/a&gt; より&lt;/p&gt;

&lt;p&gt;仕組みとしては、Unity カメラがレンダリングしているテクスチャを指すポインタを C++ 側に渡して、それをゴニョゴニョっとして映像データを取り出すようになっています。&lt;br&gt;
このゴニョゴニョする部分が大変なところで、Windows なら Direct3D 11 を使ったり、macOS なら Metal を使ったり、今回の Android なら Vulkan を使ったりして映像データを取り出します（Android は OpenGLES の場合もあるんですが、そちらは今回対応していません）。&lt;/p&gt;

&lt;p&gt;Unity は、ネイティブで Unity のテクスチャを扱えるようにするための仕組みを用意してくれていて、導入に関しては以下のドキュメントに書かれています。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/ja/2020.1/Manual/NativePluginInterface.html"&gt;低レベルネイティブプラグインインターフェース - Unity マニュアル&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これを利用するのと、あとは Vulkan のドキュメントを読みながら書くだけです。&lt;/p&gt;

&lt;h3&gt;
  
  
  Unity テクスチャのポインタ
&lt;/h3&gt;

&lt;p&gt;実際書いてみてハマった点としては、「Unity から渡されるテクスチャへのポインタは、Vulkan のテクスチャを指すわけではない」というのがあります。&lt;/p&gt;

&lt;p&gt;Unity から渡されるテクスチャへのポインタは、例えば Windows(D3D11) なら &lt;code&gt;ID3D11Resource*&lt;/code&gt; のことだったり、macOS(Metal) なら &lt;code&gt;id&amp;lt;MTLTexture&amp;gt;&lt;/code&gt; のことだったりしました。&lt;br&gt;
なので Android の場合も Vulkan 用のイメージである &lt;code&gt;VkImage&lt;/code&gt; だと思っていたのですが、全然違っていました。&lt;/p&gt;

&lt;p&gt;Android の場合、Unity から渡されたテクスチャへのポインタは自前のデータ構造になっているようで、この構造の実際の定義は公開されていないようです。&lt;br&gt;
そしてこの不透明なデータ構造の内容を展開するための関数として &lt;code&gt;IUnityGraphicsVulkan::AccessTexture&lt;/code&gt; というのがあり、この関数に Unity から渡されたテクスチャへのポインタを渡すと &lt;code&gt;UnityVulkanImage&lt;/code&gt; というのを返してくれるようになっています。&lt;/p&gt;

&lt;p&gt;これで得られた &lt;code&gt;UnityVulkanImage&lt;/code&gt; を使って、Vulkan の関数を色々と呼び出して何とかして映像データを取り出します。&lt;br&gt;
具体的な処理は &lt;a href="https://github.com/shiguredo/sora-unity-sdk/blob/469a326213c3b8a7eb4a202c5b0030c9103bc113/src/unity_camera_capturer_vulkan.cpp"&gt;sora-unity-sdk/unity_camera_capturer_vulkan.cpp at 469a326213c3b8a7eb4a202c5b0030c9103bc113 · shiguredo/sora-unity-sdk&lt;/a&gt; にあるので、気になった人は見てください。&lt;/p&gt;

&lt;h2&gt;
  
  
  ハードウェアエンコーダに対応する
&lt;/h2&gt;

&lt;p&gt;Android 用の WebRTC は大変素晴らしくて、ハードウェアエンコーダに対応しています。ただし Java コードで。&lt;br&gt;
&lt;code&gt;DefaultVideoEncoderFactory&lt;/code&gt; というクラスは、出来る限りハードウェアを利用し、利用できるハードウェアがなければソフトウェアにフォールバックするという実装になっています。&lt;/p&gt;

&lt;p&gt;ということで &lt;code&gt;DefaultVideoEncoderFactory&lt;/code&gt; オブジェクトを C++ から生成して、それを C++ 用の &lt;code&gt;video_encoder_factory&lt;/code&gt; に設定するだけです。&lt;/p&gt;

&lt;h2&gt;
  
  
  カメラデバイスの列挙に対応する
&lt;/h2&gt;

&lt;p&gt;Android にはフロントカメラとバックカメラの２つが付いている機種があったりするので、その辺を列挙するための機能があると便利です。&lt;br&gt;
Android 用の WebRTC は大変素晴らしくて、このカメラデバイスの列挙に対応しています。ただし Java コードで。&lt;br&gt;
&lt;code&gt;Camera2Enumerator&lt;/code&gt; というクラス（以下略）&lt;/p&gt;

&lt;h2&gt;
  
  
  感想
&lt;/h2&gt;

&lt;p&gt;ということで、振り返ってみると大体はビルド周りと JNI 周りをうまく整備してやる感じで、あとは Unity テクスチャと Vulkan について調べて書く程度しかやっていません。&lt;/p&gt;

&lt;p&gt;ただ、Android も JNI も Vulkan もあまり触ったことが無いので結構大変でした。&lt;br&gt;
ひたすら動かなくて、Android のログを眺めてトライアルアンドエラーを繰り返しまくってました。何とか無事動いて良かったです。&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>android</category>
      <category>unity3d</category>
    </item>
  </channel>
</rss>
