Freigeben über


Rendern von räumlichem Sound mithilfe räumlicher Audioobjekte

In diesem Artikel werden einige einfache Beispiele vorgestellt, die veranschaulichen, wie Räumlicher Sound mithilfe statischer räumlicher Audioobjekte, dynamischer räumlicher Audioobjekte und räumlicher Audioobjekte implementiert wird, die die Head Relative Transfer Function (HRTF) von Microsoft verwenden. Die Implementierungsschritte für alle drei Dieser Techniken sind sehr ähnlich, und dieser Artikel enthält ein ähnlich strukturiertes Codebeispiel für jede Technik. Vollständige End-to-End-Beispiele für reale räumliche Audioimplementierungen finden Sie unter Github-Repository microsoft Spatial Sound samples. Eine Übersicht über Windows Sonic, die plattformbasierte Lösung von Microsoft für die Unterstützung räumlicher Sound auf Xbox und Windows, finden Sie unter Spatial Sound.

Rendern von Audio mithilfe statischer räumlicher Audioobjekte

Ein statisches Audioobjekt wird verwendet, um Sound auf einem von 18 statischen Audiokanälen zu rendern, die in der AudioObjectType-Enumeration definiert sind. Jeder dieser Kanäle stellt einen realen oder virtualisierten Lautsprecher an einem festen Punkt im Raum dar, der sich im Laufe der Zeit nicht bewegt. Die statischen Kanäle, die auf einem bestimmten Gerät verfügbar sind, hängen vom verwendeten räumlichen Soundformat ab. Eine Liste der unterstützten Formate und deren Kanalanzahl finden Sie unter Räumlicher Sound.

Wenn Sie einen räumlichen Audiostream initialisieren, müssen Sie angeben, welche der verfügbaren statischen Kanäle der Stream verwendet. Die folgenden Beispielkonstantendefinitionen können verwendet werden, um allgemeine Sprecherkonfigurationen anzugeben und die Anzahl der für jeden verfügbaren Kanäle abzurufen.

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);

Der erste Schritt beim Rendern von räumlichem Audio besteht darin, den Audioendpunkt abzurufen, an den Audiodaten gesendet werden. Erstellen Sie eine instance von MMDeviceEnumerator, und rufen Sie GetDefaultAudioEndpoint auf, um das Standard-Audiorenderinggerät abzurufen.

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);

Wenn Sie einen räumlichen Audiostream erstellen, müssen Sie das Vom Stream verwendete Audioformat angeben, indem Sie eine WAVEFORMATEX-Struktur bereitstellen. Wenn Sie Audio aus einer Datei wiedergeben, wird das Format in der Regel durch das Dateiformat der Audiodatei bestimmt. In diesem Beispiel wird ein Mono-, 32-Bit- und 48Hz-Format verwendet.

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;

Der nächste Schritt beim Rendern räumlicher Audiodaten besteht darin, einen räumlichen Audiodatenstrom zu initialisieren. Rufen Sie zunächst eine instance von ISpatialAudioClient ab, indem Sie IMMDevice::Activate aufrufen. Rufen Sie ISpatialAudioClient::IsAudioObjectFormatSupported auf, um sicherzustellen, dass das verwendete Audioformat unterstützt wird. Erstellen Sie ein Ereignis, das die Audiopipeline verwendet, um die App zu benachrichtigen, dass sie für weitere Audiodaten bereit ist.

Füllen Sie eine SpatialAudioObjectRenderStreamActivationParams-Struktur auf, die zum Initialisieren des räumlichen Audiodatenstroms verwendet wird. In diesem Beispiel wird das StaticObjectTypeMask-Feld auf die ChannelMask_Stereo-Konstante festgelegt, die zuvor in diesem Artikel definiert wurde, was bedeutet, dass nur die vorderen rechten und linken Kanäle vom Audiostream verwendet werden können. Da in diesem Beispiel nur statische Audioobjekte und keine dynamischen Objekte verwendet werden, ist das Feld MaxDynamicObjectCount auf 0 festgelegt. Das Feld Kategorie ist auf ein Element der AUDIO_STREAM_CATEGORY-Enumeration festgelegt, das definiert, wie das System den Sound aus diesem Stream mit anderen Audioquellen mischt.

