Audiokinetic's Community Q&A is the forum where users can ask and answer questions within the Wwise and Strata communities. If you would like to get an answer from Audiokinetic's Technical support team, make sure you use the Support Tickets page.

How to add Second Output Device in Unreal Engine 4

0 votes
Hi, have been looking around for a way to have different events play through different Output Devices (Speakers, and headphones for example) in Unreal Engine 4.

I can do this in the Wwise editor by going to Audio->Audio Preferences and then choose what bus will play on what output device.

This obviously only works in Wwise and not in UE4.

I have found this: https://www.audiokinetic.com/library/2017.1.9_6501/?source=SDK&id=namespace_a_k_1_1_sound_engine_a247218018cc62d203b9d36a55c5647af.html

However, this is for an older version and is not supported anymore it seems like?

There is also this: https://www.audiokinetic.com/library/edge/?source=SDK&id=namespace_a_k_1_1_sound_engine_a0ebeb52530b9871d9b66b5fa3bce192f.html

It seems like using this function allows you to tie Listeners to specific output devices. By default all Listeners route to the default output device. So I'm assuming it is a matter of creating at least two listeners, Calling this function, and append one of the listeners and hope for the best.

For future reference, I will keep this thread updated and add information when I make progress.

Any help would be appreciated!
asked Sep 18, 2019 in General Discussion by Dieter T (110 points)
edited Sep 18, 2019 by Dieter T
Did you ever figure this out? Having the exact same issues. The pure C++ SDK makes getting a device ID and adding it to a listener very easy, but things are far more limited in UE4's implementation. I'd want to have a simple way of setting this, but if not I've been exploring:

- Extending the functionality of AkAudio, which is the only way UE4's implementation supports Ak::SoundEngine calls (as warned here: https://www.audiokinetic.com/library/edge/?source=UE4&id=using_cpp.html).

