空間オーディオ オブジェクトを使用して空間サウンドをレンダリングする

この記事では、静的空間オーディオ オブジェクト、動的空間オーディオ オブジェクト、および Microsoft のヘッド相対転送関数 (HRTF) を使用する空間オーディオ オブジェクトを使用して空間サウンドを実装する方法を示す簡単な例をいくつか紹介します。 これらの 3 つの手法の実装手順は非常によく似ています。この記事では、各手法に対して同様に構造化されたコード例を紹介します。 実際の空間オーディオ実装の完全なエンドツーエンドの例については、Microsoft Spatial Sound samples github リポジトリを参照してください。 Xbox と Windows での空間サウンドのサポートに関する Microsoft のプラットフォーム レベルのソリューション、Windows Sonic の概要については、「空間サウンド」を参照してください。

静的空間オーディオ オブジェクトを使用してオーディオをレンダリングする

静的オーディオ オブジェクトは、AudioObjectType 列挙体で定義されている 18 個の静的オーディオ チャネルのいずれかにサウンドをレンダリングするために使用されます。 これらの各チャネルは、時間の経過と共に移動しない空間内の固定ポイントでの、実際の、または仮想化されたスピーカーを表します。 特定のデバイスで使用できる静的チャネルは、使用されている空間サウンド形式によって異なります。 サポートされている形式とそのチャンネル数の一覧については、「空間サウンド」を参照してください。

空間オーディオ ストリームを初期化するときは、ストリームが使用する使用可能な静的チャネルを指定する必要があります。 次の定数定義の例を使用して、共通のスピーカー構成を指定し、各スピーカーで使用可能なチャネルの数を取得することができます。

const AudioObjectType ChannelMask_Mono = AudioObjectType_FrontCenter;
const AudioObjectType ChannelMask_Stereo = (AudioObjectType)(AudioObjectType_FrontLeft | AudioObjectType_FrontRight);
const AudioObjectType ChannelMask_2_1 = (AudioObjectType)(ChannelMask_Stereo | AudioObjectType_LowFrequency);
const AudioObjectType ChannelMask_Quad = (AudioObjectType)(AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_BackLeft | AudioObjectType_BackRight);
const AudioObjectType ChannelMask_4_1 = (AudioObjectType)(ChannelMask_Quad | AudioObjectType_LowFrequency);
const AudioObjectType ChannelMask_5_1 = (AudioObjectType)(AudioObjectType_FrontLeft | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft | AudioObjectType_SideRight);
const AudioObjectType ChannelMask_7_1 = (AudioObjectType)(ChannelMask_5_1 | AudioObjectType_BackLeft | AudioObjectType_BackRight);

const UINT32 MaxStaticObjectCount_7_1_4 = 12;
const AudioObjectType ChannelMask_7_1_4 = (AudioObjectType)(ChannelMask_7_1 | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft | AudioObjectType_TopBackRight);

const UINT32 MaxStaticObjectCount_7_1_4_4 = 16;
const AudioObjectType ChannelMask_7_1_4_4 = (AudioObjectType)(ChannelMask_7_1_4 | AudioObjectType_BottomFrontLeft | AudioObjectType_BottomFrontRight |AudioObjectType_BottomBackLeft | AudioObjectType_BottomBackRight);

const UINT32 MaxStaticObjectCount_8_1_4_4 = 17;
const AudioObjectType ChannelMask_8_1_4_4 = (AudioObjectType)(ChannelMask_7_1_4_4 | AudioObjectType_BackCenter);

空間オーディオをレンダリングする最初の手順は、オーディオ データの送信先となるオーディオ エンドポイントを取得することです。 MMDeviceEnumerator のインスタンスを作成し、GetDefaultAudioEndpoint を呼び出して既定のオーディオ レンダリング デバイスを取得します。

HRESULT hr;
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> deviceEnum;
Microsoft::WRL::ComPtr<IMMDevice> defaultDevice;

hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&deviceEnum);
hr = deviceEnum->GetDefaultAudioEndpoint(EDataFlow::eRender, eMultimedia, &defaultDevice);

空間オーディオ ストリームを作成するときは、WAVEFORMATEX 構造体を指定してストリームで使用するオーディオ形式を指定する必要があります。 ファイルからオーディオを再生する場合、通常、形式はオーディオ ファイル形式によって決まります。 この例では、モノラル、32 ビット、48 Hz の形式を使用します。

WAVEFORMATEX format;
format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
format.wBitsPerSample = 32;
format.nChannels = 1;
format.nSamplesPerSec = 48000;
format.nBlockAlign = (format.wBitsPerSample >> 3) * format.nChannels;
format.nAvgBytesPerSec = format.nBlockAlign * format.nSamplesPerSec;
format.cbSize = 0;

空間オーディオをレンダリングする次の手順は、空間オーディオ ストリームを初期化することです。 まず、IMMDevice::Activate を呼び出して、ISpatialAudioClient のインスタンスを取得します。 ISpatialAudioClient::IsAudioObjectFormatSupported を呼び出して、使用しているオーディオ形式がサポートされていることを確認します。 オーディオ パイプラインが、より多くのオーディオ データの準備ができていることをアプリに通知するために使用するイベントを作成します。

空間オーディオ ストリームの初期化に使用される SpatialAudioObjectRenderStreamActivationParams 構造体を設定します。 この例では、[StaticObjectTypeMask] フィールドは、この記事で前に定義した「ChannelMask_Stereo」定数に設定されています。つまり、オーディオ ストリームで使用できるのは、フロント左右のチャンネルだけです。 この例では、静的オーディオ オブジェクトのみを使用し、動的オブジェクトは使用しないため、[MaxDynamicObjectCount] フィールドは「0」に設定されます。 [Category] フィールドは、システムがこのストリームのサウンドを他のオーディオ ソースとミックスする方法を定義する、「AUDIO_STREAM_CATEGORY」列挙体のメンバーに設定されます。

