> ## 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.

# 即時音訊操作

> 在您的 XBOX GDK 遊戲中使用音訊操作管線，檢查 Game Chat 2 語音串流並套用即時音訊效果的逐步解說。

本主題簡要逐步解說如何使用即時音訊操作。

Game Chat 2 讓您可以選擇將自己插入聊天音訊管線中，以檢查和操作使用者的聊天音訊資料。
這對於在遊戲中為使用者的聲音套用有趣的音訊效果很有用。

在 Game Chat 2 中，是透過可輪詢音訊資料的音訊串流物件與音訊操作管線互動。
相較於使用回呼，您可以使用此模型在對您最方便的任何處理執行緒上檢查或操作音訊。

## 初始化音訊操作管線

根據預設，Game Chat 2 不會啟用即時音訊操作。
若要啟用它，應用程式必須在 [chat\_manager::initialize](/zh-TW/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_initialize) 中設定 `audioManipulationMode` 參數，以指定要啟用哪些形式的音訊操作。

目前支援下列音訊操作形式，這些形式位於 [game\_chat\_audio\_manipulation\_mode\_flags](/zh-TW/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](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構提供音訊串流狀態的更新。
這些更新會儲存哪個串流已更新以及如何更新的相關資訊。

可以透過呼叫 [chat\_manager::start\_processing\_stream\_state\_changes()](/zh-TW/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_start_processing_stream_state_changes) 和 [chat\_manager::finish\_processing\_stream\_state\_changes()](/zh-TW/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_finish_processing_stream_state_changes) 這對方法來輪詢這些更新。它們會以 [game\_chat\_stream\_state\_change](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構指標陣列的形式，提供所有最新的、已排入佇列的音訊串流狀態更新。應用程式應逐一查看陣列，並適當地處理每個更新。

處理完所有可用的 [game\_chat\_stream\_state\_change](/zh-TW/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](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/pre_encode_audio_stream) 類別，提供本機使用者之編碼前聊天音訊資料的存取權。

### 串流存留期

當新的 [pre\_encode\_audio\_stream](/zh-TW/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](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構傳遞。
將此串流狀態變更傳回 Game Chat 2 之後，音訊串流即可用於編碼前音訊操作。

當現有的 [pre\_encode\_audio\_stream](/zh-TW/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](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構收到通知。
這是應用程式開始清除與此音訊串流相關聯之資源的時機。
將此串流狀態變更傳回 Game Chat 2 之後，音訊串流即無法用於編碼前音訊操作。

當已關閉的 [pre\_encode\_audio\_stream](/zh-TW/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](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構收到通知。
應清除此串流的任何參考或指標。
將此串流狀態變更傳回 Game Chat 2 之後，音訊串流記憶體即會失效。

### 串流使用者

可以使用 [pre\_encode\_audio\_stream::get\_users()](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_get_users) 檢查與串流相關聯的使用者清單。

### 音訊格式

可以使用 [pre\_encode\_audio\_stream::get\_pre\_processed\_format()](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_get_pre_processed_format) 檢查應用程式從 Game Chat 2 擷取之緩衝區的音訊格式。預先處理的音訊格式為單聲道。
應用程式應預期處理以 32 位元浮點數、16 位元整數和 32 位元整數表示的資料。

應用程式必須使用 [pre\_encode\_audio\_stream::set\_processed\_format()](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_set_processed_format)，告知 Game Chat 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()](/zh-TW/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()](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_get_next_buffer) 從編碼前音訊串流擷取音訊緩衝區。
平均每 40 毫秒就有新的音訊緩衝區可用。

此方法傳回的緩衝區在使用完畢時，必須釋放至 [pre\_encode\_audio\_stream::return\_buffer()](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_return_buffer)。

在任何指定時間，編碼前音訊串流最多只能有 10 個已排入佇列或未傳回的緩衝區。
達到此限制之後，從使用者音訊來源擷取的新緩衝區會被捨棄，直到傳回部分未完成的緩衝區為止。

#### 使用 submit\_buffer() 提交音訊緩衝區

應用程式可以使用 [pre\_encode\_audio\_stream::submit\_buffer()](/zh-TW/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 毫秒，就會對此串流接下來 40 毫秒的音訊進行編碼和傳輸。

為了防止音訊斷斷續續，應以固定速率將應連續聽到之音訊的緩衝區提交至此串流。

### 串流內容

應用程式可以使用 [pre\_encode\_audio\_stream::set\_custom\_stream\_context()](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_set_custom_stream_context) 和 [pre\_encode\_audio\_stream::custom\_stream\_context()](/zh-TW/reference/chat/gamechat2/classes/pre_encode_audio_stream/methods/pre_encode_audio_stream_custom_stream_context)，在編碼前音訊串流上管理自訂的指標大小內容值。這些自訂串流內容有助於在 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:
        {
            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](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) 和 [post\_decode\_audio\_sink\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) 類別，提供解碼後聊天音訊資料的存取權。這表示使用者可以針對聊天音訊的每個本機接收者，以獨特的方式操作來自遠端使用者的音訊。

### 來源和接收器

與編碼前管線不同，處理解碼後音訊資料的模型分為兩個類別：[post\_decode\_audio\_source\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) 和 [post\_decode\_audio\_sink\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream)。

來自遠端使用者的解碼音訊可以從 [post\_decode\_audio\_source\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) 物件擷取、加以操作，然後傳送至 [post\_decode\_audio\_sink\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/post_decode_audio_sink_stream) 物件進行轉譯。
這可讓 Game Chat 2 解碼後音訊處理管線與實用的音訊中介軟體整合。

### 串流存留期

當新的 [post\_decode\_audio\_source\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) 或 [post\_decode\_audio\_sink\_stream](/zh-TW/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](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構傳遞。
將此串流狀態變更傳回 Game Chat 2 之後，音訊串流即可用於解碼後音訊操作。

當現有的 [post\_decode\_audio\_source\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) 或 [post\_decode\_audio\_sink\_stream](/zh-TW/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](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構收到通知。
這是應用程式開始清除與此音訊串流相關聯之資源的時機。
將此串流狀態變更傳回 Game Chat 2 之後，音訊串流即無法用於解碼後音訊操作。
對於來源串流，這表示不會再有緩衝區排入佇列以供操作。
對於接收器串流，這表示提交的緩衝區將不再被轉譯。

當已關閉的 [post\_decode\_audio\_source\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) 或 [post\_decode\_audio\_sink\_stream](/zh-TW/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](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change) 結構收到通知。
應清除此串流的任何參考或指標。
將此串流狀態變更傳回 Game Chat 2 之後，音訊串流記憶體即會失效。

### 串流使用者

可以使用 [post\_decode\_audio\_source\_stream::get\_users()](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_users) 檢查與解碼後來源串流相關聯的遠端使用者清單。

可以使用 [post\_decode\_audio\_sink\_stream::get\_users()](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_sink_stream/methods/post_decode_audio_sink_stream_get_users) 檢查與解碼後接收器串流相關聯的本機使用者清單。

### 音訊格式

可以使用 [post\_decode\_audio\_source\_stream::get\_pre\_processed\_format()](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_pre_processed_format) 檢查應用程式從 Game Chat 2 擷取之緩衝區的音訊格式。預先處理的音訊格式一律為單聲道、16 位元整數 PCM。

應用程式必須使用 [post\_decode\_audio\_sink\_stream::set\_processed\_format()](/zh-TW/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()](/zh-TW/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()](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_next_buffer) 從解碼後音訊來源串流擷取音訊緩衝區。平均每 40 毫秒就有新的音訊緩衝區可用。

此方法傳回的緩衝區在使用完畢時，必須釋放至 [post\_decode\_audio\_source\_stream::return\_buffer()](/zh-TW/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()](/zh-TW/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()](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_get_next_buffer) 擷取的相同緩衝區。

每 40 毫秒，就會轉譯此串流接下來 40 毫秒的音訊。
為了防止音訊斷斷續續，應以固定速率將應連續聽到之音訊的緩衝區提交至此串流。

### 隱私權和混音

由於解碼後管線的來源-接收器模型，應用程式有責任混合從 [post\_decode\_audio\_source\_stream](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/post_decode_audio_source_stream) 物件擷取的緩衝區，並將混合後的緩衝區提交至 [post\_decode\_audio\_sink\_stream](/zh-TW/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()](/zh-TW/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](/zh-TW/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2#ui)。

如果使用額外的應用程式特定限制來判斷哪些使用者出現在音訊混音中，應用程式有責任在讀取 Game Chat 2 提供的聊天指標時考量這些相同的限制。

### 串流內容

應用程式可以使用 [post\_decode\_audio\_source\_stream::set\_custom\_stream\_context](/zh-TW/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](/zh-TW/reference/chat/gamechat2/classes/post_decode_audio_source_stream/methods/post_decode_audio_source_stream_custom_stream_context) 方法，在解碼後音訊串流上管理自訂的指標大小內容值。
這些自訂串流內容有助於在 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::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)](/zh-TW/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_remove_user)，`chatUserX` 所指向的 [chat\_user](/zh-TW/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 內容)](/zh-TW/reference/chat/gamechat2/gamechat2_members)
  * 結構
    * [game\_chat\_stream\_state\_change](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change)

## 另請參閱

[Game Chat 2 簡介](/zh-TW/services/xbox-services/multiplayer/chat/game-chat2/game-chat-2-intro)

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

[API 內容 (GameChat2)](/zh-TW/reference/chat/gamechat2/gamechat2_members)

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


## Related topics

- [透過即時音訊操作套用自訂語音效果](/zh-TW/services/playfab/community/voice-communications/concepts-realtime-audio-manipulation.md)
- [game_chat_sample_type](/zh-TW/reference/chat/gamechat2/enums/game_chat_sample_type.md)
- [game_chat_stream_state_change](/zh-TW/reference/chat/gamechat2/structs/game_chat_stream_state_change.md)
- [使用 Game Chat 2 C++ API](/zh-TW/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2.md)
- [Game Chat 2 簡介](/zh-TW/services/xbox-services/multiplayer/chat/game-chat2/game-chat-2-intro.md)