Rufen Sie ISpatialAudioClient::ActivateSpatialAudioStream auf, um den Stream zu aktivieren.

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);

Hinweis

Wenn Sie die ISpatialAudioClient-Schnittstellen in einem XDK-Titel (Xbox One Development Kit) verwenden, müssen Sie zuerst EnableSpatialAudio aufrufen, bevor Sie IMMDeviceEnumerator::EnumAudioEndpoints oder IMMDeviceEnumerator::GetDefaultAudioEndpoint aufrufen. Andernfalls wird ein E_NOINTERFACE Fehler vom Aufruf von Activate zurückgegeben. EnableSpatialAudio ist nur für XDK-Titel verfügbar und muss weder für Universelle Windows-Plattform Apps aufgerufen werden, die auf Xbox One ausgeführt werden, noch für Nicht-Xbox One-Geräte.

 

Deklarieren Sie einen Zeiger für ein ISpatialAudioObject , das zum Schreiben von Audiodaten in einen statischen Kanal verwendet wird. Typische Apps verwenden ein Objekt für jeden Kanal, der im Feld StaticObjectTypeMask angegeben ist. Der Einfachheit halber verwendet dieses Beispiel nur ein einzelnes statisches Audioobjekt.

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

Rufen Sie vor dem Betreten der Audiorenderschleife ISpatialAudioObjectRenderStream::Start auf, um die Medienpipeline anzuweisen, Audiodaten anzufordern. In diesem Beispiel wird ein Zähler verwendet, um das Rendering von Audio nach 5 Sekunden zu beenden.

Warten Sie in der Renderschleife, bis das Pufferabschlussereignis signalisiert wird, das bei der Initialisierung des räumlichen Audiodatenstroms angegeben wurde. Sie sollten ein angemessenes Timeoutlimit wie 100 ms festlegen, wenn Sie auf das Ereignis warten, da jede Änderung des Rendertyps oder -endpunkts dazu führt, dass dieses Ereignis nie signalisiert wird. In diesem Fall können Sie ISpatialAudioObjectRenderStream::Reset aufrufen, um zu versuchen, den räumlichen Audiostream zurückzusetzen.

Rufen Sie als Nächstes ISpatialAudioObjectRenderStream::BeginUpdatingAudioObjects auf, um das System darüber zu informieren, dass Sie die Puffer der Audioobjekte mit Daten füllen. Diese Methode gibt die Anzahl der verfügbaren dynamischen Audioobjekte zurück, die in diesem Beispiel nicht verwendet werden, und die Frameanzahl des Puffers für Audioobjekte, die von diesem Stream gerendert werden.

Wenn noch kein statisches räumliches Audioobjekt erstellt wurde, erstellen Sie ein oder mehrere, indem Sie ISpatialAudioObjectRenderStream::ActivateSpatialAudioObject aufrufen und einen Wert aus der AudioObjectType-Enumeration übergeben, der den statischen Kanal angibt, in dem die Audiodaten des Objekts gerendert werden.

Rufen Sie als Nächstes ISpatialAudioObject::GetBuffer auf, um einen Zeiger auf den Audiopuffer des räumlichen Audioobjekts zu erhalten. Diese Methode gibt auch die Größe des Puffers in Bytes zurück. In diesem Beispiel wird die Hilfsmethode WriteToAudioObjectBuffer verwendet, um den Puffer mit Audiodaten zu füllen. Diese Methode wird weiter unten in diesem Artikel gezeigt. Nach dem Schreiben in den Puffer wird im Beispiel überprüft, ob die Lebensdauer des Objekts von 5 Sekunden erreicht wurde, und wenn ja, wird ISpatialAudioObject::SetEndOfStream aufgerufen, um die Audiopipeline darüber zu informieren, dass mit diesem Objekt kein Audio mehr geschrieben wird und das Objekt auf nullptr festgelegt ist, um seine Ressourcen freizugeben.