ISpatialAudioClient::ActivateSpatialAudioStream を呼び出してストリームをアクティブ化します。

Microsoft::WRL::ComPtr<ISpatialAudioClient> spatialAudioClient;

// Activate ISpatialAudioClient on the desired audio-device 
hr = defaultDevice->Activate(__uuidof(ISpatialAudioClient), CLSCTX_INPROC_SERVER, nullptr, (void**)&spatialAudioClient);

hr = spatialAudioClient->IsAudioObjectFormatSupported(&format);

// Create the event that will be used to signal the client for more data
HANDLE bufferCompletionEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);

SpatialAudioObjectRenderStreamActivationParams streamParams;
streamParams.ObjectFormat = &format;
streamParams.StaticObjectTypeMask = ChannelMask_Stereo;
streamParams.MinDynamicObjectCount = 0;
streamParams.MaxDynamicObjectCount = 0;
streamParams.Category = AudioCategory_SoundEffects;
streamParams.EventHandle = bufferCompletionEvent;
streamParams.NotifyObject = nullptr;

PROPVARIANT activationParams;
PropVariantInit(&activationParams);
activationParams.vt = VT_BLOB;
activationParams.blob.cbSize = sizeof(streamParams);
activationParams.blob.pBlobData = reinterpret_cast<BYTE *>(&streamParams);

Microsoft::WRL::ComPtr<ISpatialAudioObjectRenderStream> spatialAudioStream;
hr = spatialAudioClient->ActivateSpatialAudioStream(&activationParams, __uuidof(spatialAudioStream), (void**)&spatialAudioStream);

Note

Xbox One Development Kit (XDK) タイトルで ISpatialAudioClient インターフェイスを使用する場合は、IMMDeviceEnumerator::EnumAudioEndpoints または IMMDeviceEnumerator::GetDefaultAudioEndpoint を呼び出す前に、 まず EnableSpatialAudio を呼び出す必要があります。 これを行わないと、アクティブ化の呼び出しから E_NOINTERFACE エラーが返されます。 EnableSpatialAudio は XDK タイトルでのみ使用でき、Xbox One で実行されているユニバーサル Windows プラットフォーム アプリや Xbox One 以外のデバイスに対して呼び出す必要はありません。

 

静的チャネルにオーディオ データを書き込むために使用される ISpatialAudioObject の、ポインターを宣言します。 一般的なアプリでは、[StaticObjectTypeMask] フィールドで指定されたチャネルごとにオブジェクトが使用されます。 わかりやすくするために、この例では 1 つの静的オーディオ オブジェクトのみを使用しています。

// In this simple example, one object will be rendered
Microsoft::WRL::ComPtr<ISpatialAudioObject> audioObjectFrontLeft;

オーディオ レンダリング ループに入る前に、ISpatialAudioObjectRenderStream::Start を呼び出して、オーディオ データの要求を開始するようにメディア パイプラインに指示します。 この例では、カウンターを使用して、5 秒後にオーディオのレンダリングを停止します。

レンダリング ループ内で、空間オーディオ ストリームが初期化されたときに提供されるバッファー完了イベントが通知されるまで待機します。 レンダリングの種類やエンドポイントが変更されるとイベントが通知されなくなるため、イベントを待機する場合は、100 ミリ秒などの適切なタイムアウト制限を設定する必要があります。 この場合、ISpatialAudioObjectRenderStream::Reset を呼び出して、空間オーディオ ストリームのリセットを試みることができます。

次に、ISpatialAudioObjectRenderStream::BeginUpdatingAudioObjects を呼び出して、オーディオ オブジェクトのバッファーにデータを格納しようとしていることをシステムに知らせます。 このメソッドは、この例では使用されていない使用可能な動的オーディオ オブジェクトの数と、このストリームによってレンダリングされるオーディオ オブジェクトのバッファーのフレーム数を返します。

静的空間オーディオ オブジェクトがまだ作成されていない場合は、ISpatialAudioObjectRenderStream::ActivateSpatialAudioObject を呼び出して 1 つ以上を作成し、オブジェクトのオーディオがレンダリングされる静的チャネルを示す AudioObjectType 列挙から値を渡します。

次に、ISpatialAudioObject::GetBuffer を呼び出して、空間オーディオ オブジェクトのオーディオ バッファーへのポインターを取得します。 このメソッドは、バッファーのサイズ (バイト単位) も返します。 この例では、ヘルパー メソッド WriteToAudioObjectBuffer を使用して、バッファーにオーディオ データを格納します。 このメソッドは、この記事の後半で説明します。 バッファーに書き込んだ後、この例では、オブジェクトの有効期間の 5 秒に達したかどうかをチェックし、達した場合は ISpatialAudioObject::SetEndOfStream が呼び出され、このオブジェクトを使用してこれ以上オーディオは書き込まれないことと、オブジェクトはリソースを解放するために「nullptr」に設定されることをオーディオ パイプラインに知らせます。

すべてのオーディオ オブジェクトにデータを書き込んだ後、ISpatialAudioObjectRenderStream::EndUpdatingAudioObjects を呼び出して、データのレンダリングの準備ができていることをシステムに知らせます。 GetBuffer は、BeginUpdatingAudioObjectsEndUpdatingAudioObjects の呼び出しの間でのみ呼び出すことができます。

// Start streaming / rendering 
hr = spatialAudioStream->Start();

// This example will render 5 seconds of audio samples
UINT totalFrameCount = format.nSamplesPerSec * 5;