- Bypassing the implementation entirely and treating my UE4 project as a custom C++ engine that uses the SDK
(here's a starting point: https://www.linkedin.com/pulse/integrating-wwise-custom-engine-i-matthew-rosen)

The latter is particularly complex and has all sorts of issues with UE4's source (for example, Wwise uses <windows.h>/<winnt.h> which have another definition of the TEXT macro UE4 uses). This all seems like a lot of trouble for something that should be simple, and is, outside of UE4.
Hi, yes I did solve this with some help from the support!

Unfortunately, since the DLLs for Wwise is loaded in the  plugin, normal behaviour, this means you have to make these changes directly in the Wwise plugin, i.e branch out from AudioKinetic´s version.

So what I did was: In the AKSettings.h I added a TMap that will store the OutputDevice->Device Shareset (same configuration that Wwise authoring tool uses).

    /*   
    *    Add the name of the output device together with the associated DeviceShareset.
    *    Key        = HardwareOutputDevice
    *    Value    = Device Shareset
    */
    UPROPERTY(Config, EditAnywhere, Category = "OutputDevice")
    TMap<FString, FString> OutputDevices;

In AKAudioDevice.h add this variable:

//Array of Output Device IDs that has been created from AK::SoundEngine::AddOutput
TArray<AkOutputDeviceID> OutputDeviceIDs;

This is to keep track of all the different outputs you've added to the AKSettings.h TMap.

Then in AKAudioDevice.cpp, change the function OnActorSpawned to look like this:

void FAkAudioDevice::OnActorSpawned(AActor* SpawnedActor)
{
    APlayerCameraManager* AsPlayerCameraManager = Cast<APlayerCameraManager>(SpawnedActor);
    if (AsPlayerCameraManager && AsPlayerCameraManager->GetWorld()->AllowAudioPlayback())
    {
        APlayerController* CameraOwner = Cast<APlayerController>(AsPlayerCameraManager->GetOwner());
        if (CameraOwner && CameraOwner->IsLocalPlayerController())
        {
            FScopeLock Lock(&AkSettingsSection);
            const UAkSettings* AkSettings = GetDefault<UAkSettings>();
            if (AkSettings)
            {
                if (AkSettings->OutputDevices.Num() <= 0)
                {
                    UAkComponent* pAkComponent = NewObject<UAkComponent>(SpawnedActor);
                    if (pAkComponent != nullptr)
                    {
                        pAkComponent->RegisterComponentWithWorld(SpawnedActor->GetWorld());
                        pAkComponent->AttachToComponent(SpawnedActor->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform, FName());
                        AddDefaultListener(pAkComponent);
                    }
                }
                else
                {
                    for (auto& Elem : AkSettings->OutputDevices)
                    {
                        const char* HardwareOutputDevice = TCHAR_TO_ANSI(*Elem.Key);
                   
                        UAkComponent * pAkComponent1 = NewObject<UAkComponent>(SpawnedActor);
                        if (pAkComponent1 != nullptr)
                        {
                            pAkComponent1->RegisterComponentWithWorld(SpawnedActor->GetWorld());
                            pAkComponent1->AttachToComponent(SpawnedActor->GetRootComponent(), FAttachmentTransformRules::KeepWorldTransform, FName());
                            AddDefaultListener(pAkComponent1);
                        }

                        AkGameObjectID listenerID1 = pAkComponent1->GetAkGameObjectID();
                   
                        AkUInt32 uDeviceID1 = AK::GetDeviceIDFromName(const_cast<TCHAR*>(*Elem.Key));

                        // Add a secondary output connected to the device found previously, associated with listener1.
                        const char* DeviceShareset = TCHAR_TO_ANSI(*Elem.Value);
                        AkOutputSettings outputSettings1(DeviceShareset, uDeviceID1);

                        AkOutputDeviceID outID;
                        AKRESULT res1 = AK::SoundEngine::AddOutput(outputSettings1, &outID, &listenerID1, 1);
                        if (res1 != AK_Success)
                        {
                            UE_LOG(LogAkAudio, Error, TEXT("Failed to create output %d"), res1);
                        }
                        else
                        {
                            OutputDeviceIDs.Add(outID);
                        }
                    }
                }
            }
        }
    }
}

And change the Init function in AKAudioDevice.cpp so that it has these two lambda functions:

FEditorDelegates::EndPIE.AddLambda(
            [&](const bool bIsSimulating)
            {
                if (!bIsSimulating)
                {
                    AddDefaultListener(EditorListener);
                    // The Editor Listener should NEVER be the spatial audio listener
                    if (m_SpatialAudioListener == EditorListener)
                    {
                        AK::SpatialAudio::UnregisterListener(m_SpatialAudioListener->GetAkGameObjectID());
                        m_SpatialAudioListener = nullptr;
                    }

                    FScopeLock Lock(&AkSettingsSection);
                    const UAkSettings* AkSettings = GetDefault<UAkSettings>();
                    if (AkSettings)
                    {
                        for (AkOutputDeviceID ID : OutputDeviceIDs)
                        {
                            AKRESULT result = AK::SoundEngine::RemoveOutput(ID);
                            if (result != AK_Success)
                            {
                                UE_LOG(LogAkAudio, Error, TEXT("Failed to remove output %d"), result);
                            }
                        }
                    }
                }
            }
        );

        FWorldDelegates::LevelRemovedFromWorld.AddLambda(
            [&](ULevel* LevelOpened, UWorld* bAsTemplate)
            {
                FScopeLock Lock(&AkSettingsSection);
                const UAkSettings* AkSettings = GetDefault<UAkSettings>();
                if (AkSettings)
                {
                    for (AkOutputDeviceID ID : OutputDeviceIDs)
                    {
                        AKRESULT result = AK::SoundEngine::RemoveOutput(ID);
                        if (result != AK_Success)
                        {
                            UE_LOG(LogAkAudio, Error, TEXT("Failed to remove output %d"), result);
                        }
                    }
                }
            }
        );

This because OnActorSpawn is called when a new level is loaded, so before that happens we want to reset/remove all outputs, otherwise the sound engine will not work!

Hope this helped!
/Linus

1 Answer

0 votes
answered Apr 29, 2020 by Ed K. (300 points)
...