Nachdem Sie Daten in alle Ihre Audioobjekte geschrieben haben, rufen Sie ISpatialAudioObjectRenderStream::EndUpdatingAudioObjects auf, um das System darüber zu informieren, dass die Daten zum Rendern bereit sind. Sie können GetBuffer nur zwischen einem Aufruf von BeginUpdatingAudioObjects und EndUpdatingAudioObjects aufrufen.

// 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();
};

Wenn Sie mit dem Rendern von räumlichem Audio fertig sind, beenden Sie den räumlichen Audiostream, indem Sie ISpatialAudioObjectRenderStream::Stop aufrufen. Wenn Sie den Stream nicht erneut verwenden möchten, geben Sie die Ressourcen frei, indem Sie ISpatialAudioObjectRenderStream::Reset aufrufen.

// 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);

Die WriteToAudioObjectBuffer-Hilfsmethode schreibt entweder einen vollständigen Puffer von Beispielen oder die Anzahl der verbleibenden Beispiele, die durch unser app-definiertes Zeitlimit angegeben ist. Dies kann z. B. auch durch die Anzahl der in einer Quellaudiodatei verbleibenden Beispiele bestimmt werden. Eine einfache Sin-Welle, deren Häufigkeit durch den Frequenzeingabeparameter skaliert wird, wird generiert und in den Puffer geschrieben.

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;
        }
    }
}

Rendern von Audio mithilfe von dynamischen räumlichen Audioobjekten

Mit dynamischen Objekten können Sie Audio aus einer beliebigen Position im Raum relativ zum Benutzer rendern. Position und Lautstärke eines dynamischen Audioobjekts können im Laufe der Zeit geändert werden. Spiele verwenden in der Regel die Position eines 3D-Objekts in der Spielwelt, um die Position des dynamischen Audioobjekts anzugeben, das diesem zugeordnet ist. Im folgenden Beispiel wird eine einfache Struktur, My3dObject, verwendet, um den mindesten Datensatz zu speichern, der zum Darstellen eines Objekts erforderlich ist. Diese Daten umfassen einen Zeiger auf ein ISpatialAudioObject, die Position, Geschwindigkeit, Lautstärke und Tonfrequenz des Objekts sowie einen Wert, der die Gesamtzahl der Frames speichert, für die das Objekt Sound rendern soll.

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;
};

Die Implementierungsschritte für dynamische Audioobjekte sind weitgehend identisch mit den oben beschriebenen Schritten für statische Audioobjekte. Rufen Sie zunächst einen Audioendpunkt ab.

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);

Initialisieren Sie als Nächstes den räumlichen Audiostream. Rufen Sie eine instance von ISpatialAudioClient ab, indem Sie IMMDevice::Activate aufrufen. Rufen Sie ISpatialAudioClient::IsAudioObjectFormatSupported auf, um sicherzustellen, dass das verwendete Audioformat unterstützt wird. Erstellen Sie ein Ereignis, das die Audiopipeline verwendet, um die App zu benachrichtigen, dass sie für weitere Audiodaten bereit ist.

Rufen Sie ISpatialAudioClient::GetMaxDynamicObjectCount auf, um die Anzahl der vom System unterstützten dynamischen Objekte abzurufen. Wenn dieser Aufruf 0 zurückgibt, werden dynamische räumliche Audioobjekte auf dem aktuellen Gerät nicht unterstützt oder aktiviert. Informationen zum Aktivieren von räumlichem Audio und zur Anzahl dynamischer Audioobjekte, die für verschiedene räumliche Audioformate verfügbar sind, finden Sie unter Spatial Sound.