bool isRendering = true;
while (isRendering)
{
    // Wait for a signal from the audio-engine to start the next processing pass
    if (WaitForSingleObject(bufferCompletionEvent, 100) != WAIT_OBJECT_0)
    {
        hr = spatialAudioStream->Reset();

        if (hr != S_OK)
        {
            // handle the error
            break;
        }
    }

    UINT32 availableDynamicObjectCount;
    UINT32 frameCount;

    // Begin the process of sending object data and metadata
    // Get the number of dynamic objects that can be used to send object-data
    // Get the frame count that each buffer will be filled with 
    hr = spatialAudioStream->BeginUpdatingAudioObjects(&availableDynamicObjectCount, &frameCount);

    BYTE* buffer;
    UINT32 bufferLength;

    if (audioObjectFrontLeft == nullptr)
    {
        hr = spatialAudioStream->ActivateSpatialAudioObject(AudioObjectType::AudioObjectType_FrontLeft, &audioObjectFrontLeft);
        if (hr != S_OK) break;
    }

    // Get the buffer to write audio data
    hr = audioObjectFrontLeft->GetBuffer(&buffer, &bufferLength);

    if (totalFrameCount >= frameCount)
    {
        // Write audio data to the buffer
        WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), frameCount, 200.0f, format.nSamplesPerSec);

        totalFrameCount -= frameCount;
    }
    else
    {
        // Write audio data to the buffer
        WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), totalFrameCount, 750.0f, format.nSamplesPerSec);

        // Set end of stream for the last buffer 
        hr = audioObjectFrontLeft->SetEndOfStream(totalFrameCount);

        audioObjectFrontLeft = nullptr; // Release the object

        isRendering = false;
    }

    // Let the audio engine know that the object data are available for processing now
    hr = spatialAudioStream->EndUpdatingAudioObjects();
};

空間オーディオのレンダリングが完了したら、ISpatialAudioObjectRenderStream::Stop を呼び出 して空間オーディオ ストリームを停止します。 ストリームを再度使用しない場合は、ISpatialAudioObjectRenderStream::Reset を呼び出して、そのリソースを解放します。

// Stop the stream
hr = spatialAudioStream->Stop();

// Don't want to start again, so reset the stream to free its resources
hr = spatialAudioStream->Reset();

CloseHandle(bufferCompletionEvent);

WriteToAudioObjectBuffer ヘルパー メソッドは、サンプルの完全なバッファー、またはアプリで定義された時間制限で指定された残りのサンプルの数を書き込みます。 これは、たとえば、ソース オーディオ ファイル内に残っているサンプルの数によって決定することもできます。 単純な正弦波 (周波数入力パラメーターによってスケーリングされる周波数) が生成され、バッファーに書き込まれます。

void WriteToAudioObjectBuffer(FLOAT* buffer, UINT frameCount, FLOAT frequency, UINT samplingRate)
{
    const double PI = 4 * atan2(1.0, 1.0);
    static double _radPhase = 0.0;

    double step = 2 * PI * frequency / samplingRate;

    for (UINT i = 0; i < frameCount; i++)
    {
        double sample = sin(_radPhase);

        buffer[i] = FLOAT(sample);

        _radPhase += step; // next frame phase

        if (_radPhase >= 2 * PI)
        {
            _radPhase -= 2 * PI;
        }
    }
}

動的空間オーディオ オブジェクトを使用してオーディオをレンダリングする

動的オブジェクトを使用すると、ユーザーを基準にして、空間内の任意の位置からオーディオをレンダリングできます。 動的オーディオ オブジェクトの位置とボリュームは、時間の経過と共に変更される可能性があります。 通常、ゲームでは、そのゲームの世界内にある 3D オブジェクトの位置を使用して、それに関連付けられている動的オーディオ オブジェクトの位置を指定します。 次の例では、単純な構造体 My3dObject を使用して、オブジェクトを表すために必要な最小限のデータ セットを格納します。 このデータには、ISpatialAudioObject へのポインター、オブジェクトの位置、速度、ボリューム、トーンの頻度、およびオブジェクトがサウンドをレンダリングするフレームの合計数を格納する値が含まれます。

struct My3dObject
{
    Microsoft::WRL::ComPtr<ISpatialAudioObject> audioObject;
    Windows::Foundation::Numerics::float3 position;
    Windows::Foundation::Numerics::float3 velocity;
    float volume;
    float frequency; // in Hz
    UINT totalFrameCount;
};

動的オーディオ オブジェクトの実装手順は、前述の静的オーディオ オブジェクトの手順とほとんど同じです。 まず、オーディオ エンドポイントを取得します。

HRESULT hr;
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> deviceEnum;
Microsoft::WRL::ComPtr<IMMDevice> defaultDevice;

hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&deviceEnum);
hr = deviceEnum->GetDefaultAudioEndpoint(EDataFlow::eRender, eMultimedia, &defaultDevice);

次に、空間オーディオ ストリームを初期化します。 IMMDevice::Activate を呼び出して、ISpatialAudioClient のインスタンスを取得します。 ISpatialAudioClient::IsAudioObjectFormatSupported を呼び出して、使用しているオーディオ形式がサポートされていることを確認します。 オーディオ パイプラインが、より多くのオーディオ データの準備ができていることをアプリに通知するために使用するイベントを作成します。

ISpatialAudioClient::GetMaxDynamicObjectCount を呼び出して、システムでサポートされている動的オブジェクトの数を取得します。 この呼び出しで「0」が返された場合、動的空間オーディオ オブジェクトは現在のデバイスではサポートされないか、有効になりません。 空間オーディオを有効にする方法と、さまざまな空間オーディオ形式で使用できる動的オーディオ オブジェクトの数の詳細については、「空間サウンド」を参照してください。

SpatialAudioObjectRenderStreamActivationParams 構造体を設定する場合は、[MaxDynamicObjectCount] フィールドを、アプリで使用する動的オブジェクトの最大数に設定します。

ISpatialAudioClient::ActivateSpatialAudioStream を呼び出してストリームをアクティブ化します。

// Activate ISpatialAudioClient on the desired audio-device 
Microsoft::WRL::ComPtr<ISpatialAudioClient> spatialAudioClient;
hr = defaultDevice->Activate(__uuidof(ISpatialAudioClient), CLSCTX_INPROC_SERVER, nullptr, (void**)&spatialAudioClient);

