リアルタイム オーディオ操作
このトピックでは、リアルタイムのオーディオ操作について簡単に説明します。
ゲーム チャット 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()