Files
UnrealEngine/Engine/Source/Runtime/Online/Voice/Private/Windows/VoiceModuleWindows.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

382 lines
9.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VoiceModule.h"
#include "VoicePrivate.h"
#include "VoiceCaptureWindows.h"
#include "VoiceCodecOpus.h"
#include "Voice.h"
#include "IHeadMountedDisplayModule.h"
#include "HAL/IConsoleManager.h"
#if PLATFORM_SUPPORTS_VOICE_CAPTURE
FVoiceCaptureDeviceWindows* FVoiceCaptureDeviceWindows::Singleton = nullptr;
/** Helper for printing MS guids */
FString PrintMSGUID(LPGUID Guid)
{
FString Result;
if (Guid)
{
Result = FString::Printf(TEXT("{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}"),
Guid->Data1, Guid->Data2, Guid->Data3,
Guid->Data4[0], Guid->Data4[1], Guid->Data4[2], Guid->Data4[3],
Guid->Data4[4], Guid->Data4[5], Guid->Data4[6], Guid->Data4[7]);
}
return Result;
}
/** Callback to access all the voice capture devices on the platform */
BOOL CALLBACK CaptureDeviceCallback(
LPGUID lpGuid,
LPCSTR lpcstrDescription,
LPCSTR lpcstrModule,
LPVOID lpContext
)
{
// @todo identify the proper device
FVoiceCaptureDeviceWindows* VCPtr = (FVoiceCaptureDeviceWindows*)(lpContext);
UE_LOG(LogVoiceCapture, Display, TEXT("Device: %hs Desc: %hs GUID: %s Context:0x%08" UPTRINT_x_FMT), lpcstrDescription, lpcstrModule, *PrintMSGUID(lpGuid), lpContext);
if (lpGuid != nullptr)
{
// Save the enumerated device information for later use
FString DeviceDescription((LPCWSTR)lpcstrDescription);
FVoiceCaptureDeviceWindows::FCaptureDeviceInfo DeviceDesc;
DeviceDesc.DeviceName = DeviceDescription;
DeviceDesc.DeviceId = *lpGuid;
// Default devices are list first in this callback
DeviceDesc.bIsDefault = !VCPtr->Devices.Num();
// Set the default voice capture device info here
if (DeviceDesc.bIsDefault)
{
VCPtr->DefaultVoiceCaptureDevice = DeviceDesc;
}
VCPtr->Devices.Emplace(DeviceDescription, DeviceDesc);
// Allow HMD to override the default voice capture device
if (!VCPtr->HMDAudioInputDevice.IsEmpty() && !VCPtr->HMDAudioInputDevice.Compare((LPCWSTR)lpcstrModule))
{
UE_LOG(LogVoice, Display, TEXT("VoiceCapture device overridden by HMD to use '%hs' %s"), lpcstrDescription, *PrintMSGUID(lpGuid));
VCPtr->DefaultVoiceCaptureDevice = DeviceDesc;
VCPtr->Devices.Add(DeviceDescription, VCPtr->DefaultVoiceCaptureDevice);
}
}
return true;
}
FVoiceCaptureDeviceWindows::FVoiceCaptureDeviceWindows() :
bInitialized(false),
DirectSound(nullptr)
{
}
FVoiceCaptureDeviceWindows::~FVoiceCaptureDeviceWindows()
{
Shutdown();
}
FVoiceCaptureDeviceWindows* FVoiceCaptureDeviceWindows::Create()
{
if (Singleton == nullptr)
{
Singleton = new FVoiceCaptureDeviceWindows();
if (!Singleton->Init())
{
Singleton->Destroy();
}
}
return Singleton;
}
void FVoiceCaptureDeviceWindows::Destroy()
{
if (Singleton)
{
// calls Shutdown() above
delete Singleton;
Singleton = nullptr;
}
}
FVoiceCaptureDeviceWindows* FVoiceCaptureDeviceWindows::Get()
{
return Singleton;
}
class FAudioDuckingWindows
{
private:
FAudioDuckingWindows() {}
FAudioDuckingWindows(const FAudioDuckingWindows& Copy) {}
~FAudioDuckingWindows() {}
void operator=(const FAudioDuckingWindows&) {}
public:
static HRESULT EnableDuckingOptOut(IMMDevice* pEndpoint, bool bDuckingOptOutChecked)
{
HRESULT hr = S_OK;
// Activate session manager.
IAudioSessionManager2* pSessionManager2 = nullptr;
if (SUCCEEDED(hr))
{
hr = pEndpoint->Activate(__uuidof(IAudioSessionManager2),
CLSCTX_INPROC_SERVER,
nullptr,
reinterpret_cast<void **>(&pSessionManager2));
}
IAudioSessionControl* pSessionControl = nullptr;
if (SUCCEEDED(hr))
{
hr = pSessionManager2->GetAudioSessionControl(nullptr, 0, &pSessionControl);
pSessionManager2->Release();
pSessionManager2 = nullptr;
}
IAudioSessionControl2* pSessionControl2 = nullptr;
if (SUCCEEDED(hr))
{
hr = pSessionControl->QueryInterface(
__uuidof(IAudioSessionControl2),
(void**)&pSessionControl2);
pSessionControl->Release();
pSessionControl = nullptr;
}
// Sync the ducking state with the specified preference.
if (SUCCEEDED(hr))
{
hr = pSessionControl2->SetDuckingPreference((::BOOL)bDuckingOptOutChecked);
if (FAILED(hr))
{
UE_LOG(LogVoiceCapture, Display, TEXT("Failed to duck audio endpoint. Error: 0x%08x"), hr);
}
pSessionControl2->Release();
pSessionControl2 = nullptr;
}
return hr;
}
static bool UpdateAudioDucking(bool bDuckingOptOutChecked)
{
HRESULT hr = S_OK;
// Start with the default endpoint.
IMMDeviceEnumerator* pDeviceEnumerator = nullptr;
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pDeviceEnumerator));
if (SUCCEEDED(hr))
{
{
IMMDevice* pEndpoint = nullptr;
hr = pDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pEndpoint);
if (SUCCEEDED(hr))
{
LPWSTR Desc;
pEndpoint->GetId(&Desc);
UE_LOG(LogVoiceCapture, Display, TEXT("%s ducking on audio device. Desc: %s"), bDuckingOptOutChecked ? TEXT("Disabling") : TEXT("Enabling"), Desc);
CoTaskMemFree(Desc);
FAudioDuckingWindows::EnableDuckingOptOut(pEndpoint, bDuckingOptOutChecked);
pEndpoint->Release();
pEndpoint = nullptr;
}
}
if (0) // reference for enumerating all endpoints in case its necessary
{
IMMDeviceCollection* pDeviceCollection = nullptr;
IMMDevice* pCollEndpoint = nullptr;
hr = pDeviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDeviceCollection);
if (SUCCEEDED(hr))
{
IPropertyStore *pProps = nullptr;
::UINT DeviceCount = 0;
pDeviceCollection->GetCount(&DeviceCount);
for (::UINT i = 0; i < DeviceCount; ++i)
{
hr = pDeviceCollection->Item(i, &pCollEndpoint);
if (SUCCEEDED(hr) && pCollEndpoint)
{
LPWSTR Desc;
pCollEndpoint->GetId(&Desc);
hr = pCollEndpoint->OpenPropertyStore(STGM_READ, &pProps);
if (SUCCEEDED(hr))
{
PROPVARIANT varName;
// Initialize container for property value.
PropVariantInit(&varName);
// Get the endpoint's friendly-name property.
hr = pProps->GetValue(
PKEY_Device_FriendlyName, &varName);
if (SUCCEEDED(hr))
{
// Print endpoint friendly name and endpoint ID.
UE_LOG(LogVoiceCapture, Display, TEXT("%s ducking on audio device [%d]: \"%s\" (%s)"),
bDuckingOptOutChecked ? TEXT("Disabling") : TEXT("Enabling"),
i, varName.pwszVal, Desc);
CoTaskMemFree(Desc);
Desc = nullptr;
PropVariantClear(&varName);
pProps->Release();
pProps = nullptr;
}
}
FAudioDuckingWindows::EnableDuckingOptOut(pCollEndpoint, bDuckingOptOutChecked);
pCollEndpoint->Release();
pCollEndpoint = nullptr;
}
}
pDeviceCollection->Release();
pDeviceCollection = nullptr;
}
}
pDeviceEnumerator->Release();
pDeviceEnumerator = nullptr;
}
if (FAILED(hr))
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Failed to duck audio endpoint. Error: 0x%08x"), hr);
}
return SUCCEEDED(hr);
}
};
bool FVoiceCaptureDeviceWindows::Init()
{
HRESULT hr = DirectSoundCreate8(nullptr, &DirectSound, nullptr);
if (FAILED(hr))
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Failed to init DirectSound %d"), hr);
return false;
}
if (IHeadMountedDisplayModule::IsAvailable())
{
HMDAudioInputDevice = IHeadMountedDisplayModule::Get().GetAudioInputDevice();
}
Devices.Reset();
hr = DirectSoundCaptureEnumerate((LPDSENUMCALLBACK)CaptureDeviceCallback, this);
if (FAILED(hr))
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Failed to enumerate capture devices %d"), hr);
return false;
}
bool bDuckingOptOut = false;
if (GConfig)
{
if (!GConfig->GetBool(TEXT("Voice"), TEXT("bDuckingOptOut"), bDuckingOptOut, GEngineIni))
{
bDuckingOptOut = false;
}
}
FAudioDuckingWindows::UpdateAudioDucking(bDuckingOptOut);
bInitialized = true;
return true;
}
void FVoiceCaptureDeviceWindows::Shutdown()
{
// Close any active captures
for (int32 CaptureIdx = 0; CaptureIdx < ActiveVoiceCaptures.Num(); CaptureIdx++)
{
IVoiceCapture* VoiceCapture = ActiveVoiceCaptures[CaptureIdx];
VoiceCapture->Shutdown();
}
ActiveVoiceCaptures.Empty();
// Free up DirectSound
if (DirectSound)
{
DirectSound->Release();
DirectSound = nullptr;
}
bInitialized = false;
}
FVoiceCaptureWindows* FVoiceCaptureDeviceWindows::CreateVoiceCaptureObject(const FString& DeviceName, int32 SampleRate, int32 NumChannels)
{
FVoiceCaptureWindows* NewVoiceCapture = nullptr;
if (bInitialized)
{
NewVoiceCapture = new FVoiceCaptureWindows();
if (NewVoiceCapture->Init(DeviceName, SampleRate, NumChannels))
{
ActiveVoiceCaptures.Add(NewVoiceCapture);
}
else
{
NewVoiceCapture = nullptr;
}
}
return NewVoiceCapture;
}
void FVoiceCaptureDeviceWindows::FreeVoiceCaptureObject(IVoiceCapture* VoiceCaptureObj)
{
if (VoiceCaptureObj != nullptr)
{
int32 RemoveIdx = ActiveVoiceCaptures.Find(VoiceCaptureObj);
if (RemoveIdx != INDEX_NONE)
{
ActiveVoiceCaptures.RemoveAtSwap(RemoveIdx);
}
else
{
UE_LOG(LogVoiceCapture, Warning, TEXT("Trying to free unknown voice object"));
}
}
}
bool InitVoiceCapture()
{
return FVoiceCaptureDeviceWindows::Create() != nullptr;
}
void ShutdownVoiceCapture()
{
FVoiceCaptureDeviceWindows::Destroy();
}
IVoiceCapture* CreateVoiceCaptureObject(const FString& DeviceName, int32 SampleRate, int32 NumChannels)
{
FVoiceCaptureDeviceWindows* VoiceCaptureDev = FVoiceCaptureDeviceWindows::Get();
return VoiceCaptureDev ? VoiceCaptureDev->CreateVoiceCaptureObject(DeviceName, SampleRate, NumChannels) : nullptr;
}
#endif // PLATFORM_SUPPORTS_VOICE_CAPTURE