> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# リアルタイム音声操作

> Game Chat 2 のボイスストリームをリアルタイムで検査およびオーディオエフェクト適用するためのウォークスルー。XBOX GDK タイトル内で音声操作パイプラインを使用します。

このトピックでは、リアルタイム音声操作の使用について簡単に説明します。

Game Chat 2 は、チャットオーディオパイプラインに自分自身を挿入して、ユーザーのチャット音声データを検査および操作するオプションを提供します。
これは、ゲーム内でユーザーの音声に面白い音声エフェクトを適用する場合に便利です。

Game Chat 2 では、音声操作パイプラインは音声データをポーリングできる音声ストリームオブジェクトを介して操作されます。
コールバックを使用するのとは対照的に、このモデルを使用することで、自分にとって最も都合の良い任意の処理スレッドで音声を検査または操作できます。

## 音声操作パイプラインの初期化

Game Chat 2 はデフォルトではリアルタイム音声操作を有効にしません。
有効にするには、アプリは [chat\_manager::initialize](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_initialize) で `audioManipulationMode` パラメーターを設定して、どの形式の音声操作を有効にしたいかを指定する必要があります。

現在、次の音声操作の形式がサポートされており、[game\_chat\_audio\_manipulation\_mode\_flags](/reference/chat/gamechat2/enums/game_chat_audio_manipulation_mode_flags) 列挙型に含まれています。

* `game_chat_audio_manipulation_mode_flags::none`: 音声操作を無効化します。これはデフォルトの構成です。このモードでは、チャット音声は中断されずに流れます。
* `game_chat_audio_manipulation_mode_flags::pre_encode_stream_manipulation`: エンコード前の音声操作を有効化します。このモードでは、ローカルユーザーによって生成されたすべてのチャット音声はエンコードされる前に音声操作パイプラインに送られます。アプリがチャット音声データを検査するだけで操作しない場合でも、エンコードされ送信できるように、変更されていない音声バッファを Game Chat 2 に送り返す責任があります。
* `game_chat_audio_manipulation_mode_flags::post_decode_stream_manipulation`: デコード後の音声操作を有効化します。このモードでは、リモートユーザーから受信されたすべてのチャット音声は、受信側でデコードされた後、レンダリングされる前に音声操作パイプラインに送られます。アプリがチャット音声データを検査するだけで操作しない場合でも、レンダリングできるように、変更されていない音声バッファをミックスして Game Chat 2 に送り返す責任があります。

## 音声ストリーム状態変化の処理