hr = spatialAudioClient->IsAudioObjectFormatSupported(&format);

// Create the event that will be used to signal the client for more data
HANDLE bufferCompletionEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);

UINT32 maxDynamicObjectCount;
hr = spatialAudioClient->GetMaxDynamicObjectCount(&maxDynamicObjectCount);

if (maxDynamicObjectCount == 0)
{
    // Dynamic objects are unsupported
    return;
}

// Set the maximum number of dynamic audio objects that will be used
SpatialAudioObjectRenderStreamActivationParams streamParams;
streamParams.ObjectFormat = &format;
streamParams.StaticObjectTypeMask = AudioObjectType_None;
streamParams.MinDynamicObjectCount = 0;
streamParams.MaxDynamicObjectCount = min(maxDynamicObjectCount, 4);
streamParams.Category = AudioCategory_GameEffects;
streamParams.EventHandle = bufferCompletionEvent;
streamParams.NotifyObject = nullptr;

PROPVARIANT pv;
PropVariantInit(&pv);
pv.vt = VT_BLOB;
pv.blob.cbSize = sizeof(streamParams);
pv.blob.pBlobData = (BYTE *)&streamParams;

Microsoft::WRL::ComPtr<ISpatialAudioObjectRenderStream> spatialAudioStream;;
hr = spatialAudioClient->ActivateSpatialAudioStream(&pv, __uuidof(spatialAudioStream), (void**)&spatialAudioStream);

この例をサポートするために必要なアプリ固有のコードを次に示します。このコードでは、ランダムに配置されたオーディオ オブジェクトを動的に生成し、それらをベクターに格納します。

// Used for generating a vector of randomized My3DObject structs
std::vector<My3dObject> objectVector;
std::default_random_engine gen;
std::uniform_real_distribution<> pos_dist(-25, 25); // uniform distribution for random position
std::uniform_real_distribution<> vel_dist(-1, 1); // uniform distribution for random velocity
std::uniform_real_distribution<> vol_dist(0.5, 1.0); // uniform distribution for random volume
std::uniform_real_distribution<> pitch_dist(40, 400); // uniform distribution for random pitch
int spawnCounter = 0;

オーディオ レンダリング ループに入る前に、ISpatialAudioObjectRenderStream::Start を呼び出して、オーディオ データの要求を開始するようにメディア パイプラインに指示します。

レンダリング ループ内で、空間オーディオ ストリームが初期化されたときに提供されるバッファー完了イベントが通知されるまで待機します。 レンダリングの種類やエンドポイントが変更されるとイベントが通知されなくなるため、イベントを待機する場合は、100 ミリ秒などの適切なタイムアウト制限を設定する必要があります。 この場合、ISpatialAudioObjectRenderStream::Reset を呼び出して、空間オーディオ ストリームのリセットを試みることができます。

次に、ISpatialAudioObjectRenderStream::BeginUpdatingAudioObjects を呼び出して、オーディオ オブジェクトのバッファーにデータを格納しようとしていることをシステムに知らせます。 このメソッドは、使用可能な動的オーディオ オブジェクトの数と、このストリームによってレンダリングされるオーディオ オブジェクトのバッファーのフレーム数を返します。

スポーンのカウンターが指定した値に達するたびに、AudioObjectType_Dynamic を指定する ISpatialAudioObjectRenderStream::ActivateSpatialAudioObject を呼び出すことによって、新しい動的オーディオ オブジェクトをアクティブ化します。 使用可能なすべての動的オーディオ オブジェクトが既に割り当てられている場合、このメソッドは SPLAUDCLNT_E_NO_MORE_OBJECTS を返します。 この場合は、アプリ固有の優先順位付けに基づいて、以前にアクティブ化された 1 つ以上のオーディオ オブジェクトを解放するように選択することができます。 動的オーディオ オブジェクトが作成されると、ランダム化された位置、速度、ボリューム、および周波数の値とともに、新しい My3dObject 構造体に追加され、その後アクティブなオブジェクトの一覧に追加されます。

次に、アプリで定義された My3dObject 構造体を使用して、この例で表すアクティブなすべてのオブジェクトを反復処理します。 オーディオ オブジェクトごとに、ISpatialAudioObject::GetBuffer を呼び出して、空間オーディオ オブジェクトのオーディオ バッファーへのポインターを取得します。 このメソッドは、バッファーのサイズ (バイト単位) も返します。 バッファーにオーディオ データを格納するためのヘルパー メソッド、WriteToAudioObjectBuffer です。 バッファーに書き込んだ後、この例では、ISpatialAudioObject::SetPosition を呼び出して動的オーディオ オブジェクトの位置を更新します。 オーディオ オブジェクトのボリュームは、SetVolume を呼び出すことによっても変更できます。 オブジェクトの位置またはボリュームを更新しない場合は、前回設定された位置とボリュームが保持されます。 オブジェクトのアプリで定義された有効期間に達した場合、ISpatialAudioObject::SetEndOfStream が呼び出され、このオブジェクトを使用してこれ以上オーディオは書き込まれないことと、オブジェクトはリソースを解放するために「nullptr」に設定されることをオーディオ パイプラインに知らせます。

すべてのオーディオ オブジェクトにデータを書き込んだ後、ISpatialAudioObjectRenderStream::EndUpdatingAudioObjects を呼び出して、データのレンダリングの準備ができていることをシステムに知らせます。 GetBuffer は、BeginUpdatingAudioObjectsEndUpdatingAudioObjects の呼び出しの間でのみ呼び出すことができます。

// Start streaming / rendering 
hr = spatialAudioStream->Start();

