# 映像の送信と受信

Sora Unity SDK で映像を送信・受信する際に必要となる設定や API について説明します。

デバイスカメラ・Unity カメラの切り替え、ミュート操作、受信映像の描画までを確認できるようになっています。

## 映像送信と受信の提供機能について

Sora Unity SDK では以下を提供しています。

- デバイスカメラを映像送信に利用する
- Unity カメラを映像送信に利用する
- 任意の `Texture` を映像送信に利用する
- 映像送信を一時停止する (ソフトミュート)
- カメラデバイスを利用しない (ハードミュート)
- 利用可能なカメラデバイス一覧を取得する
- 受信した映像を任意のテクスチャに描画する
- 映像コーデックの指定をする
- ハードウェアアクセラレーションの利用を指定する

### プラットフォームと制限

- `Sora.OnCapturerFrame` は Android では利用できません
- iOS や macOS ではカメラアクセス権限が無い場合、デバイス列挙や接続処理が失敗するため権限を付与したうえで利用してください
- Unity カメラ送信や任意のテクスチャを送信する場合は `RenderTextureFormat.BGRA32` が必要となるため、プロジェクト設定で BGRA32 を有効にしてください

## 映像関連の API リファレンス

### Sora.Config

`Sora.Config` クラスは Sora インスタンスの接続設定を定義します。映像送信に関する主なプロパティは以下の通りです。

- `Video`: 映像トラックの送信を有効にするかどうか。`false` にすると映像送信を停止します
- `NoVideoDevice`: 物理カメラデバイスを利用しない場合に `true` を設定します
- `VideoBitRate`: 映像送信のビットレート (kbps)。省略時は接続する Sora のデフォルト値になります
- `CameraConfig`: 使用するカメラソースをまとめた `Sora.CameraConfig` のインスタンス
- `VideoCodecType`: 利用する映像コーデックを指定します。省略時は Sora のデフォルト値になります
- `VideoCodecImplementation`: 利用する映像エンコーダー / デコーダーを指定します。省略時は Internal (libwebrtc 標準) になります
- `VideoVp9Params`: VP9 コーデックのパラメーターを JSON 文字列で指定します。空文字や不正な値の場合は `INVALID-MESSAGE` エラーになります。
- `VideoAv1Params`: AV1 コーデックのパラメーターを JSON 文字列で指定します。空文字や不正な値の場合は `INVALID-MESSAGE` エラーになります。
- `VideoH264Params`: H.264 コーデックのパラメーターを JSON 文字列で指定します。空文字や不正な値の場合は `INVALID-MESSAGE` エラーになります。

### Sora.CameraConfig

`Sora.CameraConfig` クラスはカメラソースを定義します。プロパティは以下の通りです。

- `CapturerType`: カメラの種類を指定します (`DeviceCamera` / `UnityCamera` / `Texture` のいずれかでデフォルトは `DeviceCamera`)
- `UnityCamera`: Unity カメラを送信する場合に指定します
- `UnityCameraRenderTargetDepthBuffer`: Unity カメラのレンダーターゲットに確保する深度バッファビット数を指定します (デフォルトは `16`)
- `VideoCapturerDevice`: 利用する物理カメラの `DeviceName` もしくは `UniqueName` を指定します(空文字の場合はデフォルトデバイスを利用します)
- `VideoWidth`: 映像の幅を指定します(デフォルトは `640`)
- `VideoHeight`: 映像の高さを指定します(デフォルトは `480`)
- `VideoFps`: 映像のフレームレートを指定します(デフォルトは `30`)
- `Texture`: `RenderTexture` を送信する場合に指定します

#### Sora.CameraConfig のヘルパーメソッド

`Sora.CameraConfig` には設定生成を簡単にするヘルパーメソッドが用意されています。

各プロパティを個別に設定する代わりに、これらのメソッドを使うことで適切な設定のインスタンスを簡単に作成できます。

物理カメラを使用する場合:

`Sora.CameraConfig.FromDeviceCamera(string videoCapturerDevice, int videoWidth, int videoHeight, int videoFps)`

端末の内蔵カメラ、外付け Web カメラなどの物理カメラを送信ソースとして設定します。

- `videoCapturerDevice`: `Sora.GetVideoCapturerDevices()` で取得したデバイスの `DeviceName` または `UniqueName` を指定
- `videoWidth`, `videoHeight`: 送信する映像の解像度（例：1280 x 720）
- `videoFps`: 送信するフレームレート（例：30 fps）

Unity カメラを使用する場合:

