Files
UnrealEngine/Engine/Source/Runtime/Windows/AudioMixerWasapi/Private/AudioMixerWasapiDeviceThread.cpp
2025-05-18 13:04:45 +08:00

141 lines
4.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AudioMixerWasapiDeviceThread.h"
#include "AudioMixer.h"
#include "AudioMixerWasapiLog.h"
#include "ScopedCom.h"
namespace Audio
{
std::atomic<uint32> FAudioMixerWasapiDeviceThread::AudioDeviceThreadCounter = 0;
FAudioMixerWasapiRunnable::FAudioMixerWasapiRunnable(const TFunction<void()>& InDeviceRenderCallback, HANDLE& OutEventHandle) :
DeviceRenderCallback(InDeviceRenderCallback)
{
if (CreateEventHandles())
{
OutEventHandle = EventHandles[0];
}
}
FAudioMixerWasapiRunnable::FAudioMixerWasapiRunnable(const TFunction<void()>& InDeviceRenderCallback, TArray<HANDLE>& OutEventHandles, const int32 InNumRequestedHandles) :
DeviceRenderCallback(InDeviceRenderCallback)
{
if (CreateEventHandles(InNumRequestedHandles))
{
OutEventHandles = EventHandles;
}
}
bool FAudioMixerWasapiRunnable::CreateEventHandles(const uint32 InNumRequestedHandles)
{
uint32 NumHandles = InNumRequestedHandles;
if (NumHandles > MAXIMUM_WAIT_OBJECTS)
{
NumHandles = MAXIMUM_WAIT_OBJECTS;
UE_LOG(LogAudioMixerWasapi, Warning, TEXT("FAudioMixerWasapiRunnable::CreateEventHandles requested %d handles which exceeds max supported. Clamping to %d. "), InNumRequestedHandles, MAXIMUM_WAIT_OBJECTS);
}
EventHandles.SetNumZeroed(NumHandles);
for (uint32 Index = 0; Index < NumHandles; ++Index)
{
// Not using FEvent/FEventWin here because we need access to the raw, platform
// handle (see SetEventHandler() below).
HANDLE EventHandle = ::CreateEvent(nullptr, 0, 0, nullptr);
if (EventHandle == nullptr)
{
EventHandles.Reset();
return false;
}
EventHandles[Index] = EventHandle;
}
return true;
}
uint32 FAudioMixerWasapiRunnable::Run()
{
bIsRunning = true;
FScopedCoInitialize ScopedCoInitialize;
const uint32 NumHandles = EventHandles.Num();
const HANDLE* Handles = EventHandles.GetData();
while (bIsRunning.load())
{
static constexpr uint32 TimeoutInMs = 1000;
// WASAPI events for multiple audio devices will all be signaled at the
// same time when belonging to the same physical device.
uint32 Result = ::WaitForMultipleObjects(NumHandles, Handles, true, TimeoutInMs);
if (Result == WAIT_TIMEOUT)
{
++OutputStreamTimeoutsDetected;
}
else if (Result >= WAIT_OBJECT_0 && Result < (WAIT_OBJECT_0 + NumHandles))
{
DeviceRenderCallback();
}
}
return 0;
}
void FAudioMixerWasapiRunnable::Stop()
{
bIsRunning = false;
if (OutputStreamTimeoutsDetected > 0)
{
UE_LOG(LogAudioMixerWasapi, Display, TEXT("FAudioMixerWasapiRunnable::Stop render stream reported %d timeouts"), OutputStreamTimeoutsDetected);
}
}
FAudioMixerWasapiDeviceThread::FAudioMixerWasapiDeviceThread(const TFunction<void()>& InDeviceRenderCallback, HANDLE& OutEventHandle) :
DeviceRenderRunnable(MakeUnique<FAudioMixerWasapiRunnable>(InDeviceRenderCallback, OutEventHandle))
{
}
FAudioMixerWasapiDeviceThread::FAudioMixerWasapiDeviceThread(const TFunction<void()>& InDeviceRenderCallback, TArray<HANDLE>& OutEventHandles, const int32 InNumRequestedHandles) :
DeviceRenderRunnable(MakeUnique<FAudioMixerWasapiRunnable>(InDeviceRenderCallback, OutEventHandles, InNumRequestedHandles))
{
}
bool FAudioMixerWasapiDeviceThread::Start()
{
TRACE_CPUPROFILER_EVENT_SCOPE(Audio::FAudioMixerWasapiDeviceThread::Start);
check(DeviceRenderThread == nullptr);
DeviceRenderThread = TUniquePtr<FRunnableThread>(FRunnableThread::Create(DeviceRenderRunnable.Get(), *FString::Printf(TEXT("AudioDeviceThread(%d)"), ++AudioDeviceThreadCounter), 0, TPri_TimeCritical));
return DeviceRenderThread.IsValid();
}
void FAudioMixerWasapiDeviceThread::Stop()
{
if (DeviceRenderThread.IsValid())
{
TRACE_CPUPROFILER_EVENT_SCOPE(Audio::FAudioMixerWasapiDeviceThread::Stop);
bool bShouldWait = true;
DeviceRenderThread->Kill(bShouldWait);
DeviceRenderThread.Reset();
}
}
void FAudioMixerWasapiDeviceThread::Abort()
{
if (DeviceRenderThread.IsValid())
{
TRACE_CPUPROFILER_EVENT_SCOPE(Audio::FAudioMixerWasapiDeviceThread::Abort);
// Always wait for thread to complete otherwise we can crash if
// the stream is disposed of mid-callback.
bool bShouldWait = true;
DeviceRenderThread->Kill(bShouldWait);
DeviceRenderThread.Reset();
}
}
}