do
{
    // Wait for a signal from the audio-engine to start the next processing pass
    if (WaitForSingleObject(bufferCompletionEvent, 100) != WAIT_OBJECT_0)
    {
        break;
    }

    UINT32 availableDynamicObjectCount;
    UINT32 frameCount;

    // Begin the process of sending object data and metadata
    // Get the number of active objects that can be used to send object-data
    // Get the frame count that each buffer will be filled with 
    hr = spatialAudioStream->BeginUpdatingAudioObjects(&availableDynamicObjectCount, &frameCount);

    BYTE* buffer;
    UINT32 bufferLength;

    // Spawn a new dynamic audio object every 200 iterations
    if (spawnCounter % 200 == 0 && spawnCounter < 1000)
    {
        // Activate a new dynamic audio object
        Microsoft::WRL::ComPtr<ISpatialAudioObject> audioObject;
        hr = spatialAudioStream->ActivateSpatialAudioObject(AudioObjectType::AudioObjectType_Dynamic, &audioObject);

        // If SPTLAUDCLNT_E_NO_MORE_OBJECTS is returned, there are no more available objects
        if (SUCCEEDED(hr))
        {
            // Init new struct with the new audio object.
            My3dObject obj = {
                audioObject,
                Windows::Foundation::Numerics::float3(static_cast<float>(pos_dist(gen)), static_cast<float>(pos_dist(gen)), static_cast<float>(pos_dist(gen))),
                Windows::Foundation::Numerics::float3(static_cast<float>(vel_dist(gen)), static_cast<float>(vel_dist(gen)), static_cast<float>(vel_dist(gen))),
                static_cast<float>(static_cast<float>(vol_dist(gen))),
                static_cast<float>(static_cast<float>(pitch_dist(gen))),
                format.nSamplesPerSec * 5 // 5 seconds of audio samples
            };

            objectVector.insert(objectVector.begin(), obj);
        }
    }
    spawnCounter++;

    // Loop through all dynamic audio objects
    std::vector<My3dObject>::iterator it = objectVector.begin();
    while (it != objectVector.end())
    {
        it->audioObject->GetBuffer(&buffer, &bufferLength);

        if (it->totalFrameCount >= frameCount)
        {
            // Write audio data to the buffer
            WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), frameCount, it->frequency, format.nSamplesPerSec);

            // Update the position and volume of the audio object
            it->audioObject->SetPosition(it->position.x, it->position.y, it->position.z);
            it->position += it->velocity;
            it->audioObject->SetVolume(it->volume);

            it->totalFrameCount -= frameCount;

            ++it;
        }
        else
        {
            // If the audio object reaches its lifetime, set EndOfStream and release the object

            // Write audio data to the buffer
            WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), it->totalFrameCount, it->frequency, format.nSamplesPerSec);

            // Set end of stream for the last buffer 
            hr = it->audioObject->SetEndOfStream(it->totalFrameCount);

            it->audioObject = nullptr; // Release the object

            it->totalFrameCount = 0;

            it = objectVector.erase(it);
        }
    }

    // Let the audio-engine know that the object data are available for processing now
    hr = spatialAudioStream->EndUpdatingAudioObjects();
} while (objectVector.size() > 0);

空間オーディオのレンダリングが完了したら、ISpatialAudioObjectRenderStream::Stop を呼び出 して空間オーディオ ストリームを停止します。 ストリームを再度使用しない場合は、ISpatialAudioObjectRenderStream::Reset を呼び出して、そのリソースを解放します。

// Stop the stream 
hr = spatialAudioStream->Stop();

// We don't want to start again, so reset the stream to free it's resources.
hr = spatialAudioStream->Reset();

CloseHandle(bufferCompletionEvent);

HRTF の動的空間オーディオ オブジェクトを使用してオーディオをレンダリングする

別の API のセットである ISpatialAudioRenderStreamForHrtfISpatialAudioObjectForHrtf では、Microsoft のヘッド相対転送関数 (HRTF) を使用してサウンドを減衰させる空間オーディオを有効にし、時間の経過と共に変更される可能性のある空間内のエミッタの位置を、ユーザーを基準にしてシミュレートします。 HRTF オーディオ オブジェクトを使用すると、位置に加えて、空間内の向き、円錐型やカーディオイド型などのサウンドを発する指向性、オブジェクトが仮想リスナーの近くおよび遠くに移動する減衰モデルを指定できます。 これらの HRTF インターフェイスは、ユーザーがデバイスの空間オーディオ エンジンとして、Windows Sonic for Headphones を選択した場合にのみ使用できます。 Windows Sonic for Headphones を使用するようにデバイスを構成する方法については、「空間サウンド」を参照してください。

ISpatialAudioRenderStreamForHrtf APIISpatialAudioObjectForHrtf API を使用すると、アプリケーションは Windows Sonic for Headphones レンダリング パスを直接明示的に使用できます。 これらの API では、Dolby Atmos for Home Theater や Dolby Atmos for Headphones などの空間サウンド形式や、サウンド コントロール パネルを介したコンシューマー制御の出力形式切り替えや、スピーカー経由での再生はサポートされていません。 これらのインターフェイスは、Windows Sonic for Headphones 固有の機能 (環境プリセットや、一般的なコンテンツ作成パイプラインの外部でプログラムで指定された距離ベースのロールオフなど) を使用する Windows Mixed Reality アプリケーションで使用することを目的としています。 ほとんどのゲームと仮想現実のシナリオでは、代わりに ISpatialAudioClient を使用することをお勧めします。 両方の API セットの実装手順はほぼ同じであるため、両方のテクノロジを実装し、現在のデバイスで使用できる機能に応じて、実行時に切り替えることができます。

Mixed Reality アプリでは、通常、仮想世界での 3D オブジェクトの位置を使用して、それに関連付けられている動的オーディオ オブジェクトの位置を指定します。 次の例では、単純な構造体 My3dObjectForHrtf を使用して、オブジェクトを表すために必要な最小限のデータ セットを格納します。 このデータには、ISpatialAudioObjectForHrtf へのポインター、オブジェクトの位置、向き、速度、トーンの頻度、およびオブジェクトがサウンドをレンダリングするフレームの合計数を格納する値が含まれます。