`Sora.CameraConfig.FromUnityCamera(UnityEngine.Camera unityCamera, int unityCameraRenderTargetDepthBuffer, int videoWidth, int videoHeight, int videoFps)`

Unityシーン内のカメラが映している内容を送信ソースとして設定します。ゲーム画面やCG映像を配信したい場合に使用します。

- `unityCamera`: 送信したい Unity の Camera コンポーネント（例： `Camera.main` ）
- `unityCameraRenderTargetDepthBuffer`: RenderTexture 深度バッファビット数（通常は16または24）
- `videoWidth`, `videoHeight`: 送信する映像の解像度
- `videoFps`: 送信するフレームレート

Texture を使用する場合:

`Sora.CameraConfig.FromTexture(UnityEngine.Texture texture, int videoFps)`

Unity で生成・管理している任意の `Texture` を送信ソースとして指定します。

- `texture`: 送信したい `Texture` （ `RenderTexture` 推奨）
- `videoFps`: 送信するフレームレート

ヘルパーメソッドを利用すると `CapturerType` に応じた必要な設定を利用することができます。

### Sora クラスの映像関連 API

- `Sora.Connect(Sora.Config config)`: 指定した設定で接続します
- `Sora.VideoEnabled`: ソフトミュートのためのトグルプロパティ
- `Sora.SwitchCamera(Sora.CameraConfig cameraConfig)`: 接続中に送信カメラを切り替えます。`NoVideoDevice` の設定に依存せず呼び出します
- `Sora.OnRender()`: Unity カメラを利用する場合に `WaitForEndOfFrame` 後に呼び出してフレームを送信します
- `Sora.OnCapturerFrame`: キャプチャ直後のフレームを受け取るコールバック。別スレッドで実行されます
- `Sora.GetVideoCapturerDevices()`: 利用可能なカメラデバイス一覧を `Sora.DeviceInfo[]` で取得します (列挙に失敗した場合は `null` が返ります)
- `Sora.OnAddTrack` / `Sora.OnRemoveTrack`: 受信映像トラックの追加・削除時に通知されます
- `Sora.GetVideoTrackFromVideoSinkId(uint videoSinkId)`: `videoSinkId` から対応する `VideoTrack` を取得します。指定した `videoSinkId` に対応する `VideoTrack` が見つからない場合は例外をスローします
- `Sora.VideoTrack`: 映像トラックを表します。 `Sora.GetVideoTrackFromVideoSinkId()` や `Sora.OnMediaStreamTrack` で取得できます- `VideoTrack.GetVideoSinkId(Sora sora)`: この `VideoTrack` に対応する `videoSinkId` を取得します
- `Sora.RenderTrackToTexture(uint videoSinkId, Texture texture)`: `videoSinkId` で受信した映像を `texture` にレンダリングします。送信中の場合、自身の `videoSinkId` を指定すれば送信映像もレンダリングできます
- `Sora.DispatchEvents()`: メインスレッドでイベントを処理するために `Update()` などから呼び出します

## 利用方法

### 接続と送信設定

以下は基本的な送信設定の例です。例では既に初期化済みの `Sora` インスタンス (`sora`) を利用しています。

```csharp
// Inspector などで取得した設定値をもとに送信設定を組み立てる例
Sora sora; // 初期化済みの Sora インスタンス
var signalingUrl = "wss://example.com/signaling"; // シグナリング URL
var channelId = "sora"; // チャンネル ID
var selectedDevice = "camera_unique_name"; // 利用するデバイスカメラ名
Camera capturedCamera = Camera.main; // Unity カメラ送信時に利用するカメラ
var renderTargetDepth = 16; // RenderTexture の深度バッファビット数
int videoWidth = 1280; // 映像の幅
int videoHeight = 720; // 映像の高さ
int videoFps = 30; // 映像のフレームレート
int videoBitRate = 1500; // 映像のビットレート (kbps)
var useUnityCamera = false; // true なら Unity カメラを送信
var disableDevice = false; // true なら NoVideoDevice を有効化

var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    Role = Sora.Role.Sendrecv,
    Video = true, // 映像送信を有効化
    NoVideoDevice = disableDevice, // 物理カメラデバイスを利用しない場合は true
    VideoBitRate = videoBitRate, // 送信ビットレート (kbps)
};

// 物理カメラか Unity カメラかを判定してカメラソースを設定
// 使用するカメラに応じて、ヘルパーメソッドで CameraConfig を生成します
// useUnityCamera が true かつ capturedCamera が null でない場合は Unity カメラを使用します
// それ以外の場合、物理カメラデバイスを使用 (Web カメラなどの実カメラを送信)します
config.CameraConfig = useUnityCamera && capturedCamera != null
    ? Sora.CameraConfig.FromUnityCamera(capturedCamera, renderTargetDepth, videoWidth, videoHeight, videoFps)
    : Sora.CameraConfig.FromDeviceCamera(selectedDevice, videoWidth, videoHeight, videoFps);

sora.Connect(config);
```

