다음을 통해 공유


실시간 오디오 조작

이 항목에는 실시간 오디오 조작을 사용하는 간략한 연습이 있습니다.

게임 채팅 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 인스턴스가 앱을 사용할 준비가 되면 game_chat_stream_state_change 구조체를 통해 game_chat_stream_state_change_type::pre_encode_audio_stream_created로 설정된 state_change_type 필드로 제공됩니다. 이 스트림 상태 변경 내용이 게임 채팅 2로 반환되면 오디오 스트림은 사전 인코드된 오디오 조작에 사용됩니다.

기존 pre_encode_audio_stream을 오디오 조작에 사용할 수 없게 되면 game_chat_stream_state_change 구조체를 통해 game_chat_stream_state_change_type::pre_encode_audio_stream_closed로 설정된 state_change_type 필드로 앱에 알립니다. 이는 앱이 오디오 스트림과 관련된 리소스 정리를 시작할 기회입니다. 이 스트림 상태 변경 내용이 게임 채팅 2로 반환되면 오디오 스트림은 사전 인코드된 오디오 조작에 사용될 수 없게 됩니다.

사용 중지된 pre_encode_audio_stream에 반환된 모든 리소스가 있는 경우 스트림은 제거되며 game_chat_stream_state_change 구조체를 통해 game_chat_stream_state_change_type::pre_encode_audio_stream_destroyed로 설정된 state_change_type 필드로 앱에 알립니다. 이 스트림에 대한 모든 참조 또는 포인터를 정리해야 합니다. 이 스트림 상태 변경 내용이 게임 채팅 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(Performance Monitor counter), 32비트 정수 PCM 또는 16비트 정수 PCM 형식이어야 합니다.
  • 형식의 샘플 속도가 해당 플랫폼에 따른 사전 조건을 충족해야 합니다. Xbox One ERA 및 Xbox Series X|S는 8KHz, 12KHz, 16KHz 및 24KHz 샘플 속도를 지원합니다. Xbox One 및 Windows PC용 UWP(유니버설 Windows 플랫폼)는 8KHz, 12KHz, 16KHz, 24KHz, 32KHz, 44.1KHz, 48KHz 샘플 속도를 지원합니다.

인코딩 전 오디오 검색 및 제출

앱은 pre_encode_audio_stream::get_available_buffer_count()를 사용하여 처리할 수 있는 버퍼 수에 대한 사전 인코드된 오디오 스트림의 쿼리를 수행할 수 있습니다. 이 정보는 앱이 버퍼의 최소 개수가 제공될 때까지 오디오 처리를 지연하려고 할 경우에 사용할 수 있습니다.

10개의 버퍼만 각 사전 인코드 오디오 스트림의 큐에 대기하고 오디오 지연은 오디오 파이프라인에서 대기 시간을 도입합니다. 앱에서 4개 이상의 버퍼를 큐에 추가하기 전에 먼저 이 스트림이 사전에 인코드되지 않은 오디오 스트림을 드레이닝하는 것이 좋습니다.

get_next_buffer()를 사용하여 오디오 버퍼 검색

앱은 pre_encode_audio_stream::get_next_buffer()를 사용하여 사전에 인코드된 오디오 스트림에서 오디오 버퍼를 검색할 수 있습니다. 40밀리초마다 한 번씩 새 오디오 버퍼를 사용할 수 있습니다.

이 메서드에서 반환된 버퍼는 사용이 완료되면 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()에서 검색된 버퍼와 반드시 같아야 하는 것은 아닙니다.

이 스트림과 연결된 사용자에 따라 제출된 버퍼에 대한 개인 정보/권한이 적용됩니다. 40ms마다 이 스트림의 오디오의 다음 40ms가 인코드 및 전송됩니다.

오디오 장애를 방지하기 위해, 연속으로 들려야 하는 오디오의 버퍼는 이 스트림에 일정한 속도로 제출되어야 합니다.

스트림 컨텍스트

앱은 pre_encode_audio_stream::set_custom_stream_context()pre_encode_audio_stream::custom_stream_context()를 사용하여 사전 인코드 오디오 스트림에서 사용자 지정 포인터 크기 조정 컨텍스트 값을 관리할 수 있습니다. 사용자 지정 스트림 컨텍스트는 게임 채팅 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:
        {
            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_streampost_decode_audio_sink_stream 클래스를 통해 사후 디코드 채팅 오디오 데이터에 대한 액세스를 제공합니다. 따라서 사용자가 채팅 오디오의 각 로컬 수신자에게 고유하게 원격 사용자의 오디오를 조작할 수 있습니다.

원본 및 싱크

인코드되지 않은 파이프라인을 사용하는 경우와는 달리, 사후 디코드 오디오 데이터 처리를 위한 모델은 post_decode_audio_source_streampost_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_createdgame_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_closedgame_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,000~200,000개 샘플이어야 합니다.

사후 인코드 오디오 검색 및 제출

앱은 post_decode_audio_source_stream::get_available_buffer_count()를 사용하여 처리할 수 있는 버퍼 수에 대한 사후 디코드 오디오 소스 스트림을 쿼리할 수 있습니다. 이 정보는 앱이 버퍼의 최소 개수가 제공될 때까지 오디오 처리를 지연하려고 할 경우에 사용할 수 있습니다. 각 사후 디코드 오디오 소스 스트림의 큐에는 10개의 버퍼가 대기하고 오디오 파이프라인에는 오디오 지연 시간이 발생합니다. 앱에서 4개 이상의 버퍼를 큐에 추가하기 전에 먼저 이 스트림이 사후에 디코드되지 않은 오디오 스트림을 드레이닝하는 것이 좋습니다.

get_next_buffer()를 사용하여 오디오 버퍼 검색

앱은 post_decode_audio_source_stream::get_next_buffer()를 사용하여 사후 디코드 오디오 소스 스트림에서 오디오 버퍼를 검색할 수 있습니다. 40밀리초마다 한 번씩 새 오디오 버퍼를 사용할 수 있습니다.

이렇게 반환된 버퍼들은 사용이 완료되면 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()에서 검색된 버퍼와 반드시 같아야 하는 것은 아닙니다.

40ms마다 이 스트림의 다음 40ms간 오디오가 렌더링됩니다. 오디오 장애를 방지하기 위해, 연속으로 들려야 하는 오디오의 버퍼는 이 스트림에 일정한 속도로 제출되어야 합니다.

개인 정보 및 믹싱

디코드 후 파이프라인의 소스 싱크 모델 때문에 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_contextpost_decode_audio_source_stream::custom_stream_context 메서드를 사용하여 디코드 후 오디오 스트림에서 사용자 지정 포인터 크기 조정 컨텍스트 값을 관리할 수 있습니다. 사용자 지정 스트림 컨텍스트는 게임 채팅 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::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 게임 개발 키트