struct My3dObjectForHrtf
{
    Microsoft::WRL::ComPtr<ISpatialAudioObjectForHrtf> audioObject;
    Windows::Foundation::Numerics::float3 position;
    Windows::Foundation::Numerics::float3 velocity;
    float yRotationRads;
    float deltaYRotation;
    float frequency; // in Hz
    UINT totalFrameCount;
};

動的 HRTF オーディオ オブジェクトの実装手順は、前のセクションで説明した動的オーディオ オブジェクトの手順とほとんど同じです。 まず、オーディオ エンドポイントを取得します。

HRESULT hr;
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> deviceEnum;
Microsoft::WRL::ComPtr<IMMDevice> defaultDevice;

hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&deviceEnum);
hr = deviceEnum->GetDefaultAudioEndpoint(EDataFlow::eRender, eMultimedia, &defaultDevice);

次に、空間オーディオ ストリームを初期化します。 IMMDevice::Activate を呼び出して、ISpatialAudioClient のインスタンスを取得します。 ISpatialAudioClient::IsAudioObjectFormatSupported を呼び出して、使用しているオーディオ形式がサポートされていることを確認します。 オーディオ パイプラインが、より多くのオーディオ データの準備ができていることをアプリに通知するために使用するイベントを作成します。

ISpatialAudioClient::GetMaxDynamicObjectCount を呼び出して、システムでサポートされている動的オブジェクトの数を取得します。 この呼び出しで「0」が返された場合、動的空間オーディオ オブジェクトは現在のデバイスではサポートされないか、有効になりません。 空間オーディオを有効にする方法と、さまざまな空間オーディオ形式で使用できる動的オーディオ オブジェクトの数の詳細については、「空間サウンド」を参照してください。

SpatialAudioHrtfActivationParams 構造体を設定する場合は、[MaxDynamicObjectCount] フィールドを、アプリで使用する動的オブジェクトの最大数に設定します。 HRTF のアクティブ化パラメーターでは、SpatialAudioHrtfDistanceDecaySpatialAudioHrtfDirectivityUnionSpatialAudioHrtfEnvironmentTypeSpatialAudioHrtfOrientation など、いくつかの追加パラメーターがサポートされています。これらのパラメーターは、ストリームから作成された新しいオブジェクトに対してこれらの設定の既定値を指定します。 これらのパラメーターは省略可能です。 既定値を指定しないようにする場合は、フィールドを nullptr に設定します。

ISpatialAudioClient::ActivateSpatialAudioStream を呼び出してストリームをアクティブ化します。

// Activate ISpatialAudioClient on the desired audio-device 
Microsoft::WRL::ComPtr<ISpatialAudioClient> spatialAudioClient;
hr = defaultDevice->Activate(__uuidof(ISpatialAudioClient), CLSCTX_INPROC_SERVER, nullptr, (void**)&spatialAudioClient);

Microsoft::WRL::ComPtr<ISpatialAudioObjectRenderStreamForHrtf>  spatialAudioStreamForHrtf;
hr = spatialAudioClient->IsSpatialAudioStreamAvailable(__uuidof(spatialAudioStreamForHrtf), NULL);

hr = spatialAudioClient->IsAudioObjectFormatSupported(&format);

// Create the event that will be used to signal the client for more data
HANDLE bufferCompletionEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);

UINT32 maxDynamicObjectCount;
hr = spatialAudioClient->GetMaxDynamicObjectCount(&maxDynamicObjectCount);

SpatialAudioHrtfActivationParams streamParams;
streamParams.ObjectFormat = &format;
streamParams.StaticObjectTypeMask = AudioObjectType_None;
streamParams.MinDynamicObjectCount = 0;
streamParams.MaxDynamicObjectCount = min(maxDynamicObjectCount, 4);
streamParams.Category = AudioCategory_GameEffects;
streamParams.EventHandle = bufferCompletionEvent;
streamParams.NotifyObject = NULL;

SpatialAudioHrtfDistanceDecay decayModel;
decayModel.CutoffDistance = 100;
decayModel.MaxGain = 3.98f;
decayModel.MinGain = float(1.58439 * pow(10, -5));
decayModel.Type = SpatialAudioHrtfDistanceDecayType::SpatialAudioHrtfDistanceDecay_NaturalDecay;
decayModel.UnityGainDistance = 1;

streamParams.DistanceDecay = &decayModel;

SpatialAudioHrtfDirectivity directivity;
directivity.Type = SpatialAudioHrtfDirectivityType::SpatialAudioHrtfDirectivity_Cone;
directivity.Scaling = 1.0f;

SpatialAudioHrtfDirectivityCone cone;
cone.directivity = directivity;
cone.InnerAngle = 0.1f;
cone.OuterAngle = 0.2f;

SpatialAudioHrtfDirectivityUnion directivityUnion;
directivityUnion.Cone = cone;
streamParams.Directivity = &directivityUnion;

SpatialAudioHrtfEnvironmentType environment = SpatialAudioHrtfEnvironmentType::SpatialAudioHrtfEnvironment_Large;
streamParams.Environment = &environment;

SpatialAudioHrtfOrientation orientation = { 1,0,0,0,1,0,0,0,1 }; // identity matrix
streamParams.Orientation = &orientation;

PROPVARIANT pv;
PropVariantInit(&pv);
pv.vt = VT_BLOB;
pv.blob.cbSize = sizeof(streamParams);
pv.blob.pBlobData = (BYTE *)&streamParams;

hr = spatialAudioClient->ActivateSpatialAudioStream(&pv, __uuidof(spatialAudioStreamForHrtf), (void**)&spatialAudioStreamForHrtf);

この例をサポートするために必要なアプリ固有のコードを次に示します。このコードでは、ランダムに配置されたオーディオ オブジェクトを動的に生成し、それらをベクターに格納します。

