次の方法で共有


リアルタイム オーディオ操作

このトピックでは、リアルタイムのオーディオ操作について簡単に説明します。

ゲーム チャット 2 では、チャット オーディオ パイプラインに自身を挿入して、ユーザーのチャット オーディオ データを検査および操作することができます。 これは、ゲーム中のユーザーの音声に面白いオーディオ効果を適用する場合に便利です。

ゲーム チャット 2 では、オーディオ操作パイプラインは、オーディオ データ用にポーリングできるオーディオ ストリーム オブジェクトを使って操作できます。 コールバックを使うのとは対照的に、このモデルを使用して、最も便利な処理スレッドでオーディオの検査や操作を行うことができます。

オーディオ操作パイプラインの初期化

既定では、ゲーム チャット 2 はリアルタイム オーディオ操作を有効にしません。 リアルタイムのオーディオ操作を有効にするには、アプリが audioManipulationMode パラメーターを設定して、chat_manager::initialize で有効にするオーディオ操作の形式を指定する必要があります。

現在は、次のオーディオ操作形式がサポートされており、game_chat_audio_manipulation_mode_flags 列挙型に含まれています。

  • game_chat_audio_manipulation_mode_flags::none: オーディオ操作を無効にします。 これは既定の構成です。 このモードでは、チャット オーディオ フローは中断されません。
  • game_chat_audio_manipulation_mode_flags::pre_encode_stream_manipulation: プリエンコード オーディオ操作を有効にします。 このモードでは、ローカル ユーザーが生成したすべてのチャット オーディオは、エンコードされる前にオーディオ操作パイプラインを通じて供給されます。 アプリは単にチャット オーディオ データを検査するだけで、それを操作していない場合でも、エンコードや送信ができるように、変更していない音声バッファーをゲーム チャット 2 に再送信する必要があります。
  • game_chat_audio_manipulation_mode_flags::post_decode_stream_manipulation: ポストデコード オーディオ操作を有効にします。 このモードでは、受信者によってデコードされ、レンダリングされる前に、リモート ユーザーから受信したすべてのチャット オーディオがオーディオ操作パイプラインを通じて供給されます。 アプリが単にチャット オーディオ データを検査するだけで、それを操作していない場合でも、アプリは、変更していない音声バッファーをミキシングして、ゲーム チャット 2 に再送信する必要があります。

オーディオ ストリームの状態の変化の処理

ゲーム チャット 2 では、game_chat_stream_state_change 構造体によって、オーディオ ストリームの状態の更新が提供されます。 これらの更新には、更新されたストリームとその更新内容に関する情報が格納されています。

chat_manager::start_processing_stream_state_changes() メソッドと chat_manager::finish_processing_stream_state_changes() メソッドのペアを呼び出すことにより、これらの更新をポーリングできます。 これらのメソッドでは、キューに入れられた最新のオーディオ ストリーム状態更新のすべてが game_chat_stream_state_change 構造体ポインターの配列として提供されます。 アプリは、配列を反復処理し、各更新を適切に処理する必要があります。

利用可能なすべての game_chat_stream_state_change 更新プログラムが処理された後、その配列はchat_manager::finish_processing_stream_state_changes()からゲーム チャット 2 に返されます。 この例を次に示します。

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);

プリエンコード チャット オーディオ データの操作

ゲーム チャット 2 では、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 構造体を通して提供されます。 このストリームの状態変更がゲーム チャット 2 に返されると、オーディオ ストリームがプリエンコード オーディオ操作に使用できるようになります。

既存のpre_encode_audio_stream がオーディオ操作に使用できなくなると、アプリには state_change_typeフィールドがgame_chat_stream_state_change_type::pre_encode_audio_stream_closedに設定されたgame_chat_stream_state_change構造体を介して通知されます。 これはアプリにとっては、オーディオ ストリームに関連付けられているリソースのクリーン アップを開始するチャンスです。 このストリームの状態変更がゲーム チャット 2 に返されると、オーディオ ストリームがプリエンコード オーディオ操作に使用できなくなります。