Legen Sie beim Auffüllen der SpatialAudioObjectRenderStreamActivationParams-Struktur das Feld MaxDynamicObjectCount auf die maximale Anzahl dynamischer Objekte fest, die Ihre App verwendet.

Rufen Sie ISpatialAudioClient::ActivateSpatialAudioStream auf, um den Stream zu aktivieren.

// 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);

Im Folgenden finden Sie einen App-spezifischen Code, der dieses Beispiel unterstützen muss. Dadurch werden zufällig positionierte Audioobjekte dynamisch erzeugt und in einem Vektor gespeichert.

// 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;

Rufen Sie vor dem Betreten der Audiorenderschleife ISpatialAudioObjectRenderStream::Start auf, um die Medienpipeline anzuweisen, Audiodaten anzufordern.

Warten Sie in der Renderschleife, bis das Pufferabschlussereignis, das wir bei der Initialisierung des räumlichen Audiodatenstroms bereitgestellt haben, um signalisiert zu werden. Sie sollten ein angemessenes Timeoutlimit wie 100 ms festlegen, wenn Sie auf das Ereignis warten, da jede Änderung des Rendertyps oder -endpunkts dazu führt, dass dieses Ereignis nie signalisiert wird. In diesem Fall können Sie ISpatialAudioObjectRenderStream::Reset aufrufen, um zu versuchen, den räumlichen Audiostream zurückzusetzen.

Rufen Sie als Nächstes ISpatialAudioObjectRenderStream::BeginUpdatingAudioObjects auf, um das System darüber zu informieren, dass Sie die Puffer der Audioobjekte mit Daten füllen. Diese Methode gibt die Anzahl der verfügbaren dynamischen Audioobjekte und die Frameanzahl des Puffers für Audioobjekte zurück, die von diesem Stream gerendert werden.

Immer wenn der Spawnzähler den angegebenen Wert erreicht, aktivieren wir ein neues dynamisches Audioobjekt, indem wir ISpatialAudioObjectRenderStream:ActivateSpatialAudioObject aufrufen und AudioObjectType_Dynamic angeben. Wenn alle verfügbaren dynamischen Audioobjekte bereits zugeordnet wurden, gibt diese Methode SPLAUDCLNT_E_NO_MORE_OBJECTS zurück. In diesem Fall können Sie ein oder mehrere zuvor aktivierte Audioobjekte basierend auf Ihrer app-spezifischen Priorisierung freigeben. Nachdem das dynamische Audioobjekt erstellt wurde, wird es einer neuen My3dObject-Struktur mit zufälligen Positions-, Geschwindigkeits-, Lautstärke- und Häufigkeitswerten hinzugefügt, die dann der Liste der aktiven Objekte hinzugefügt werden.

Als Nächstes durchlaufen Sie alle aktiven Objekte, die in diesem Beispiel mit der app-definierten My3dObject-Struktur dargestellt werden. Rufen Sie für jedes Audioobjekt ISpatialAudioObject::GetBuffer auf, um einen Zeiger auf den Audiopuffer des räumlichen Audioobjekts zu erhalten. Diese Methode gibt auch die Größe des Puffers in Bytes zurück. Die Hilfsmethode WriteToAudioObjectBuffer, um den Puffer mit Audiodaten zu füllen. Nach dem Schreiben in den Puffer aktualisiert das Beispiel die Position des dynamischen Audioobjekts, indem ISpatialAudioObject::SetPosition aufgerufen wird. Die Lautstärke des Audioobjekts kann auch durch Aufrufen von SetVolume geändert werden. Wenn Sie die Position oder das Volume des Objekts nicht aktualisieren, werden die Position und das Volume des letzten Festlegens beibehalten. Wenn die von der App definierte Lebensdauer des Objekts erreicht wurde, wird ISpatialAudioObject::SetEndOfStream aufgerufen, um die Audiopipeline darüber zu informieren, dass mit diesem Objekt kein Audio mehr geschrieben wird, und das Objekt auf nullptr festgelegt ist, um seine Ressourcen freizugeben.