// Used for generating a vector of randomized My3DObject structs
std::vector<My3dObjectForHrtf> objectVector;
std::default_random_engine gen;
std::uniform_real_distribution<> pos_dist(-10, 10); // uniform distribution for random position
std::uniform_real_distribution<> vel_dist(-.02, .02); // uniform distribution for random velocity
std::uniform_real_distribution<> yRotation_dist(-3.14, 3.14); // uniform distribution for y-axis rotation
std::uniform_real_distribution<> deltaYRotation_dist(.01, .02); // uniform distribution for y-axis rotation
std::uniform_real_distribution<> pitch_dist(40, 400); // uniform distribution for random pitch

int spawnCounter = 0;

オーディオ レンダリング ループに入る前に、ISpatialAudioObjectRenderStreamForHrtf::Start を呼び出して、オーディオ データの要求を開始するようにメディア パイプラインに指示します。

レンダリング ループ内で、空間オーディオ ストリームが初期化されたときに提供されるバッファー完了イベントが通知されるまで待機します。 レンダリングの種類やエンドポイントが変更されるとイベントが通知されなくなるため、イベントを待機する場合は、100 ミリ秒などの適切なタイムアウト制限を設定する必要があります。 この場合、ISpatialAudioRenderStreamForHrtf::Reset を呼び出して、空間オーディオ ストリームのリセットを試みることができます。

次に、ISpatialAudioRenderStreamForHrtf::BeginUpdatingAudioObjects を呼び出して、オーディオ オブジェクトのバッファーにデータを格納しようとしていることをシステムに知らせます。 このメソッドは、この例では使用されていない使用可能な動的オーディオ オブジェクトの数と、このストリームによってレンダリングされるオーディオ オブジェクトのバッファーのフレーム数を返します。

スポーンのカウンターが指定した値に達するたびに、AudioObjectType_Dynamic を指定する ISpatialAudioRenderStreamForHrtf::ActivateSpatialAudioObjectForHrtf を呼び出すことによって、新しい動的オーディオ オブジェクトをアクティブ化します。 使用可能なすべての動的オーディオ オブジェクトが既に割り当てられている場合、このメソッドは SPLAUDCLNT_E_NO_MORE_OBJECTS を返します。 この場合は、アプリ固有の優先順位付けに基づいて、以前にアクティブ化された 1 つ以上のオーディオ オブジェクトを解放するように選択することができます。 動的オーディオ オブジェクトが作成されると、ランダム化された位置、回転、速度、ボリューム、および周波数の値とともに、新しい My3dObjectForHrtf 構造体に追加され、その後アクティブなオブジェクトの一覧に追加されます。

次に、アプリで定義された My3dObjectForHrtf 構造体を使用して、この例で表すアクティブなすべてのオブジェクトを反復処理します。 オーディオ オブジェクトごとに、ISpatialAudioObjectForHrtf::GetBuffer を呼び出して、空間オーディオ オブジェクトのオーディオ バッファーへのポインターを取得します。 このメソッドは、バッファーのサイズ (バイト単位) も返します。 この記事で前述した、バッファーにオーディオ データを格納するためのヘルパー メソッド、WriteToAudioObjectBuffer です。 バッファーに書き込んだ後、この例では、ISpatialAudioObjectForHrtf::SetPositionISpatialAudioObjectForHrtf::SetOrientation を呼び出して、HRTF オーディオ オブジェクトの位置と向きを更新します。 この例では、ヘルパー メソッドの CalculateEmitterConeOrientationMatrix を使用し、3D オブジェクトが指している方向を指定して方向行列を計算します。 このメソッドの実装を次に示します。 オーディオ オブジェクトのボリュームは、ISpatialAudioObjectForHrtf::SetGain を呼び出すことによっても変更できます。 オブジェクトの位置、向き、またはボリュームを更新しない場合は、前回設定された位置、向き、およびボリュームが保持されます。 オブジェクトのアプリで定義された有効期間に達した場合、ISpatialAudioObjectForHrtf::SetEndOfStream が呼び出され、このオブジェクトを使用してこれ以上オーディオは書き込まれないことと、オブジェクトはリソースを解放するために「nullptr」に設定されることをオーディオ パイプラインに知らせます。

すべてのオーディオ オブジェクトにデータを書き込んだ後、ISpatialAudioRenderStreamForHrtf::EndUpdatingAudioObjects を呼び出して、データのレンダリングの準備ができていることをシステムに知らせます。 GetBuffer は、BeginUpdatingAudioObjectsEndUpdatingAudioObjects の呼び出しの間でのみ呼び出すことができます。

// Start streaming / rendering 
hr = spatialAudioStreamForHrtf->Start();