終了した pre_encode_audio_stream からすべてのリソースが返された場合は、ストリームが破棄され、アプリには state_change_type フィールドが game_chat_stream_state_change_type::pre_encode_audio_stream_destroyed に設定された game_chat_stream_state_change 構造体を介して通知されます。 このストリームへの参照またはポインターをすべてクリーンアップする必要があります。 このストリームの状態変更がゲーム チャット 2 に返されると、オーディオ ストリーム メモリは無効になります。

ストリームのユーザー

ストリームに関連付けられているユーザーのリストは、pre_encode_audio_stream::get_users() を使用して調べることができます。

オーディオ形式

アプリがゲーム チャット 2 から取得するバッファーのオーディオ形式は、pre_encode_audio_stream::get_pre_processed_format() を使用して調べることができます。 前処理済みのオーディオ形式はモノラルです。 アプリは、32 ビット浮動小数点値、16 ビット整数値、32 ビット整数値として表されるデータを処理することを想定する必要があります。

アプリは、pre_encode_audio_stream::set_processed_format()を使用して、エンコーディングおよび通信用に送信された操作バッファーのオーディオ形式を、ゲーム チャット 2 に通知する必要があります。 プリエンコード オーディオ ストリームの処理形式は、次の前提条件を満たしている必要があります。

  • 形式はモノラルでなければなりません。
  • 形式は、32 ビット浮動小数点パフォーマンス モニター カウンター (PCM)、32 ビット整数 PCM、16 ビット整数 PCM の形式でなければなりません。
  • 形式のサンプル レートは、そのプラットフォームに基づく前提条件に従う必要があります。 Xbox One ERA と Xbox Series X|S は、8 KHz、12 KHz、16 KHz、24 KHz のサンプル レートをサポートしています。 Xbox One 用 および Windows PC 用のユニバーサル Windows プラットフォーム (UWP) は、8 KHz、12 KHz、16 KHz、24 KHz、32 KHz、44.1 KHz、48 KHz のサンプル レートをサポートしています。

プリエンコード オーディオの取得と送信

アプリでは、プリエンコード オーディオ ストリームに、pre_encode_audio_stream::get_available_buffer_count() を使用して、処理に利用可能なバッファー数をクエリできます。 この情報は、バッファーの最小数が使用可能になるまでアプリがオーディオ処理を延期する場合に使用できます。

プリエンコード オーディオ ストリームでは、10 個のバッファーだけがキューイングされ、オーディオの遅延によってオーディオ パイプラインに遅延が発生します。 アプリでは、4 つ以上のバッファーをキューする前にはプリエンコード オーディオ ストリームをドレインすることをお勧めします。

get_next_buffer() を使用したオーディオ バッファーの取得

アプリは、pre_encode_audio_stream::get_next_buffer() を使用して、プリエンコード オーディオ ストリームからオーディオ バッファーを取得できます。 新しいオーディオ バッファーは平均 40 ミリ秒毎に 1 つ利用可能です。

このメソッドによって返されるバッファーは、使い終わったら pre_encode_audio_stream::return_buffer() にリリースされる必要があります。

プリエンコード オーディオ ストリームには、最大 10 個のキューまたは返されていないバッファーがいつでも存在可能です。 この制限に達すると、未処理のバッファーの一部が返されるまで、ユーザーのオーディオ ソースからキャプチャされた新しいバッファーが破棄されます。

submit_buffer() によるオーディオ バッファーの送信

アプリでは、pre_encode_audio_stream::submit_buffer() を使用して、エンコードや送信のために、ゲーム チャット 2 に返されるオーディオ バッファーの検査および操作が可能になります。 ゲーム チャット 2 では、インプレースおよびアウトオブプレースのオーディオ操作をサポートしています。 pre_encode_audio_stream::submit_buffer() に送信されるバッファーは、必ずしも pre_encode_audio_stream::get_next_buffer() から取得されたものと同じバッファーである必要はありません。

これらの送信バッファーのプライバシーまたは特権は、このストリームに関連付けられているユーザーに基づいて適用されます。 40 ミリ秒ごとに、このストリームの次の 40 ミリ秒のオーディオがエンコードされて送信されます。

オーディオの中断を防ぐため、連続して聞こえるオーディオのバッファーは一定のレートでこのストリームに送信する必要があります。

ストリーム コンテキスト

