# 音声の送信と受信

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

## 音声送信と受信の提供機能について

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

- デバイスマイクを音声送信に利用する
- Unity 内で生成した音声を送信する
- 音声送信を一時停止する (ソフトミュート)
- 録音デバイスを利用しない (ハードミュート)
- 利用可能な録音・再生デバイス一覧を取得する
- 受信した音声を任意の方法で再生する
- 音声コーデックやビットレートを指定する
- ハンズフリー切り替えなど再生経路を制御する

### 利用時の注意事項

- `Sora.OnHandleAudio` は Unity スレッドとは別スレッドで呼び出されます。バッファ操作時はロックなどで同期を取ってください
- `Sora.DispatchEvents()` を呼び出さなくても `Sora.OnHandleAudio` は発火します
- `Sora.ProcessAudio()` は 48000Hz Stereo のデータのみ受け取ることができます
- `Config.UnityAudioInput` を `true` にしている場合、`NoAudioDevice` を `true` にしても Unity 側の音声入力は継続します

## 音声関連の API リファレンス

### Sora.Config

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

- `Audio`: 音声トラックを有効にするかどうか。`false` にすると停止します
- `NoAudioDevice`: 物理マイクデバイスを利用しない場合に `true` を設定します
- `UnityAudioInput`: Unity で生成した音声を `Sora.ProcessAudio()` から送信する場合に `true` を設定します
- `UnityAudioOutput`: OS の再生デバイスではなく `Sora.OnHandleAudio` コールバックで音声を受け取りたい場合に `true` を設定します
- `AudioRecordingDevice`: 利用する録音デバイスの `DeviceName` または `UniqueName` (空文字ならデフォルトデバイス)
- `AudioPlayoutDevice`: 利用する再生デバイスの `DeviceName` または `UniqueName` (空文字ならデフォルトデバイス)
- `AudioCodecType`: 利用する音声コーデックを指定します (現状は `Sora.AudioCodecType.OPUS` のみ)
- `AudioBitRate`: 音声のビットレート (kbps)。省略時は Sora のデフォルト値になります
- `AudioStreamingLanguageCode`: 字幕用途などで利用する言語コードを指定します

### Sora クラスの音声関連 API

- `Sora.Connect(Sora.Config config)`: 指定した設定で接続します
- `Sora.ProcessAudio(float[] data, int offset, int samples)`: `UnityAudioInput` 利用時に送信したいサンプルを渡します
- `Sora.OnHandleAudio`: `UnityAudioOutput` 利用時に受信音声を取得するコールバック
- `Sora.AudioEnabled`: ソフトミュートのためのトグルプロパティ
- `Sora.GetAudioRecordingDevices()` / `Sora.GetAudioPlayoutDevices()`: 利用可能な録音・再生デバイス一覧を `Sora.DeviceInfo[]` で取得します (列挙に失敗した場合は `null`)
- `Sora.OnMediaStreamTrack`: 受信したトラックを取得するコールバック。音声トラックの場合は `AudioTrack` と `connectionId` を取得できます。`track.Kind` で音声か映像かを判別できます
- `Sora.OnRemoveMediaStreamTrack`: トラックの削除時に通知されます
- `Sora.DispatchEvents()`: メインスレッドでイベントを処理します `OnHandleAudio` 以外のコールバックでは呼び出しが必要です

#### Sora.AudioTrack クラスの API

- `SetVolume(double volume)`: このトラックの再生音量を設定します (0.0〜10.0)

> **注釈**
>
> 音声データを直接取得する機能は、[音声データ取得機能](functions_audio_sink.html) を参照してください。

## 利用方法

### 接続と送受信設定

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

```csharp
Sora sora; // 初期化済みの Sora インスタンス
var signalingUrl = "wss://example.com/signaling"; // シグナリング URL
var channelId = "sora"; // チャンネル ID
var unityAudioInput = false; // true なら Unity 内で生成した音声を送信
var unityAudioOutput = false; // true なら再生を Unity 側で処理
var disableDevice = false; // true なら NoAudioDevice を有効化
var recordingDevice = ""; // 空文字ならデフォルト録音デバイス
var playoutDevice = ""; // 空文字ならデフォルト再生デバイス

var config = new Sora.Config()
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    Role = Sora.Role.Sendrecv,
    Audio = true, // 音声送信を有効化
    NoAudioDevice = disableDevice, // 録音デバイスを利用しない場合は true
    UnityAudioInput = unityAudioInput, // Unity 生成音声を送信する場合に true
    UnityAudioOutput = unityAudioOutput, // Unity 側で再生する場合に true
    AudioRecordingDevice = recordingDevice, // 利用する録音デバイス
    AudioPlayoutDevice = playoutDevice, // 利用する再生デバイス
};

sora.Connect(config);
```

### 利用可能なオーディオデバイスの列挙

`Sora.GetAudioRecordingDevices()` と `Sora.GetAudioPlayoutDevices()` を使うと利用可能なデバイスを取得できます。取得結果をログに出力する例です。

```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)
    {
        // デバイス情報をログに出力
        Debug.Log($"{label}: DeviceName={device.DeviceName}, UniqueName={device.UniqueName}");
    }
}

void Start()
{
    DumpDeviceInfo("audio recording devices", Sora.GetAudioRecordingDevices());
    DumpDeviceInfo("audio playout devices", Sora.GetAudioPlayoutDevices());
}
```

### 音声コーデックとビットレートの設定

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

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

`Sora.Config.AudioCodecType` を使用して利用する音声コーデックを指定できます。省略した場合は Sora サーバーのデフォルト値が使用されます (現状 `OPUS` のみ)。

