次の方法で共有


リアルタイム オーディオ操作を使用してカスタム ボイス エフェクトを適用する

PlayFab パーティーは、リアルタイムのネットワークおよびボイス チャット ソリューションです。 ボイス チャット用に構成されている場合、PlayFab パーティーはマイク オーディオを送信し、変更されていない状態で再生します。 一部のゲームでは、空間オーディオや音声フィルターなどのカスタム オーディオ エフェクトを実装するために、ボイス チャット オーディオ バッファーにアクセスする必要があります。 この説明書では、リアルタイム オーディオ操作機能を使用して、PlayFab パーティーでボイス チャットオーディオをインターセプトおよび変更する方法のチュートリアルを提供します。

前提条件

このチュートリアルでは、PlayFab パーティーでのボイス チャットの基礎知識があることを前提としています。

プラットフォームのサポート

リアルタイム オーディオ操作は、すべてのプラットフォームで使用できるわけではありません。 リアルタイム オーディオ操作に関連付けられているメソッドは、統合されたクロスプラットフォーム ヘッダーに存在しますが、現在は Windows、Xbox、PlayStation® 5 にのみ実装されています。 このメソッドを使用すると、他のプラットフォームではエラーが返されます。

オーディオ ストリーム

リアルタイム オーディオ操作では、オーディオをライブラリから取得したり、ライブラリにオーディオを送信したりするためのオーディオ ストリームの概念が導入されています。 オーディオ ストリームには次の 2 種類があります。 1 つ目は、ソース ストリームです。 ソース ストリームは、チャット コントロールからオーディオを取得するために使用されます。 各チャット コントロールは、ボイス ストリームと呼ばれる 1 つのソース ストリームのみを持つことができます。 ローカル チャット コントロールの場合、これはマイク入力を取得するために使用されます。リモート チャット コントロールの場合、これはボイス オーディオを取得するために使用されます。 チャット コントロールの音声ストリームが存在する場合、ライブラリは、オーディオを自動的に処理するのではなく、そのチャット コントロールのソース オーディオを音声ストリームにリダイレクトします。 ローカル チャット コントロールの場合、自動的にエンコードして送信するのではなく、マイク オーディオを音声ストリームにリダイレクトすることを意味します。リモート チャット コントロールの場合、再生する各ローカル チャット コントロールに自動的に送信するのではなく、着信音声オーディオを音声ストリームにリダイレクトすることを意味します。 ソース ストリームは PartyAudioManipulationSourceStream によって表されます。

ストリームの 2 番目の種類は、シンク ストリームです。 シンク ストリームは、チャット コントロールにオーディオを送信するために使用されます。 シンク ストリームを持つことができるのはローカル チャット コントロールのみであり、それぞれ 2 つ持つことができます。 これらは、キャプチャ ストリームおよびレンダー ストリームと呼ばれます。 チャット コントロールのキャプチャ ストリームが存在する場合、ライブラリはキャプチャ ストリームからオーディオをプルして、マイクではなく他のチャット コントロールにエンコードして送信します。 チャット コントロールのレンダー ストリームが存在する場合、ライブラリはレンダー ストリームからオーディオをプルし、リモート チャット コントロールから自動的に再生されるボイス チャット オーディオに加えてこれを再生します。 キャプチャ ストリームに送信されたオーディオは、ローカル チャット コントロールのマイク入力として使用されます。レンダー ストリームに送信されたオーディオは、ローカル チャット コントロールのオーディオ出力デバイスに再生または「レンダリング」されます。 シンク ストリームは PartyAudioManipulationSinkStream によって表されます。

オーディオ ストリームの構成

既定では、ライブラリはオーディオの取得、トランスポート、再生を処理します。 そのため、チャット コントロールはオーディオ ストリームなしで作成されます。 ストリーム構成メソッド (PartyLocalChatControl::ConfigureAudioManipulationCaptureStream()PartyLocalChatControl::ConfigureAudioManipulationRenderStream()、および PartyChatControl::ConfigureAudioManipulationVoiceStream()) を使用して、チャット コントロール用に 1 つ以上のストリームを作成できます。 構成が完了すると、PartyLocalChatControl::GetAudioManipulationCaptureStream()PartyLocalChatControl::GetAudioManipulationRenderStream()、および PartyChatControl::GetAudioManipulationVoiceStream() を使用してストリームを順次取得できます。

各ストリーム構成方法では、ストリームから取得またはストリームに送信するオーディオの形式を指定できます。 サポートされている形式の詳細については、各ストリーム構成方法のリファレンス ドキュメントをご覧ください。

ソース ストリームからオーディオを取得する

PartyAudioManipulationSourceStream::GetNextBuffer() を使用して、ソース ストリームからオーディオを取得できます。 音声アクティビティが検出されると、約 40 ミリ秒ごとに新しいバッファーを使用できるようになります。 バッファーが使用できない場合、呼び出しは成功し、長さ 0 のバッファーが提供されます。 瞬時に使用可能なバッファーの合計数は、PartyAudioManipulationSourceStream::GetAvailableBufferCount() を使用して取得できます。