### 利用可能なカメラデバイスの列挙

`Sora.GetVideoCapturerDevices()` を使うと利用可能なカメラデバイスを取得できます。

戻り値は `Sora.DeviceInfo[]` で、列挙が失敗した場合は `null` が返ります。

取得したデバイス名、UniqueName を使って `Sora.CameraConfig.VideoCapturerDevice` に設定できます。

ここでは取得結果をログに出力する例を示します。

```csharp
void DumpDeviceInfo(string label, Sora.DeviceInfo[] devices)
{
    if (devices == null || devices.Length == 0)
    {
        // デバイスが見つからない場合の処理
        Debug.LogWarning($"{label}: device not found");
        return;
    }

    foreach (var device in devices)
    {
        // DeviceName と UniqueName をログに出力
        Debug.Log($"{label}: DeviceName={device.DeviceName}, UniqueName={device.UniqueName}");
    }
}

void Start()
{
    // 例として起動時にデバイス一覧を出力
    DumpDeviceInfo("video capturer devices", Sora.GetVideoCapturerDevices());
}
```

### 映像コーデックとビットレートの設定

映像品質に影響する主要な設定について説明します。

#### コーデックの指定

`Sora.Config.VideoCodecType` を使用して利用する映像コーデックを指定できます。省略した場合は Sora サーバーのデフォルト値が使用されます。

利用可能なコーデック:

- `Sora.VideoCodecType.VP9`
- `Sora.VideoCodecType.VP8`
- `Sora.VideoCodecType.H264`
- `Sora.VideoCodecType.AV1`
- `Sora.VideoCodecType.H265`

コーデックを利用する前に、クライアントがそのエンコーダ／デコーダを利用可能か `Sora.GetVideoCodecCapability()` で確認してください。

`Encoder / Decoder` が `true` でないコーデックを指定すると、接続処理や切断処理でクラッシュする恐れがあります。

#### コーデックパラメーターの指定

各コーデックには詳細なパラメーターを設定できます。

##### VP9 のパラメーター

`Sora.Config.VideoVp9Params` で VP9 コーデックのプロファイル ID を JSON 文字列として指定できます。

```csharp
// SoraSample.cs を参考にした VP9 のパラメーターを設定するサンプルコード
// enableVideoVp9Params が true の場合に VP9 パラメーターを設定します

// VP9 パラメーターを設定する場合有効にする
public bool enableVideoVp9Params = false;
// VP9 パラメーターを設定 0, 1, 2, 3 が利用可能
public int videoVp9ParamsProfileId;

// VP9 パラメーターを設定する場合は JSON 文字列を設定する
string videoVp9ParamsJson = "";
if (enableVideoVp9Params)
{
    var vp9Params = new VideoVp9Params()
    {
        profile_id = videoVp9ParamsProfileId
    };
    videoVp9ParamsJson = JsonUtility.ToJson(vp9Params);
}

var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    VideoCodecType = Sora.VideoCodecType.VP9,
    VideoVp9Params = videoVp9ParamsJson,
};
```

##### AV1 のパラメーター

`Sora.Config.VideoAv1Params` で AV1 コーデックのプロファイルを JSON 文字列として指定できます。

```csharp
// SoraSample.cs を参考にした AV1 のパラメーターを設定するサンプルコード
// enableVideoAv1Params が true の場合に AV1 パラメーターを設定します

// AV1 パラメーターを設定する場合有効にする
public bool enableVideoAv1Params = false;
// AV1 パラメーターを設定 0, 1, 2 が利用可能
public int videoAv1ParamsProfile;

// AV1 パラメーターを設定する場合は JSON 文字列を設定する
string videoAv1ParamsJson = "";
if (enableVideoAv1Params)
{
    var av1Params = new VideoAv1Params()
    {
        profile = videoAv1ParamsProfile
    };
    videoAv1ParamsJson = JsonUtility.ToJson(av1Params);
}

var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    VideoCodecType = Sora.VideoCodecType.AV1,
    VideoAv1Params = videoAv1ParamsJson,
};
```

##### H.264 のパラメーター

`Sora.Config.VideoH264Params` で H.264 コーデックのプロファイルレベル ID を JSON 文字列として指定できます。