do
{
    // Wait for a signal from the audio-engine to start the next processing pass
    if (WaitForSingleObject(bufferCompletionEvent, 100) != WAIT_OBJECT_0)
    {
        break;
    }

    UINT32 availableDynamicObjectCount;
    UINT32 frameCount;

    // Begin the process of sending object data and metadata
    // Get the number of active objects that can be used to send object-data
    // Get the frame count that each buffer will be filled with 
    hr = spatialAudioStreamForHrtf->BeginUpdatingAudioObjects(&availableDynamicObjectCount, &frameCount);

    BYTE* buffer;
    UINT32 bufferLength;

    // Spawn a new dynamic audio object every 200 iterations
    if (spawnCounter % 200 == 0 && spawnCounter < 1000)
    {
        // Activate a new dynamic audio object
        Microsoft::WRL::ComPtr<ISpatialAudioObjectForHrtf> audioObject;
        hr = spatialAudioStreamForHrtf->ActivateSpatialAudioObjectForHrtf(AudioObjectType::AudioObjectType_Dynamic, &audioObject);

        // If SPTLAUDCLNT_E_NO_MORE_OBJECTS is returned, there are no more available objects
        if (SUCCEEDED(hr))
        {
            // Init new struct with the new audio object.
            My3dObjectForHrtf obj = { audioObject,
                Windows::Foundation::Numerics::float3(static_cast<float>(pos_dist(gen)), static_cast<float>(pos_dist(gen)), static_cast<float>(pos_dist(gen))),
                Windows::Foundation::Numerics::float3(static_cast<float>(vel_dist(gen)), static_cast<float>(vel_dist(gen)), static_cast<float>(vel_dist(gen))),
                static_cast<float>(static_cast<float>(yRotation_dist(gen))),
                static_cast<float>(static_cast<float>(deltaYRotation_dist(gen))),
                static_cast<float>(static_cast<float>(pitch_dist(gen))),
                format.nSamplesPerSec * 5 // 5 seconds of audio samples
            };

            objectVector.insert(objectVector.begin(), obj);
        }
    }
    spawnCounter++;

    // Loop through all dynamic audio objects
    std::vector<My3dObjectForHrtf>::iterator it = objectVector.begin();
    while (it != objectVector.end())
    {
        it->audioObject->GetBuffer(&buffer, &bufferLength);

        if (it->totalFrameCount >= frameCount)
        {
            // Write audio data to the buffer
            WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), frameCount, it->frequency, format.nSamplesPerSec);

            // Update the position and volume of the audio object
            it->audioObject->SetPosition(it->position.x, it->position.y, it->position.z);
            it->position += it->velocity;


            Windows::Foundation::Numerics::float3 emitterDirection = Windows::Foundation::Numerics::float3(cos(it->yRotationRads), 0, sin(it->yRotationRads));
            Windows::Foundation::Numerics::float3 listenerDirection = Windows::Foundation::Numerics::float3(0, 0, 1);
            DirectX::XMFLOAT4X4 rotationMatrix;

            DirectX::XMMATRIX rotation = CalculateEmitterConeOrientationMatrix(emitterDirection, listenerDirection);
            XMStoreFloat4x4(&rotationMatrix, rotation);

            SpatialAudioHrtfOrientation orientation = {
                rotationMatrix._11, rotationMatrix._12, rotationMatrix._13,
                rotationMatrix._21, rotationMatrix._22, rotationMatrix._23,
                rotationMatrix._31, rotationMatrix._32, rotationMatrix._33
            };

            it->audioObject->SetOrientation(&orientation);
            it->yRotationRads += it->deltaYRotation;

            it->totalFrameCount -= frameCount;

            ++it;
        }
        else
        {
            // If the audio object reaches its lifetime, set EndOfStream and release the object

            // Write audio data to the buffer
            WriteToAudioObjectBuffer(reinterpret_cast<float*>(buffer), it->totalFrameCount, it->frequency, format.nSamplesPerSec);

            // Set end of stream for the last buffer 
            hr = it->audioObject->SetEndOfStream(it->totalFrameCount);

            it->audioObject = nullptr; // Release the object

            it->totalFrameCount = 0;

            it = objectVector.erase(it);
        }
    }

    // Let the audio-engine know that the object data are available for processing now
    hr = spatialAudioStreamForHrtf->EndUpdatingAudioObjects();

} while (objectVector.size() > 0);

空間オーディオのレンダリングが完了したら、ISpatialAudioRenderStreamForHrtf::Stop を呼び出 して空間オーディオ ストリームを停止します。 ストリームを再度使用しない場合は、ISpatialAudioRenderStreamForHrtf::Reset を呼び出して、そのリソースを解放します。

// Stop the stream 
hr = spatialAudioStreamForHrtf->Stop();

// We don't want to start again, so reset the stream to free it's resources.
hr = spatialAudioStreamForHrtf->Reset();

CloseHandle(bufferCompletionEvent);

次のコード例は、3D オブジェクトが指している方向を指定して方向行列を計算するために前の例で使用した、CalculateEmitterConeOrientationMatrix ヘルパー メソッドの実装を示しています。

DirectX::XMMATRIX CalculateEmitterConeOrientationMatrix(Windows::Foundation::Numerics::float3 listenerOrientationFront, Windows::Foundation::Numerics::float3 emitterDirection)
{
    DirectX::XMVECTOR vListenerDirection = DirectX::XMLoadFloat3(&listenerOrientationFront);
    DirectX::XMVECTOR vEmitterDirection = DirectX::XMLoadFloat3(&emitterDirection);
    DirectX::XMVECTOR vCross = DirectX::XMVector3Cross(vListenerDirection, vEmitterDirection);
    DirectX::XMVECTOR vDot = DirectX::XMVector3Dot(vListenerDirection, vEmitterDirection);
    DirectX::XMVECTOR vAngle = DirectX::XMVectorACos(vDot);
    float angle = DirectX::XMVectorGetX(vAngle);

    // The angle must be non-zero
    if (fabsf(angle) > FLT_EPSILON)
    {
        // And less than PI
        if (fabsf(angle) < DirectX::XM_PI)
        {
            return DirectX::XMMatrixRotationAxis(vCross, angle);
        }

        // If equal to PI, find any other non-collinear vector to generate the perpendicular vector to rotate about
        else
        {
            DirectX::XMFLOAT3 vector = { 1.0f, 1.0f, 1.0f };
            if (listenerOrientationFront.x != 0.0f)
            {
                vector.x = -listenerOrientationFront.x;
            }
            else if (listenerOrientationFront.y != 0.0f)
            {
                vector.y = -listenerOrientationFront.y;
            }
            else // if (_listenerOrientationFront.z != 0.0f)
            {
                vector.z = -listenerOrientationFront.z;
            }
            DirectX::XMVECTOR vVector = DirectX::XMLoadFloat3(&vector);
            vVector = DirectX::XMVector3Normalize(vVector);
            vCross = DirectX::XMVector3Cross(vVector, vEmitterDirection);
            return DirectX::XMMatrixRotationAxis(vCross, angle);
        }
    }

    // If the angle is zero, use an identity matrix
    return DirectX::XMMatrixIdentity();
}

立体音響

ISpatialAudioClient

ISpatialAudioObject