効率を高めるために、GetNextBuffer() は、バッファー全体をコピーするのではなく、ライブラリのメモリを指すバッファーを提供します。 必要に応じて、インプレースで変更できます。 バッファーの処理が完了したら、ライブラリがメモリを再利用できるように、PartyAudioManipulationSourceStream::ReturnBuffer() を使用してバッファーを解放する必要があります。 返される前に複数のバッファーを取得でき、バッファーを取得した順序で返す必要はありません。

シンク ストリームにオーディオを送信する

PartyAudioManipulationSinkStream::SubmitBuffer() を使用してシンク ストリームにオーディオを送信できます。 バッファーはライブラリによってコピーされ、呼び出しが完了した直後に解放できます。

40 ミリ秒ごとに、ライブラリはシンク ストリームに送信された 40 ミリ秒のオーディオを消費します。 オーディオの問題を回避するには、一定の速度でオーディオを送信する必要があります。

シナリオ

マイク オーディオ操作 (事前エンコードバッファー操作)

マイク オーディオ操作は、他のチャット コントロールに送信される前にマイク オーディオをインターセプトして変更する操作です。 これは、マイク オーディオがエンコードされて他のチャット コントロールに送信される前に変更されるため、「エンコード前のバッファー操作」と呼ばれることがあります。 ローカル チャット コントロールにこのシナリオを実装する場合、まずローカル チャット コントロールの音声ストリームとキャプチャ ストリームを構成します。 構成が完了すると、その 1 つのチャット コントロールのマイク オーディオを処理するために専用オーディオ スレッドの各ティックによって呼び出される関数は、次のようになります。

// An app-defined function that takes a microphone buffer and generates a new
// buffer that should be transmitted to other chat controls.
std::vector<uint8_t>
ProcessLocalVoiceBuffer(
    PartyMutableDataBuffer* inputBuffer
    );

void
ProcessLocalMicrophoneAudioForSingleChatControl(
    PartyLocalChatControl* chatControl
    )
{
    // Get the voice stream from which we want to retrieve audio. This provides
    // the audio generated by the chat control's input device.
    PartyAudioManipulationSourceStream* voiceStream;
    RETURN_VOID_IF_FAILED(chatControl->GetAudioManipulationVoiceStream(&voiceStream));

    // Get the capture stream to which we want to submit audio. This is used to
    // submit audio that will be transmitted to other chat controls.
    PartyAudioManipulationSinkStream* captureStream;
    RETURN_VOID_IF_FAILED(chatControl->GetAudioManipulationCaptureStream(&captureStream));

    // Get the next audio buffer from the voice stream.
    PartyMutableDataBuffer buffer;
    RETURN_VOID_IF_FAILED(voiceStream->GetNextBuffer(&buffer));

    // If we retrieved a buffer, process it.
    if (buffer.bufferByteCount > 0)
    {
        // Use the buffer we retrieved to generate a new buffer that will be
        // treated as the "real" capture input and transmitted to other chat
        // controls.
        std::vector<uint8_t> processedBuffer = ProcessLocalVoiceBuffer(&buffer);

        // Convert the buffer to a Party type.
        PartyDataBuffer partyBuffer;
        partyBuffer.bufferByteCount = static_cast<uint32_t>(processedBuffer.size());
        partyBuffer.buffer = processedBuffer.data();

        // Submit the processed buffer to the capture stream.
        PartyError error = captureStream->SubmitBuffer(&partyBuffer);
        if (PARTY_FAILED(error))
        {
            printf("Failed to submit buffer to sink stream! error = 0x%08x", error);
        }

        // Return the original buffer back to the voice stream.
        error = voiceStream->ReturnBuffer(buffer.buffer);
        if (PARTY_FAILED(error))
        {
            printf("Failed to return buffer to source stream! error = 0x%08x", error);
        }
    }
}

リモート オーディオ操作 (デコード後のバッファー操作)

リモート オーディオ操作は、各ローカル チャット コントロールにレンダリングされる前に着信オーディオをインターセプトして変更する操作です。 これは、デコードされた後、レンダリングされる前に着信オーディオが変更されるため、「エンコード後のバッファー操作」と呼ばれることがあります。 このシナリオを実装する場合、まず各リモート チャット コントロールの音声ストリームと各ローカル チャット コントロールのレンダー ストリームを構成します。 次に、オーディオ スレッドの各ティックで、各音声ストリームからオーディオをプルし、必要に応じてエフェクトを適用しながらオーディオを 1 つのストリームにミックスし、混合バッファーを各レンダー ストリームに送信します。 ゲーム シナリオによっては、ローカル チャット コントロールごとにバッファーを異なるストリームに混在させる必要があります。 着信音声オーディオを処理するために専用オーディオ スレッドの各ティックによって呼び出される関数は、次のようになります。