アプリでは、pre_encode_audio_stream::set_custom_stream_context() および pre_encode_audio_stream::custom_stream_context() を使用して、プリエンコード オーディオ ストリームで、カスタム ポインター サイズ コンテキスト値を管理できます。 これらのカスタム ストリーム コンテキストは、ゲーム チャット 2 のオーディオ ストリームと補助データの間でマッピングを作成する場合に役立ちます。 たとえば、ストリーム メタデータやゲーム状態などです。

次に、1 つのオーディオ処理フレームでプリエンコード オーディオ ストリームを使用する方法の簡単なエンドツーエンドのサンプルを示します。

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);

ポストデコード チャット オーディオ データの操作

ゲーム チャット 2 では、post_decode_audio_source_stream および post_decode_audio_sink_stream クラスを使用して、ポストデコード チャット オーディオ データにアクセスできます。 つまり、ユーザーはチャット オーディオのローカル レシーバーごとに、リモート ユーザーからの音声を一意に操作できます。

ソースとシンク

プリエンコード パイプラインとは異なり、ポストデコード オーディオ データを扱うモデルは、2 つのクラスに分かれています。post_decode_audio_source_stream および post_decode_audio_sink_streamです。

リモート ユーザーのデコード済みのオーディオは post_decode_audio_source_stream オブジェクトから取得、操作して、レンダリング用に post_decode_audio_sink_stream オブジェクトに送信されます。 これにより、ゲーム チャット 2 のポストデコード オーディオ処理パイプラインと、有用なオーディオ ミドルウェアの統合が可能になります。

ストリームの有効期間

新しい post_decode_audio_source_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 構造体を通じて配信されます。 このストリーム状態変更がゲーム チャット 2 に返されると、オーディオ ストリームはポストデコード オーディオ操作に使用できるようになります。

既存の post_decode_audio_source_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 構造体を通じて通知されます。 これはアプリにとっては、オーディオ ストリームに関連付けられているリソースのクリーン アップを開始するチャンスです。 このストリーム状態変更がゲーム チャット 2 に返されると、オーディオ ストリームはポストデコード オーディオ操作に使用できなくなります。 ソース ストリームの場合、操作用にキューに入れられるバッファーがなくなることを意味します。 シンク ストリームの場合、送信されたバッファーがレンダリングされなくなることを意味します。

閉じられた post_decode_audio_source_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 構造体を通じてアプリに通知されます。 このストリームへの参照またはポインターをすべてクリーンアップする必要があります。 このストリームの状態変更がゲーム チャット 2 に返されると、オーディオ ストリーム メモリは無効になります。

ストリームのユーザー

ポストデコード ソース ストリームに関連付けられているリモート ユーザーのリストは、post_decode_audio_source_stream::get_users() を使用して調べることができます。

ポストデコード シンク ストリームに関連付けられているローカル ユーザーのリストは、post_decode_audio_sink_stream::get_users() を使用して調べることができます。

オーディオ形式

アプリがゲーム チャット 2 から取得するバッファーのオーディオ形式は、post_decode_audio_source_stream::get_pre_processed_format() を使用して調べることができます。 前処理されたオーディオ形式は、常にモノラル、16 ビット整数 PCM です。

このアプリは、post_decode_audio_sink_stream::set_processed_format() を使用してレンダリング用に送信される操作済みバッファーのオーディオ形式をゲーム チャット 2 に通知する必要があります。 ポストデコード オーディオ シンク ストリームの処理形式は、次の前提条件を満たしている必要があります。

  • 64 チャネル未満の形式である必要があります。
  • 16 ビット整数 PCM (最適に)、20 ビット整数 PCM (24 ビット コンテナー) で、24 ビット整数 PCM、32 ビット整数 PCM、または 32 ビットの浮動小数点 PCM (16 ビット整数 PCM 後の優先形式) 形式である必要があります。
  • 形式のサンプル レートは、1 秒あたり 1000 ~ 200000 サンプルである必要があります。

ポストエンコード オーディオの取得と送信

アプリでは、post_decode_audio_source_stream::get_available_buffer_count() を利用して、処理に使用可能なバッファー数を対象に、ポストデコード オーディオ ソース ストリームにクエリを実行できます。 この情報は、バッファーの最小数が使用可能になるまでアプリがオーディオ処理を延期する場合に使用できます。 各ポストデコード オーディオ ソース ストリームは、10 個のバッファーのみがキューされ、オーディオ パイプラインのオーディオ遅延が発生します。 アプリでは、4 つ以上のバッファーをキューする前にはポストデコード オーディオ ストリームをドレインすることをお勧めします。