Nachdem Sie Daten in alle Ihre Audioobjekte geschrieben haben, rufen Sie ISpatialAudioObjectRenderStream::EndUpdatingAudioObjects auf, um das System darüber zu informieren, dass die Daten zum Rendern bereit sind. Sie können GetBuffer nur zwischen einem Aufruf von BeginUpdatingAudioObjects und EndUpdatingAudioObjects aufrufen.

// 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);

Wenn Sie mit dem Rendern von räumlichem Audio fertig sind, beenden Sie den räumlichen Audiostream, indem Sie ISpatialAudioObjectRenderStream::Stop aufrufen. Wenn Sie den Stream nicht erneut verwenden möchten, geben Sie die Ressourcen frei, indem Sie ISpatialAudioObjectRenderStream::Reset aufrufen.

// 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);

Rendern von Audio mithilfe von dynamischen räumlichen Audioobjekten für HRTF

Eine weitere Gruppe von APIs, ISpatialAudioRenderStreamForHrtf und ISpatialAudioObjectForHrtf, ermöglichen räumliche Audiowiedergabe, die die Head-relative Transfer Function (HRTF) von Microsoft verwendet, um Sounds zu attenuieren, um die Position des Emitters im Raum zu simulieren, relativ zum Benutzer, der im Laufe der Zeit geändert werden kann. Zusätzlich zur Position können Sie mit HRTF-Audioobjekten eine Ausrichtung im Raum angeben, eine Direktivität, in der Sound ausgegeben wird, z. B. eine Kegel- oder Nierenform, und ein Zerfallsmodell, wenn sich das Objekt näher und weiter vom virtuellen Listener entfernt. Beachten Sie, dass diese HRTF-Schnittstellen nur verfügbar sind, wenn der Benutzer Windows Sonic für Kopfhörer als räumliches Audiomodul für das Gerät ausgewählt hat. Informationen zum Konfigurieren eines Geräts für die Verwendung Windows Sonic für Kopfhörer finden Sie unter Spatial Sound.

Mit den APIs ISpatialAudioRenderStreamForHrtf und ISpatialAudioObjectForHrtf kann eine Anwendung den Windows Sonic für Kopfhörer Renderpfad explizit direkt verwenden. Diese APIs unterstützen keine räumlichen Soundformate, z. B. Dolby Atmos for Home Theater oder Dolby Atmos for Headphones, noch den wechselseitigen Ausgabeformat über das Sound-Bedienfeld oder die Wiedergabe über Lautsprecher. Diese Schnittstellen sind für die Verwendung in Windows Mixed Reality Anwendungen vorgesehen, die Windows Sonic für Kopfhörer-spezifischen Funktionen verwenden möchten (z. B. Umgebungsvoreinstellungen und programmgesteuerte entfernungsbasierte Rolloffs außerhalb typischer Inhaltserstellungspipelines). Die meisten Spiele und Virtual Reality-Szenarien verwenden stattdessen lieber ISpatialAudioClient . Die Implementierungsschritte für beide API-Sätze sind nahezu identisch, sodass es möglich ist, beide Technologien zu implementieren und zur Laufzeit zu wechseln, je nachdem, welches Feature auf dem aktuellen Gerät verfügbar ist.

Mixed Reality-Apps verwenden in der Regel die Position eines 3D-Objekts in der virtuellen Welt, um die Position des diesem zugeordneten dynamischen Audioobjekts anzugeben. Im folgenden Beispiel wird die einfache Struktur My3dObjectForHrtf verwendet, um den minimalen Datensatz zu speichern, der zum Darstellen eines Objekts erforderlich ist. Diese Daten umfassen einen Zeiger auf ein ISpatialAudioObjectForHrtf, die Position, Ausrichtung, Geschwindigkeit und Tonfrequenz des Objekts sowie einen Wert, der die Gesamtzahl der Frames speichert, für die das Objekt Sound rendern soll.

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;
};