Game Chat 2 は、[game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体を通じて音声ストリームの状態への更新を提供します。
これらの更新は、どのストリームが更新されたか、およびどのように更新されたかについての情報を格納します。

これらの更新は、[chat\_manager::start\_processing\_stream\_state\_changes()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_start_processing_stream_state_changes) および [chat\_manager::finish\_processing\_stream\_state\_changes()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_finish_processing_stream_state_changes) の一対のメソッドを呼び出すことでポーリングできます。それらは、最新のキューに入っているすべての音声ストリーム状態の更新を [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体ポインターの配列として提供します。アプリは配列を反復処理し、各更新を適切に処理する必要があります。

利用可能なすべての [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 更新が処理された後、その配列は `chat_manager::finish_processing_stream_state_changes()` を介して Game Chat 2 に戻す必要があります。
これは次の例に示されています。

```cpp theme={null}
uint32_t streamStateChangeCount;
game_chat_stream_state_change_array streamStateChanges;
chat_manager::singleton_instance().start_processing_stream_state_changes(&streamStateChangeCount, &streamStateChanges);

for (uint32_t streamStateChangeIndex = 0; streamStateChangeIndex < streamStateChangeCount; ++streamStateChangeIndex)
{
    switch (streamStateChanges[streamStateChangeIndex]->state_change_type)
    {
        case game_chat_stream_state_change_type::pre_encode_audio_stream_created:
        {
            HandlePreEncodeAudioStreamCreated(streamStateChanges[streamStateChangeIndex].pre_encode_audio_stream);
            break;
        }

        case Xs::game_chat_2::game_chat_stream_state_change_type::pre_encode_audio_stream_closed:
        {
            HandlePreEncodeAudioStreamClosed(streamStateChanges[streamStateChangeIndex].pre_encode_audio_stream);
            break;
        }

        ...
    }
}
chat_manager::singleton_instance().finish_processing_stream_state_changes(streamStateChanges);
```

## エンコード前チャット音声データの操作

Game Chat 2 は、[pre\_encode\_audio\_stream](/reference/chat/gamechat2/classes/pre_encode_audio_stream/pre_encode_audio_stream) クラスを介してローカルユーザーのエンコード前のチャット音声データへのアクセスを提供します。

### ストリームのライフタイム

新しい [pre\_encode\_audio\_stream](/reference/chat/gamechat2/classes/pre_encode_audio_stream/pre_encode_audio_stream) インスタンスがアプリで使用できるようになると、`state_change_type` フィールドが `game_chat_stream_state_change_type::pre_encode_audio_stream_created` に設定された [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体を通じて配信されます。
このストリーム状態変化が Game Chat 2 に戻された後、音声ストリームはエンコード前音声操作に利用可能になります。

既存の [pre\_encode\_audio\_stream](/reference/chat/gamechat2/classes/pre_encode_audio_stream/pre_encode_audio_stream) が音声操作に使用できなくなると、アプリは `state_change_type` フィールドが `game_chat_stream_state_change_type::pre_encode_audio_stream_closed` に設定された [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体を通じて通知されます。
これは、この音声ストリームに関連付けられたリソースのクリーンアップを開始するアプリのチャンスです。
このストリーム状態変化が Game Chat 2 に戻された後、音声ストリームはエンコード前音声操作に利用できなくなります。

閉じられた [pre\_encode\_audio\_stream](/reference/chat/gamechat2/classes/pre_encode_audio_stream/pre_encode_audio_stream) がすべてのリソースを返した後、ストリームは破棄され、アプリは `state_change_type` フィールドが `game_chat_stream_state_change_type::pre_encode_audio_stream_destroyed` に設定された [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体を通じて通知されます。
このストリームへの参照またはポインターはクリーンアップする必要があります。
このストリーム状態変化が Game Chat 2 に戻された後、音声ストリームメモリは無効になります。

### ストリームユーザー

ストリームに関連付けられたユーザーのリストは、[pre\_encode\_audio\_stream::get\_users()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_get_users) を使用して検査できます。

### 音声フォーマット

アプリが Game Chat 2 から取得するバッファの音声フォーマットは、[pre\_encode\_audio\_stream::get\_pre\_processed\_format()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_get_pre_processed_format) を使用して検査できます。前処理される音声フォーマットはモノラルです。
アプリは、32 ビット浮動小数点、16 ビット整数、および 32 ビット整数として表されるデータを処理する必要があります。

アプリは、エンコードおよび送信のために送信される操作済みバッファの音声フォーマットを、[pre\_encode\_audio\_stream::set\_processed\_format()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_set_processed_format) を使用して Game Chat 2 に通知する必要があります。エンコード前音声ストリームの処理済みフォーマットは、次の前提条件を満たす必要があります。

* フォーマットはモノラルである必要があります。
* フォーマットは、32 ビット浮動小数点 Performance Monitor カウンター (PCM)、32 ビット整数 PCM、または 16 ビット整数 PCM フォーマットである必要があります。
* フォーマットのサンプルレートは、プラットフォームに基づく前提条件に従う必要があります。XBOX One ERA および XBOX Series X|S は 8 KHz、12 KHz、16 KHz、および 24 KHz のサンプルレートをサポートします。XBOX One 用ユニバーサル Windows プラットフォーム (UWP) および Windows PC は、8 KHz、12 KHz、16 KHz、24 KHz、32 KHz、44.1 KHz、および 48 KHz のサンプルレートをサポートします。

### エンコード前音声の取得と送信

アプリは、[pre\_encode\_audio\_stream::get\_available\_buffer\_count()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_get_available_buffer_count) を使用して、処理できる利用可能なバッファの数についてエンコード前音声ストリームに問い合わせできます。この情報は、アプリが最小限のバッファ数が利用可能になるまで音声処理を遅延させたい場合に使用できます。

各エンコード前音声ストリームでキューに入れられるのは 10 バッファのみで、音声の遅延は音声パイプラインにレイテンシを導入します。アプリは、4 バッファ以上をキューに入れる前にエンコード前音声ストリームをドレインすることをお勧めします。

#### get\_next\_buffer() での音声バッファの取得

アプリは、[pre\_encode\_audio\_stream::get\_next\_buffer()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_get_next_buffer) を使用してエンコード前音声ストリームから音声バッファを取得できます。
新しい音声バッファは、平均して 40 ms ごとに 1 回利用可能になります。

このメソッドが返すバッファは、使用が終わったときに [pre\_encode\_audio\_stream::return\_buffer()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_return_buffer) に解放する必要があります。

エンコード前音声ストリームでは、任意の時点で最大 10 個のキューに入れられた、または返却されていないバッファが存在できます。
この制限に達すると、未処理のバッファの一部が返却されるまで、ユーザーの音声ソースからキャプチャされる新しいバッファはドロップされます。

#### submit\_buffer() での音声バッファの送信

アプリは、[pre\_encode\_audio\_stream::submit\_buffer()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_submit_buffer) を使用して、検査および操作した音声バッファを Game Chat 2 にエンコードと送信のために送り返せます。Game Chat 2 はインプレースおよびアウトオブプレースの音声操作をサポートしています。`pre_encode_audio_stream::submit_buffer()` に送信されるバッファは、必ずしも `pre_encode_audio_stream::get_next_buffer()` から取得されたバッファと同じである必要はありません。

これらの送信されたバッファのプライバシー/権限は、このストリームに関連付けられたユーザーに基づいて適用されます。
40 ms ごとに、このストリームからの次の 40 ms の音声がエンコードおよび送信されます。

音声の途切れを防ぐには、継続的に聞かれるべき音声のバッファを一定のレートでこのストリームに送信する必要があります。

### ストリームコンテキスト

アプリは、[pre\_encode\_audio\_stream::set\_custom\_stream\_context()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_set_custom_stream_context) および [pre\_encode\_audio\_stream::custom\_stream\_context()](/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_custom_stream_context) を使用して、エンコード前音声ストリームでカスタムのポインターサイズのコンテキスト値を管理できます。これらのカスタムストリームコンテキストは、Game Chat 2 の音声ストリームと補助データ (たとえば、ストリームメタデータやゲーム状態) 間のマッピングを作成するのに役立ちます。

### 例

以下は、1 つの音声処理フレームでエンコード前音声ストリームを使用する方法を示す簡略化されたエンドツーエンドサンプルです。

```cpp theme={null}
uint32_t streamStateChangeCount;
game_chat_stream_state_change_array streamStateChanges;
chat_manager::singleton_instance().start_processing_stream_state_changes(&streamStateChangeCount, &streamStateChanges);

for (uint32_t streamStateChangeIndex = 0; streamStateChangeIndex < streamStateChangeCount; ++streamStateChangeIndex)
{
    switch (streamStateChanges[streamStateChangeIndex]->state_change_type)
    {
        case game_chat_stream_state_change_type::pre_encode_audio_stream_created:
        {
            pre_encode_audio_stream* stream = streamStateChanges[streamStateChangeIndex]->pre_encode_audio_stream;
            stream->set_processed_audio_format(...);
            stream->set_custom_stream_context(...);
            HandlePreEncodeAudioStreamCreated(stream);
            break;
        }

        case game_chat_2::game_chat_stream_state_change_type::pre_encode_audio_stream_closed:
        {
            HandlePreEncodeAudioStreamClosed(streamStateChanges[streamStateChangeIndex].pre_encode_audio_stream);
            break;
        }

        case game_chat_2::game_chat_stream_state_change_type::pre_encode_audio_stream_destroyed:
        {
            HandlePreEncodeAudioStreamDestroyed(streamStateChanges[streamStateChangeIndex].pre_encode_audio_stream);
            break;
        }

        ...
    }
}
chat_manager::singleton_instance().finish_processing_stream_state_changes(streamStateChanges);

uint32_t preEncodeAudioStreamCount;
pre_encode_audio_stream_array preEncodeAudioStreams;
chat_manager::singleton_instance().get_pre_encode_audio_streams(&preEncodeAudioStreamCount, &preEncodeAudioStreams);
for (uint32_t preEncodeAudioStreamIndex = 0; preEncodeAudioStreamIndex < preEncodeAudioStreamCount; ++preEncodeAudioStreamIndex)
{
    pre_encode_audio_stream* stream = preEncodeAudioStreams[preEncodeAudioStreamIndex];
    StreamContext* context = reinterpret_cast<StreamContext*>(stream->custom_stream_context());

    game_chat_audio_format audio_format = stream->get_pre_processed_format();

    uint32_t preProcessedBufferByteCount;
    void* preProcessedBuffer;
    stream->get_next_buffer(&preProcessedBufferByteCount, &preProcessedBuffer);

    while (preProcessedBuffer != nullptr)
    {
        void* processedBuffer = nullptr;
        switch (audio_format.bits_per_sample)
        {
            case 16:
            {
                assert (audio_format.sample_type == game_chat_sample_type::integer);
                processedBuffer = ManipulateChatBuffer<int16_t>(preProcessedBufferByteCount, preProcessedBuffer, context);
                break;
            }

            case 32:
            {
                switch (audio_format.sample_type)
                {
                    case game_chat_sample_type::integer:
                    {
                        processedBuffer = ManipulateChatBuffer<int32_t>(preProcessedBufferByteCount, preProcessedBuffer, context);
                        break;
                    }

                    case game_chat_sample_type::ieee_float:
                    {
                        processedBuffer = ManipulateChatBuffer<float>(preProcessedBufferByteCount, preProcessedBuffer, context);
                        break;
                    }

                    default:
                    {
                        assert(false);
                        break;
                    }
                }
                break;
            }

            default:
            {
                assert(false);
                break;
            }
        }
        // processedBuffer can be the same as preProcessedBuffer (in-place manipulation) or it can be a buffer of
        // memory not managed by Game Chat 2 (out-of-place manipulation).
        stream->submit_buffer(processedBuffer);
        // Only return buffers retrieved from Game Chat 2. Don't return foreign memory to return_buffer.
        stream->return_buffer(preProcessedBuffer);
        stream->get_next_buffer(&preProcessedBufferByteCount, &preProcessedBuffer);
    }
}

Sleep(audioProcessingPeriodInMilliseconds);
```

## デコード後チャット音声データの操作

Game Chat 2 は、[post\_decode\_audio\_source\_stream](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) および [post\_decode\_audio\_sink\_stream](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) クラスを介してデコード後のチャット音声データへのアクセスを提供します。これは、ユーザーが各ローカルのチャット音声受信者ごとにリモートユーザーからの音声を独自に操作できることを意味します。

### ソースとシンク

エンコード前パイプラインとは異なり、デコード後の音声データを扱うモデルは、[post\_decode\_audio\_source\_stream](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) と [post\_decode\_audio\_sink\_stream](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) の 2 つのクラスに分割されています。

リモートユーザーからのデコードされた音声は [post\_decode\_audio\_source\_stream](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) オブジェクトから取得され、操作されて、レンダリングのために [post\_decode\_audio\_sink\_stream](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) オブジェクトに送信されます。
これにより、Game Chat 2 のデコード後音声処理パイプラインと役立つ音声ミドルウェアとの統合が可能になります。

### ストリームのライフタイム

新しい [post\_decode\_audio\_source\_stream](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) または [post\_decode\_audio\_sink\_stream](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) インスタンスがアプリで使用できるようになると、それぞれ `state_change_type` フィールドが `game_chat_stream_state_change_type::post_decode_audio_source_stream_created` または `game_chat_stream_state_change_type::post_decode_audio_sink_stream_created` に設定された [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体を通じて配信されます。
このストリーム状態変化が Game Chat 2 に戻された後、音声ストリームはデコード後音声操作に利用可能になります。

既存の [post\_decode\_audio\_source\_stream](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) または [post\_decode\_audio\_sink\_stream](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) が音声操作に使用できなくなると、アプリは、それぞれ `state_change_type` フィールドが `game_chat_stream_state_change_type::post_decode_audio_source_stream_closed` または `game_chat_stream_state_change_type::post_decode_audio_sink_stream` に設定された [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体を通じて通知されます。
これは、この音声ストリームに関連付けられたリソースのクリーンアップを開始するアプリのチャンスです。
このストリーム状態変化が Game Chat 2 に戻された後、音声ストリームはデコード後音声操作に利用できなくなります。
ソースストリームの場合、これは操作用にバッファがキューに入らなくなることを意味します。
シンクストリームの場合、これは送信されたバッファがもうレンダリングされないことを意味します。

閉じられた [post\_decode\_audio\_source\_stream](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) または [post\_decode\_audio\_sink\_stream](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) がすべてのリソースを返した後、ストリームは破棄され、アプリはそれぞれ `state_change_type` フィールドが `game_chat_stream_state_change_type::post_decode_audio_source_stream_destroyed` または `game_chat_stream_state_change_type::post_decode_audio_sink_stream_destroyed` に設定された [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 構造体を通じて通知されます。
このストリームへの参照またはポインターはクリーンアップする必要があります。
このストリーム状態変化が Game Chat 2 に戻された後、音声ストリームメモリは無効になります。

### ストリームユーザー

デコード後ソースストリームに関連付けられたリモートユーザーのリストは、[post\_decode\_audio\_source\_stream::get\_users()](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_users) を使用して検査できます。

デコード後シンクストリームに関連付けられたローカルユーザーのリストは、[post\_decode\_audio\_sink\_stream::get\_users()](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/methods/post_decode_audio_sink_stream_get_users) を使用して検査できます。

### 音声フォーマット

アプリが Game Chat 2 から取得するバッファの音声フォーマットは、[post\_decode\_audio\_source\_stream::get\_pre\_processed\_format()](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_pre_processed_format) を使用して検査できます。前処理された音声フォーマットは常にモノラルの 16 ビット整数 PCM です。

アプリは、レンダリングのために送信される操作済みバッファの音声フォーマットを、[post\_decode\_audio\_sink\_stream::set\_processed\_format()](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/methods/post_decode_audio_sink_stream_set_processed_format) を使用して Game Chat 2 に通知する必要があります。デコード後音声シンクストリームの処理済みフォーマットは、次の前提条件を満たす必要があります。

* フォーマットのチャンネル数は 64 未満である必要があります。
* フォーマットは、16 ビット整数 PCM (最適)、20 ビット整数 PCM (24 ビットコンテナ内)、24 ビット整数 PCM、32 ビット整数 PCM、または 32 ビット浮動小数点 PCM (16 ビット整数 PCM の次に推奨されるフォーマット) である必要があります。
* フォーマットのサンプルレートは、1,000 から 200,000 サンプル/秒の範囲である必要があります。

### エンコード後音声の取得と送信

アプリは、[post\_decode\_audio\_source\_stream::get\_available\_buffer\_count()](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_available_buffer_count) を使用して、処理できる利用可能なバッファの数についてデコード後音声ソースストリームに問い合わせできます。
この情報は、アプリが最小限のバッファ数が利用可能になるまで音声処理を遅延させたい場合に使用できます。
各デコード後音声ソースストリームでキューに入れられるのは 10 バッファのみで、音声の遅延は音声パイプラインにレイテンシを導入します。アプリは、4 バッファ以上をキューに入れる前にデコード後音声ストリームをドレインすることをお勧めします。

#### get\_next\_buffer() での音声バッファの取得

アプリは、[post\_decode\_audio\_source\_stream::get\_next\_buffer()](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_next_buffer) を使用してデコード後音声ソースストリームから音声バッファを取得できます。新しい音声バッファは、平均して 40 ms ごとに 1 回利用可能になります。

このメソッドが返すバッファは、使用が終わったときに [post\_decode\_audio\_source\_stream::return\_buffer()](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_return_buffer) に解放する必要があります。

デコード後音声ソースストリームでは、任意の時点で最大 10 個のキューに入れられた、または返却されていないバッファが存在できます。
この制限に達すると、未処理のバッファの一部が返却されるまで、リモートユーザーからの新しいデコードされたバッファはドロップされます。

#### submit\_buffer() での音声バッファの送信

アプリは、[post\_decode\_audio\_sink\_stream::submit\_mixed\_buffer()](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/methods/post_decode_audio_sink_stream_submit_mixed_buffer) を使用して、検査および操作したバッファをデコード後音声シンクストリームを通じてレンダリングのために Game Chat 2 に送り返せます。
Game Chat 2 はインプレースおよびアウトオブプレースの音声操作をサポートしています。`post_decode_audio_sink_stream::submit_mixed_buffer()` に送信されるバッファは、必ずしも [post\_decode\_audio\_source\_stream::get\_next\_buffer()](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_next_buffer) から取得されたバッファと同じである必要はありません。

40 ms ごとに、このストリームからの次の 40 ms の音声がレンダリングされます。
音声の途切れを防ぐには、継続的に聞かれるべき音声のバッファを一定のレートでこのストリームに送信する必要があります。

### プライバシーとミキシング

デコード後パイプラインのソース-シンクモデルのため、[post\_decode\_audio\_source\_stream](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) オブジェクトから取得したバッファをミックスし、ミックスされたバッファをレンダリングのために [post\_decode\_audio\_sink\_stream](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) オブジェクトに送信するのはアプリの責任です。これはまた、適切なプライバシーと権限を強制してミックスを実行することもアプリの責任であることを意味します。
Game Chat 2 は、[post\_decode\_audio\_sink\_stream::can\_receive\_audio\_from\_source\_stream()](/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/methods/post_decode_audio_sink_stream_can_receive_audio_from_source_stream) を提供して、この情報のクエリを簡単かつ効率的にします。

### チャットインジケーター

デコード後音声操作は、各ユーザーのチャットインジケーターの状態に影響しません。
たとえば、リモートユーザーがミュートされている場合、音声はアプリに提供されます。ただし、そのリモートユーザーのチャットインジケーターは引き続きミュートされていることを示します。

リモートユーザーが話しているとき、その音声は提供されます。ただし、アプリがそのユーザーからの音声を含む音声ミックスを提供するかどうかに関係なく、チャットインジケーターは話していることを示します。
UI とチャットインジケーターの詳細については、[Game Chat 2 の使用](/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2#ui) を参照してください。

音声ミックス内にどのユーザーが存在するかを決定するために追加のアプリ固有の制約を使用する場合、Game Chat 2 が提供するチャットインジケーターを読み取るときに、同じ制約を考慮するのはアプリの責任です。

### ストリームコンテキスト

アプリは、[post\_decode\_audio\_source\_stream::set\_custom\_stream\_context](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_set_custom_stream_context) および [post\_decode\_audio\_source\_stream::custom\_stream\_context](/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_custom_stream_context) メソッドを使用して、デコード後音声ストリームでカスタムのポインターサイズのコンテキスト値を管理できます。
これらのカスタムストリームコンテキストは、Game Chat 2 の音声ストリームと補助データ (たとえば、ストリームメタデータやゲーム状態) 間のマッピングを作成するのに役立ちます。

### 例

以下は、1 つの音声処理フレームでデコード後音声ストリームを使用する方法を示す簡略化されたエンドツーエンドサンプルです。

```cpp theme={null}
uint32_t streamStateChangeCount;
game_chat_stream_state_change_array streamStateChanges;
chat_manager::singleton_instance().start_processing_stream_state_changes(&streamStateChangeCount, &streamStateChanges);

for (uint32_t streamStateChangeIndex = 0; streamStateChangeIndex < streamStateChangeCount; ++streamStateChangeIndex)
{
    switch (streamStateChanges[streamStateChangeIndex]->state_change_type)
    {
        case game_chat_stream_state_change_type::post_decode_audio_source_stream_created:
        {
            post_decode_audio_source_stream* stream = streamStateChanges[streamStateChangeIndex]->post_decode_audio_source_stream;
            stream->set_custom_stream_context(...);
            HandlePostDecodeAudioSourceStreamCreated(stream);
            break;
        }

        case game_chat_stream_state_change_type::post_decode_audio_source_stream_closed:
        {
            HandlePostDecodeAudioSourceStreamClosed(stream);
            break;
        }

        case game_chat_stream_state_change_type::post_decode_audio_source_stream_destroyed:
        {
            HandlePostDecodeAudioSourceStreamDestroyed(stream);
            break;
        }

        case game_chat_stream_state_change_type::post_decode_audio_sink_stream_created:
        {
            post_decode_audio_sink_stream* stream = streamStateChanges[streamStateChangeIndex]->post_decode_audio_sink_stream;
            stream->set_custom_stream_context(...);
            stream->set_processed_format(...);
            HandlePostDecodeAudioSinkStreamCreated(stream);
            break;
        }

        case game_chat_stream_state_change_type::post_decode_audio_sink_stream_closed:
        {
            HandlePostDecodeAudioSinkStreamClosed(stream);
            break;
        }

        case game_chat_stream_state_change_type::post_decode_audio_sink_stream_destroyed:
        {
            HandlePostDecodeAudioSinkStreamDestroyed(stream);
            break;
        }

        ...
    }
}

chat_manager::singleton_instance().finish_processing_stream_state_changes(streamStateChanges);

uint32_t sourceStreamCount;
post_decode_audio_source_stream_array sourceStreams;
chatManager::singleton_instance().get_post_decode_audio_source_streams(&sourceStreamCount, &sourceStreams);

uint32_t sinkStreamCount;
post_decode_audio_sink_stream_array sinkStreams;
chatManager::singleton_instance().get_post_decode_audio_sink_streams(&sinkStreamCount, &sinkStreams);

//
// MixBuffer is a custom type defined as:
// struct MixBuffer
// {
//     uint32_t bufferByteCount;
//     void* buffer;
// };
//
std::vector<std::pair<post_decode_audio_source_stream*, MixBuffer>> cachedSourceBuffers;

for (uint32_t sourceStreamIndex = 0; sourceStreamIndex < sourceStreamCount; ++sourceStreamIndex)
{
    post_decode_audio_source_stream* sourceStream = sourceStreams[sourceStreamIndex];

    MixBuffer mixBuffer;
    sourceStream->get_next_buffer(&mixBuffer.bufferByteCount, &mixBuffer.buffer);
    if (buffer != nullptr)
    {
        // Stash the buffer to return after we're done with mixing. If this program was using audio middleware, now
        // would be an appropriate time to plumb the buffer through the middleware.
        cachedSourceBuffer.push_back(std::pair<post_decode_audio_source_stream*, MixBuffer>{sourceStream, mixBuffer});
    }
}

// Loop over each sink stream, perform mixing, and submit.
for (uint32_t sinkStreamIndex = 0; sinkStreamIndex < sinkStreamCount; ++sinkStreamIndex)
{
    post_decode_audio_sink_stream* sinkStream = sinkStreams[sinkStreamIndex];

    if (sinkStream->is_open())
    {
        std::vector<std::pair<MixBuffer, float>> buffersToMixForThisStream;

        for (const std::pair<post_decode_audio_source_stream, MixBuffer>& sourceBufferPair : cachedSourceBuffers)
        {
            float volume;
            if (sinkStream->can_receive_audio_from_source_stream(sourceBufferPair.first, &volume))
            {
                buffersToMixForThisStream.push_back(std::pair<MixBuffer, float>{sourceBufferPair.second, volume});
            }
        }

        if (buffersToMixForThisStream.size() > 0)
        {
            uint32_t mixedBufferByteCount;
            uint8_t* mixedBuffer;
            MixPostDecodeBuffers(buffersToMixForThisStream, &mixedBufferByteCount, &mixedBuffer);
            sinkStream->submit_mixed_buffer(mixedBufferByteCount, mixedBuffer);
        }
    }
}

// Return buffers after mix and submission.
for (const std::pair<post_decode_audio_source_stream*, MixBuffer>& cachedSourceBuffer : cachedSourceBuffers)
{
    post_decode_audio_source_stream* sourceStream = cachedSourceBuffer.first;
    void* bufferToReturn = cachedSourceBuffer.second.buffer;
    sourceStream->return_buffer(bufferToReturn);
}

Sleep(audioProcessingPeriodInMilliseconds);
```

<a id="chat-user-lifetimes" />

## チャットユーザーのライフタイム

リアルタイム音声操作の有効化は、チャットユーザーのライフタイムに影響します。
[chat\_manager::remove\_user(chatUserX)](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_remove_user) が呼び出された場合、`chatUserX` によって指される [chat\_user](/reference/chat/gamechat2/classes/chat_user/chat_user) オブジェクトは、`chatUserX` を参照するすべての音声ストリームが破棄されるまで有効なままです。

次のシナリオを考えてみてください。

```cpp theme={null}
// At some point, a chat user, chatUserX, leaves the game session.
chat_manager::singleton_instance().remove_user(chatUserX);

// chatUserX is still valid, but to avoid further synchronization, prevent non-audio-stream use of chatUserX.
chatUserX = nullptr;

// On the audio processing thread...
uint32_t streamStateChangeCount;
game_chat_stream_state_change_array streamStateChanges;
chat_manager::singleton_instance().start_processing_stream_state_changes(&streamStateChangeCount, &streamStateChanges);
for (uint32_t streamStateChangeIndex = 0; streamStateChangeIndex < streamStateChangeCount; ++streamStateChangeIndex)
{
    switch (streamStateChanges[streamStateChangeIndex]->state_change_type)
    {
        ...

        // All the streams that are associated with chatUserX will close.
        case Xs::game_chat_2::game_chat_stream_state_change_type::pre_encode_audio_stream_closed:
        {
            CleanupPreEncodeAudioStreamResources(streamStateChanges[streamStateChangeIndex].pre_encode_audio_stream);
            break;
        }

        ...
    }
}
chat_manager::singleton_instance().finish_processing_stream_state_changes(streamStateChanges);

// The next time the app processes stream state changes...
uint32_t streamStateChangeCount;
game_chat_stream_state_change_array streamStateChanges;
chat_manager::singleton_instance().start_processing_stream_state_changes(&streamStateChangeCount, &streamStateChanges);
for (uint32_t streamStateChangeIndex = 0; streamStateChangeIndex < streamStateChangeCount; ++streamStateChangeIndex)
{
    switch (streamStateChanges[streamStateChangeIndex]->state_change_type)
    {
        ...

        case Xs::game_chat_2::game_chat_stream_state_change_type::pre_encode_audio_stream_destroyed:
        {
            uint32_t chatUserCount;
            Xs::game_chat_2::chat_user_array chatUsers;
            streamStateChanges[streamStateChangeIndex].pre_encode_audio_stream->get_users(&chatUserCount, &chatUsers);
            assert(chatUserCount != 0);
            for (uint32_t chatUserIndex = 0; chatUserIndex < chatUserCount; ++chatUserIndex)
            {
                // chat_user objects such as chatUserX will still be valid while the destroyed state change is being processed.
                Log(chatUsers[chatUserIndex]->xbox_user_id());
            }
            break;
        }

        ...
    }
}
chat_manager::singleton_instance().finish_processing_stream_state_changes(streamStateChanges);
// After the all destroyed state changes have been processed for all streams associated with chatUserX, its memory will be invalidated.
// Don't call methods on chatUserX. For example, chatUserX->xbox_user_id()
```

## リファレンス API ドキュメント

* [Gamechat2 (API 内容)](/reference/chat/gamechat2/gamechat2_members)
  * 構造体
    * [game\_chat\_stream\_state\_change](/reference/chat/gamechat2/structs/game_chat_stream_state_change)

## 関連項目

[Game Chat 2 の概要](/services/xbox-services/multiplayer/chat/game-chat2/game-chat-2-intro)

[Game Chat 2 C++ API の使用](/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2)

[API 内容 (GameChat2)](/reference/chat/gamechat2/gamechat2_members)

[Microsoft Game Development Kit](/services/playfab/sdks/platforms/gdk)


## Related topics

- [Game Chat 2 C++ API の使用](/ja-jp/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2.md)
- [Game Chat 2 の概要](/ja-jp/services/xbox-services/multiplayer/chat/game-chat2/game-chat-2-overview.md)
- [リアルタイム オーディオ操作でカスタム ボイス エフェクトを適用する](/ja-jp/services/playfab/community/voice-communications/concepts-realtime-audio-manipulation.md)
- [Lobby and Matchmaking のリアルタイム メッセージ](/ja-jp/services/playfab/multiplayer/lobby/lobby-and-matchmaking-real-time-messages.md)
- [リアルタイム分析の中核概念](/ja-jp/services/playfab/data-analytics/ingest-data/real-time-analytics-core-concepts.md)