get_next_buffer() を使用したオーディオ バッファーの取得

アプリは、post_decode_audio_source_stream::get_next_buffer()を使用して、ポストデコード オーディオソースストリームからオーディオ バッファーを取得できます。 新しいオーディオ バッファーは平均 40 ミリ秒毎に 1 つ利用可能です。

このメソッドによって返されるバッファーは、使い終わったら post_decode_audio_source_stream::return_buffer() にリリースされる必要があります。

ポストデコード オーディオ ソース ストリームには、キューに入れられたバッファーまたは返されていないバッファーが常に 10 個まで存在することができます。 この制限に達すると、未処理のバッファーの一部が返されるまで、リモート ユーザーの新しいデコード済みバッファーは破棄されます。

submit_buffer() によるオーディオ バッファーの送信

アプリでは、post_decode_audio_sink_stream::submit_mixed_buffer() を使用して、ポストデコード オーディオ シンク ストリームで検証済バッファーと操作済バッファーをゲーム チャット 2 に再送信できます。 ゲーム チャット 2 では、インプレースおよびアウトオブプレースのオーディオ操作をサポートしています。 post_decode_audio_sink_stream::submit_mixed_buffer() に送信されるバッファーは、必ずしも post_decode_audio_source_stream::get_next_buffer() から取得されたものと同じバッファーである必要はありません。

このストリームの次の 40 ms のオーディオは、40 ms ごとにレンダリングされます。 オーディオの中断を防ぐため、連続して聞こえるオーディオのバッファーは一定のレートでこのストリームに送信する必要があります。

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

ポストデコード パイプラインのソース-シンク モデルのため、post_decode_audio_source_stream オブジェクトから取得されたバッファーをミキシングし、post_decode_audio_sink_stream オブジェクトにミキシングしたバッファーを送信してレンダリングする必要があります。 これは、適切なプライバシーと特権を強制されたミキシングの実行は、アプリの責務でもあることも意味します。 ゲーム チャット 2 では、この情報のクエリを簡単かつ効率的にするために、post_decode_audio_sink_stream::can_receive_audio_from_source_stream() を提供します。

チャット インジケーター

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

リモート ユーザーが話しているときは、その音声が提供されます。 ただし、アプリがそのユーザーの音声を含むオーディオ ミックスを提供しているかどうかに関係なく、チャット インジケーターはお話し中を示します。 UI およびチャット インジケーターの詳細については、「ゲーム チャット 2 の使用」を参照してください。

アプリ固有の追加の制限を使用して、オーディオ ミックスに存在するユーザーを判断する場合、ゲーム チャット 2 が提供するチャット インジケーターを読み取るときに、アプリ側で同じ制限を考慮する必要があります。

ストリーム コンテキスト

アプリでは、post_decode_audio_source_stream::set_custom_stream_context および post_decode_audio_source_stream::custom_stream_context の方法を使用して、ポストデコード オーディオ ストリームのカスタム ポインター サイズ コンテキスト値を管理できます。 これらのカスタム ストリーム コンテキストは、ゲーム チャット 2 のオーディオ ストリームと補助データの間でマッピングを作成する場合に役立ちます。 たとえば、ストリーム メタデータやゲーム状態などです。

次に、1 つのオーディオ処理フレームでポストデコード オーディオ ストリームを使用する方法の簡単なエンドツーエンドのサンプルを示します。

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);

チャット ユーザーの有効期間

リアルタイムのオーディオ操作を有効にすると、チャット ユーザーの有効期間に影響します。 chat_manager::remove_user(chatUserX) が呼び出された場合、chatUserX によって示された chat_user オブジェクトは、chatUserX を参照するすべてのオーディオ ストリームが破棄されるまで有効なままです。

次のシナリオを考えてみましょう。

// 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()

関連項目

ゲーム チャット 2 の概要

ゲーム チャット 2 C++ API の使用

API の内容 (GameChat2)

Microsoft Game Development Kit