```csharp
// SoraSample.cs を参考にした H.264 のパラメーターを設定するサンプルコード
// enableVideoH264Params が true の場合に H.264 パラメーターを設定します

// H.264 パラメーターを設定する場合有効にする
public bool enableVideoH264Params = false;
// H.264 パラメーターを設定
// 例: "42e01f" はレベル 3.1
public string videoH264ParamsProfileLevelId = "";

// H.264 パラメーターを設定する場合は JSON 文字列を設定する
string videoH264ParamsJson = "";
if (enableVideoH264Params)
{
    var h264Params = new VideoH264Params()
    {
        profile_level_id = videoH264ParamsProfileLevelId
    };
    videoH264ParamsJson = JsonUtility.ToJson(h264Params);
}

var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    VideoCodecType = Sora.VideoCodecType.H264,
    VideoH264Params = videoH264ParamsJson,
};
```

#### ビットレートの設定

`Sora.Config.VideoBitRate` で送信ビットレート（kbps）を指定できます。省略した場合は Sora サーバーのデフォルト値が使用されます。

```csharp
var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    VideoCodecType = Sora.VideoCodecType.H264, // コーデックを指定
    VideoBitRate = 1500, // ビットレート(kbps)を指定
};

sora.Connect(config);
```

`VideoCodecImplementation` を使用してエンコーダー/デコーダーの実装を指定することも可能です。

詳細は [エンコーダー / デコーダーの指定](functions_hardware_acceleration.html) を参照してください。

### Unity カメラを送信する場合

Unity カメラを映像送信に利用する場合の設定と実装方法を説明します。

#### 設定方法

Unity カメラを使用する場合は `CameraConfig` で `CapturerType.UnityCamera` を指定し、送信したいカメラを設定します。

```csharp
var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    CameraConfig = new Sora.CameraConfig()
    {
        CapturerType = Sora.CapturerType.UnityCamera,
        UnityCamera = capturedCamera, // 送信したいCamera
        VideoFps = 30,
        VideoWidth = 1280,
        VideoHeight = 720,
    },
};
```

または、ヘルパーメソッドを使用することもできます：

```csharp
config.CameraConfig = Sora.CameraConfig.FromUnityCamera(capturedCamera, 16, 1280, 720, 30);
```

#### フレーム送信の実装

Unity カメラを送信する際は `WaitForEndOfFrame` の最後で `Sora.OnRender()` を呼び出します。

この処理はコルーチンとして実装し、`Start()` などで開始してください。

```csharp
void Start()
{
    StartCoroutine(Render());
}

IEnumerator Render()
{
    while (true)
    {
        yield return new WaitForEndOfFrame(); // Unity の描画完了を待機
        if (sora != null)
        {
            // OnRender() は必ず WaitForEndOfFrame の後に呼び出すこと
            sora.OnRender(); // Unity カメラの映像フレームを Sora に転送
        }
    }
}
```

### Texture を送信する場合

`CapturerType.Texture` を使うと、任意の `Texture` を映像トラックとして送信できます。ポストプロセス済みのゲーム画面やコンピュートシェーダーの結果などを配信したい場合に有効です。

> **注釈**
>
> `Texture2D` なども指定できますが、ピクセルを書き換えるたびに Unity が内部テクスチャを作り直し、以前に取得した `GetNativeTexturePtr()` が無効になります。
> Sora に渡したポインタが切れてしまうため、動的に更新する場合は `RenderTexture` を Sora に渡し、`Graphics.Blit(Texture2D, RenderTexture)` などで内容を反映させる運用を推奨します。

#### 設定例

```csharp
// 生成した RenderTexture を Sora に送信し、かつ画面に表示するサンプル
// 事前に RenderTexture を用意していない場合でも自動で生成します

Sora sora;
// RenderTexture を送る(Inspector で割り当て可。未設定なら生成)
public RenderTexture captureTargetTexture;
// captureTargetTexture の内容を画面表示する Texture
public Texture2D stagingTexture;

InitSora();

GetVideoSize(videoSize, out var videoWidth, out var videoHeight);

// RenderTexture 未割り当てなら生成
if (captureTargetTexture == null)
{
    captureTargetTexture = new RenderTexture(videoWidth, videoHeight, 16, RenderTextureFormat.BGRA32);
    captureTargetTexture.Create();
}
else if (!captureTargetTexture.IsCreated())
{
    captureTargetTexture.Create();
}

// stagingTexture もサイズを揃える
if (stagingTexture == null ||
    stagingTexture.width != captureTargetTexture.width ||
    stagingTexture.height != captureTargetTexture.height)
{
    stagingTexture = new Texture2D(captureTargetTexture.width, captureTargetTexture.height, TextureFormat.BGRA32, false);
}

// stagingTexture を更新して RenderTexture に反映(内容を書き換えた直後に呼ぶ)
stagingTexture.Apply(false, false);
Graphics.Blit(stagingTexture, captureTargetTexture);

// RenderTexture を送る設定
var cameraConfig = Sora.CameraConfig.FromTexture(captureTargetTexture, videoFps);

var config = new Sora.Config()
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    CameraConfig = cameraConfig,
};
sora.Connect(config);

// 毎フレーム更新したい場合は Update() で
void Update()
{
    if (stagingTexture != null && captureTargetTexture != null)
    {
        stagingTexture.Apply(false, false);
        Graphics.Blit(stagingTexture, captureTargetTexture);
    }
}
```