// This is an app-defined function that takes a local chat control and list of remote voice buffers and generates
// a single mixed buffer to submit to the local chat control's audio output.
std::vector<uint8_t>
GetOutputMixBuffer(
    PartyLocalChatControl& localChatControl,
    const std::map<PartyAudioManipulationSourceStream*, PartyMutableDataBuffer>& remoteVoiceBuffers
    );

void
ProcessRemoteVoiceAudio(
    const std::vector<PartyChatControl*>& remoteChatControls,
    const std::vector<PartyLocalChatControl*>& localChatControls
    )
{
    std::map<PartyAudioManipulationSourceStream*, PartyMutableDataBuffer> remoteVoiceBuffers;

    // Acquire voice buffers from each remote chat control.
    for (auto remoteChatControl : remoteChatControls)
    {
        // Get the voice stream for this chat control from which we will retrieve audio.
        PartyAudioManipulationSourceStream* voiceStream;
        PartyError error = remoteChatControl->GetAudioManipulationVoiceStream(&voiceStream);
        if (PARTY_FAILED(error))
        {
            printf("Failed to get voice stream! error = 0x%08x", error);
            continue;
        }

        // Get the next audio buffer from the voice stream.
        PartyMutableDataBuffer buffer;
        error = voiceStream->GetNextBuffer(&buffer);
        if (PARTY_FAILED(error))
        {
            printf("Failed to get next buffer! error = 0x%08x", error);
            continue;
        }

        // If we retrieved a buffer, cache it in the map for mixing.
        if (buffer.bufferByteCount > 0)
        {
            remoteVoiceBuffers[voiceStream] = buffer;
        }
    }

    // If we didn't acquire any source buffers, we don't have anything to mix.
    if (remoteVoiceBuffers.empty())
    {
        return;
    }

    // Mix the voice buffers and submit to each render stream.
    for (auto localChatControl : localChatControls)
    {
        // Get the render stream for this chat control to which we will submit audio.
        PartyAudioManipulationSinkStream* renderStream;
        PartyError error = localChatControl->GetAudioManipulationRenderStream(&renderStream);
        if (PARTY_FAILED(error))
        {
            printf("Failed to get render stream! error = 0x%08x", error);
            continue;
        }

        // Mix the buffers the buffers to generate a new, mixed buffer.
        std::vector<uint8_t> mixedBuffer = GetOutputMixBuffer(*localChatControl, remoteVoiceBuffers);

        // Convert the buffer to a party type.
        PartyDataBuffer partyBuffer;
        partyBuffer.bufferByteCount = static_cast<uint32_t>(mixedBuffer.size());
        partyBuffer.buffer = mixedBuffer.data();

        // Submit the mixed buffer to the render stream.
        error = renderStream->SubmitBuffer(&partyBuffer);
        if (PARTY_FAILED(error))
        {
            printf("Failed to submit buffer to render stream! error = 0x%08x", error);
        }
    }

    // Release the voice buffers.
    for (auto voiceBuffer : remoteVoiceBuffers)
    {
        // Return the voice buffer that we had cached from this voice stream.
        PartyError error = voiceBuffer.first->ReturnBuffer(voiceBuffer.second.buffer);
        if (PARTY_FAILED(error))
        {
            printf("Failed to return buffer! error = 0x%08x", error);
        }
    }
}

プライバシーとミキシングに関する考慮事項

このライブラリは、リモート チャット コントロールが音声を生成する限り、リモート チャット コントロールの音声ストリームを介してオーディオを提供します。チャット許可およびミュートを構成すると、少なくとも 1 つのローカル チャット コントロールによってオーディオが再生されます。 1 つのローカル チャット コントロールに対してオーディオを再生する必要があるが、別のローカル チャット コントロールでは再生しない場合、後者のチャット コントロールのオーディオ ミックスからオーディオを省略する必要があります。

チャット インジケーターに関する考慮事項

リモート チャット コントロールのボイス ストリームを構成しても、そのチャット インジケーターに影響はありません。 適切な UI インジケーターを選択するには、チャット インジケーターとミキシング ロジックの違いを調整するためのロジックを実装する必要がある場合があります。 たとえば、チャット インジケーターはチャット コントロールが話していることを示しているが、カスタム ミキシング ロジックがオーディオをドロップすることを選択する場合があります。

混合シナリオ

一部のシナリオでは、一部のチャット コントロールではオーディオ操作を有効にし、他のチャット コントロールでは有効にしない場合があります。 たとえば、ゲーム マッチ中に遭遇した敵プレイヤーにエフェクトを適用し、同じチームのプレイヤーには適用しない場合があります。 このようなシナリオでは、リモート オーディオ操作について前に説明した手順に従い、オーディオ エフェクトを適用するチャット コントロールの音声ストリームのみを構成できます。 ミュートとアクセス許可の構成で許可されている限り、残りのリモート チャット コントロールのオーディオは、ローカル チャット コントロールに自動的にレンダリングされます。