> ## 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](/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) 检查与流关联的用户列表。

### 音频格式

可以使用 [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) 检查应用从 Game Chat 2 检索的缓冲区的音频格式。预处理音频格式为单声道。
应用应准备处理表示为 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 位浮点性能监视器计数器 (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 可用一次。

此方法返回的缓冲区在使用完毕后必须释放到 [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 音频流和辅助数据(例如流元数据和游戏状态)之间创建映射。

### 示例

以下是一个简化的端到端示例,说明如何在一个音频处理帧中使用编码前音频流。

```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)。

可以从 [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) 检查与解码后接收器流关联的本地用户列表。

### 音频格式

可以使用 [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) 检查应用从 Game Chat 2 检索的缓冲区的音频格式。预处理音频格式始终为单声道、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 可用一次。

此方法返回的缓冲区在使用完毕后必须释放到 [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 音频流和辅助数据(例如流元数据和游戏状态)之间创建映射。

### 示例

以下是一个简化的端到端示例,说明如何在一个音频处理帧中使用解码后音频流。

```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_sample_type](/zh-CN/reference/chat/gamechat2/enums/game_chat_sample_type.md)
- [game_chat_stream_state_change](/zh-CN/reference/chat/gamechat2/structs/game_chat_stream_state_change.md)
- [使用 Game Chat 2 C++ API](/zh-CN/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2.md)
- [game_chat_audio_manipulation_mode_flags](/zh-CN/reference/chat/gamechat2/enums/game_chat_audio_manipulation_mode_flags.md)
- [Game Chat 2](/zh-CN/services/xbox-services/multiplayer/chat/game-chat2/index.md)