### カメラの切り替え

接続中にデバイスカメラとUnityカメラを切り替える場合は `SwitchCamera()` を使用します。

```csharp
// クラスのフィールドとして定義
Sora sora;
public Camera capturedCamera;
public string videoCapturerDevice = "";

public void SwitchToUnityCamera()
{
    if (sora == null) return;
    sora.SwitchCamera(Sora.CameraConfig.FromUnityCamera(capturedCamera, 16, 1280, 720, 30));
}

public void SwitchToDeviceCamera()
{
    if (sora == null) return;
    sora.SwitchCamera(Sora.CameraConfig.FromDeviceCamera(videoCapturerDevice, 1280, 720, 30));
}
```

### ソフトミュートとハードミュート

ミュートにはデバイスを維持するソフトミュートと、デバイス自体を利用しないハードミュートの 2 種類があります。

ソフトミュートは `Sora.VideoEnabled` を切り替えて行います。この場合カメラデバイスは解放されません。

```csharp
public void ToggleVideo()
{
    if (sora == null) return;
    sora.VideoEnabled = !sora.VideoEnabled;
}
```

カメラデバイスを利用しないハードミュートは接続時に `Sora.Config.NoVideoDevice` を `true` に設定することで行います。

> **警告**
>
> 送信中ハードウェアミュートに切り替えることはできず、再接続をする必要があります。

`Sora.SwitchCamera()` を呼び出すとカメラを取得しに行くため、ハードミュート中は呼び出さないでください。

意図せずカメラを取得してミュートが解除されることがあります。

```csharp
var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    NoVideoDevice = true, // 物理カメラデバイスを利用しない
};

sora.Connect(config);
```

### 映像受信の処理

受信した映像トラックは `OnAddTrack` / `OnRemoveTrack` で管理し、`RenderTrackToTexture` で描画します。

`connectionId` が空文字の場合は自身の映像です。

この例は SoraUnitySdkExamples の SoraSample.cs にもありますのでそちらも参考にしてください。

```csharp
// 受信映像トラックを管理するためのDictionary
Dictionary<string, GameObject> tracks = new Dictionary<string, GameObject>();

// 映像トラックが追加された時のコールバック
sora.OnAddTrack = (videoSinkId, connectionId) =>
{
    // UI要素を生成(baseContentはプリファブとして事前に用意)
    // Hierarchy に Canvas を作成して RawImage を対象にすると簡単です
    var obj = Instantiate(baseContent);
    obj.name = $"track {videoSinkId}";
    obj.transform.SetParent(scrollViewContent.transform);
    obj.SetActive(true);

    // 映像を描画するためのテクスチャを生成
    var image = obj.GetComponent<UnityEngine.UI.RawImage>();
    image.texture = new Texture2D(320, 240, TextureFormat.RGBA32, false);

    // トラック管理用のDictionaryに追加
    tracks[videoSinkId] = obj;
};

// 映像トラックが削除された時のコールバック
sora.OnRemoveTrack = (videoSinkId, connectionId) =>
{
    if (!tracks.TryGetValue(videoSinkId, out var obj)) return;

    // テクスチャとGameObjectを適切に破棄
    var image = obj.GetComponent<UnityEngine.UI.RawImage>();
    Destroy(image.texture);
    Destroy(obj);
    tracks.Remove(videoSinkId);
};

void Update()
{
    if (sora == null) return;

    // 受信イベントをメインスレッドで処理(必須)
    sora.DispatchEvents();

    // 各トラックの映像をテクスチャに描画
    foreach (var pair in tracks)
    {
        var image = pair.Value.GetComponent<UnityEngine.UI.RawImage>();
        // 受信映像をテクスチャに描画(毎フレーム呼び出す)
        sora.RenderTrackToTexture(pair.Key, image.texture);
    }
}
```