Die Implementierungsschritte für dynamische HRTF-Audioobjekte entsprechen weitgehend den Schritten für dynamische Audioobjekte, die im vorherigen Abschnitt beschrieben wurden. Rufen Sie zunächst einen Audioendpunkt ab.

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);

Initialisieren Sie als Nächstes den räumlichen Audiodatenstrom. Rufen Sie eine instance von ISpatialAudioClient ab, indem Sie IMMDevice::Activate aufrufen. Rufen Sie ISpatialAudioClient::IsAudioObjectFormatSupported auf, um sicherzustellen, dass das verwendete Audioformat unterstützt wird. Erstellen Sie ein Ereignis, das die Audiopipeline verwendet, um die App zu benachrichtigen, dass sie für weitere Audiodaten bereit ist.

Rufen Sie ISpatialAudioClient::GetMaxDynamicObjectCount auf, um die Anzahl der vom System unterstützten dynamischen Objekte abzurufen. Wenn dieser Aufruf 0 zurückgibt, werden dynamische räumliche Audioobjekte auf dem aktuellen Gerät nicht unterstützt oder aktiviert. Informationen zum Aktivieren von räumlichem Audio und zur Anzahl der dynamischen Audioobjekte, die für verschiedene räumliche Audioformate verfügbar sind, finden Sie unter Raumklang.

Legen Sie beim Auffüllen der SpatialAudioHrtfActivationParams-Struktur das Feld MaxDynamicObjectCount auf die maximale Anzahl von dynamischen Objekten fest, die ihre App verwenden wird. Die Aktivierungsparameter für HRTF unterstützen einige zusätzliche Parameter, z. B. spatialAudioHrtfDistanceDecay, SpatialAudioHrtfDirectivityUnion, SpatialAudioHrtfEnvironmentType und SpatialAudioHrtfOrientation, die die Standardwerte dieser Einstellungen für neue Objekte angeben, die aus dem Stream erstellt wurden. Diese Parameter sind optional. Legen Sie die Felder auf NULLptr fest, um keine Standardwerte anzugeben.

Rufen Sie ISpatialAudioClient::ActivateSpatialAudioStream auf, um den Stream zu aktivieren.

// 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);

Im Folgenden finden Sie appspezifischen Code, der dieses Beispiel unterstützen muss. Dadurch werden dynamisch zufällig positionierte Audioobjekte erzeugt und in einem Vektor gespeichert.

// 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;

Rufen Sie vor dem Betreten der Audiorenderingschleife ISpatialAudioObjectRenderStreamForHrtf::Start auf, um die Medienpipeline anzuweisen, mit der Anforderung von Audiodaten zu beginnen.

Warten Sie in der Renderschleife, bis das Pufferabschlussereignis, das wir bei der Initialisierung des räumlichen Audiodatenstroms bereitgestellt haben, um signalisiert zu werden. Sie sollten beim Warten auf das Ereignis ein angemessenes Timeoutlimit wie 100 ms festlegen, da jede Änderung des Rendertyps oder Endpunkts dazu führt, dass dieses Ereignis nie signalisiert wird. In diesem Fall können Sie ISpatialAudioRenderStreamForHrtf::Reset aufrufen, um zu versuchen, den räumlichen Audiodatenstrom zurückzusetzen.

Rufen Sie als Nächstes ISpatialAudioRenderStreamForHrtf::BeginUpdatingAudioObjects auf, um das System darüber zu informieren, dass Sie die Puffer der Audioobjekte mit Daten füllen. Diese Methode gibt die Anzahl der verfügbaren dynamischen Audioobjekte zurück, die in diesem Beispiel nicht verwendet werden, und die Frameanzahl des Puffers für Audioobjekte, die von diesem Stream gerendert werden.