```csharp
var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    AudioCodecType = Sora.AudioCodecType.OPUS, // コーデックを指定
};

sora.Connect(config);
```

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

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

```csharp
var config = new Sora.Config
{
    SignalingUrl = signalingUrl,
    ChannelId = channelId,
    AudioBitRate = 64, // 音声のビットレート (kbps) を指定
};

sora.Connect(config);
```

### Unity 音声入力を送信する場合

Unity で生成した音声を送信する場合は `UnityAudioInput` を `true` にし、`Sora.ProcessAudio()` にフレームデータを渡します。

以下は `AudioRenderer` を利用して 1 フレーム分のサンプルを送る例です。

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

IEnumerator RenderAudio()
{
    // Unity 内で音を鳴らしておくとキャプチャ状況を確認しやすい
    audioSourceInput.Play();
    // オーディオレンダラーを開始
    AudioRenderer.Start();

    while (true)
    {
        // Sora インスタンスが存在し、Unity 音声入力が有効な場合のみ処理する
        if (sora == null || !unityAudioInput) continue;
        // 1 フレーム分のサンプル数を取得する
        var samples = AudioRenderer.GetSampleCountForCaptureFrame();
        // ステレオモードでない場合はスキップ
        if (AudioSettings.speakerMode != AudioSpeakerMode.Stereo) continue;

        // ステレオ (2 チャンネル) 分のバッファを確保
        using (var buf = new Unity.Collections.NativeArray<float>(samples * 2, Unity.Collections.Allocator.Temp))
        {
            // オーディオデータをレンダリング
            AudioRenderer.Render(buf);
            // Sora 経由で音声データを送信
            sora.ProcessAudio(buf.ToArray(), 0, samples);
        }
    }
}
```

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

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

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

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

ハードミュートは接続時に `Sora.Config.NoAudioDevice` を `true` に設定します。送信中に切り替えることはできないため、設定を変える場合は再接続が必要です。

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

sora.Connect(config);
```

> **警告**
>
> `UnityAudioInput` が `true` の場合、`NoAudioDevice` を `true` にしても Unity からの音声入力は継続します。

### Unity 内で受信音声を再生する場合

`Config.UnityAudioOutput` を `true` にすると、受信音声が `Sora.OnHandleAudio` でコールバックされます。以下はキューを使って `AudioClip` に流し込む例です。

```csharp
// 受信した音声データを保持するキュー
Queue<short[]> audioBuffer = new Queue<short[]>();
// キューに格納されているサンプル数の合計
int audioBufferSamples = 0;
// 現在読み取り中のチャンク内の位置
int audioBufferPosition = 0;

void SetupAudioOutput()
{
    // 音声データ受信時のコールバックを設定
    sora.OnHandleAudio = (buf, samples, channels) =>
    {
        lock (audioBuffer)
        {
            // 受信したバッファをキューに追加
            audioBuffer.Enqueue(buf);
            // サンプル数を加算
            audioBufferSamples += samples;
        }
    };

    // 複数の受信者がいる場合は、重複にならないよう "Remote_{connectionId}" などを使用してください
    // 48000Hz Stereo のデータのみ受け取ることができます
    var clip = AudioClip.Create("Remote", 480000, 1, 48000, true, data =>
    {
        lock (audioBuffer)
        {
            // バッファが空、またはデータ不足の場合は無音を返す
            if (audioBuffer.Count == 0 || audioBufferSamples < data.Length)
            {
                Array.Fill(data, 0.0f);
                return;
            }

            // キューの先頭チャンクを取得
            var chunk = audioBuffer.Peek();
            // 要求されたデータ長分のサンプルを取り出す
            for (int i = 0; i < data.Length; ++i)
            {
                // 現在のチャンクを読み終えた場合、次のチャンクへ移動
                while (audioBufferPosition >= chunk.Length)
                {
                    audioBuffer.Dequeue();
                    chunk = audioBuffer.Peek();
                    audioBufferPosition = 0;
                }
                // short 型 (±32768) から float 型 (±1.0) に変換
                data[i] = chunk[audioBufferPosition] / 32768.0f;
                ++audioBufferPosition;
            }
            // 処理したサンプル数を減算
            audioBufferSamples -= data.Length;
        }
    });

    // AudioClip を AudioSource にセットして再生開始
    audioSourceOutput.clip = clip;
    audioSourceOutput.Play();
}
```

### ハンズフリー制御 (`Sora.IAudioOutputHelper`)

`Sora.AudioOutputHelperFactory.Create()` で `IAudioOutputHelper` を生成すると、ハンズフリー切り替えなどの再生経路制御が可能になります。詳細は [ハンズフリー](functions_iaudiooutputhelper.html) も参照してください。

```csharp
Sora.IAudioOutputHelper audioOutputHelper;

void InitAudioOutputHelper()
{
    // ルート変更時に呼ばれるコールバックを指定して生成
    audioOutputHelper = Sora.AudioOutputHelperFactory.Create(OnChangeRoute);
}

void OnChangeRoute()
{
    if (audioOutputHelper == null) return;

    // ルート変更を検知して処理を行う
    Debug.Log("音声ルートが変更されました: " +
              (audioOutputHelper.IsHandsfree() ? "ハンズフリー ON" : "ハンズフリー OFF"));
}

public void OnClickHandsfree()
{
    if (audioOutputHelper == null) return;
    audioOutputHelper.SetHandsfree(!audioOutputHelper.IsHandsfree());
}
```