Wenn der Spawnzähler den angegebenen Wert erreicht, aktivieren wir ein neues dynamisches Audioobjekt, indem wir ISpatialAudioRenderStreamForHrtf::ActivateSpatialAudioObjectForHrtf aufrufen und AudioObjectType_Dynamic angeben. Wenn alle verfügbaren dynamischen Audioobjekte bereits zugeordnet wurden, gibt diese Methode SPLAUDCLNT_E_NO_MORE_OBJECTS zurück. In diesem Fall können Sie ein oder mehrere zuvor aktivierte Audioobjekte basierend auf Ihrer app-spezifischen Priorisierung freigeben. Nachdem das dynamische Audioobjekt erstellt wurde, wird es einer neuen My3dObjectForHrtf-Struktur mit zufälligen Positions-, Dreh-, Geschwindigkeits-, Lautstärke- und Frequenzwerten hinzugefügt, die dann der Liste der aktiven Objekte hinzugefügt werden.

Als Nächstes durchlaufen Sie alle aktiven Objekte, die in diesem Beispiel mit der von der App definierten My3dObjectForHrtf-Struktur dargestellt werden. Rufen Sie für jedes Audioobjekt ISpatialAudioObjectForHrtf::GetBuffer auf, um einen Zeiger auf den Audiopuffer des räumlichen Audioobjekts zu erhalten. Diese Methode gibt auch die Größe des Puffers in Bytes zurück. Die weiter oben in diesem Artikel aufgeführte Hilfsmethode WriteToAudioObjectBuffer, um den Puffer mit Audiodaten zu füllen. Nach dem Schreiben in den Puffer aktualisiert das Beispiel die Position und Ausrichtung des HRTF-Audioobjekts, indem ISpatialAudioObjectForHrtf::SetPosition und ISpatialAudioObjectForHrtf::SetOrientation aufgerufen werden. In diesem Beispiel wird die Hilfsmethode CalculateEmitterConeOrientationMatrix verwendet, um die Ausrichtungsmatrix unter Berücksichtigung der Richtung zu berechnen, auf die das 3D-Objekt zeigt. Die Implementierung dieser Methode wird unten gezeigt. Die Lautstärke des Audioobjekts kann auch durch Aufrufen von ISpatialAudioObjectForHrtf::SetGain geändert werden. Wenn Sie die Position, Ausrichtung oder Lautstärke des Objekts nicht aktualisieren, werden die Position, Die Ausrichtung und das Volumen des letzten Festlegens beibehalten. Wenn die von der App definierte Lebensdauer des Objekts erreicht wurde, wird ISpatialAudioObjectForHrtf::SetEndOfStream aufgerufen, um der Audiopipeline mitzuteilen, dass mit diesem Objekt kein Audio mehr geschrieben wird, und das Objekt auf nullptr festgelegt ist, um seine Ressourcen freizugeben.

Nachdem Sie Daten in alle Ihre Audioobjekte geschrieben haben, rufen Sie ISpatialAudioRenderStreamForHrtf::EndUpdatingAudioObjects auf, um dem System mitzuteilen, dass die Daten zum Rendern bereit sind. Sie können GetBuffer nur zwischen einem Aufruf von BeginUpdatingAudioObjects und EndUpdatingAudioObjects aufrufen.

// 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);

Wenn Sie mit dem Rendern räumlicher Audiodaten fertig sind, beenden Sie den räumlichen Audiodatenstrom, indem Sie ISpatialAudioRenderStreamForHrtf::Stop aufrufen. Wenn Sie den Stream nicht erneut verwenden möchten, geben Sie die Ressourcen frei, indem Sie ISpatialAudioRenderStreamForHrtf::Reset aufrufen.

// 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);

Das folgende Codebeispiel zeigt die Implementierung der CalculateEmitterConeOrientationMatrix-Hilfsmethode , die im obigen Beispiel verwendet wurde, um die Ausrichtungsmatrix unter Berücksichtigung der Richtung zu berechnen, die das 3D-Objekt zeigt.

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();
}

Raumklang

ISpatialAudioClient

ISpatialAudioObject