You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb Brandon.Schaefer #preflight 62474a46e434babd8a0febfa [CL 19615416 by jimmy smith in ue5-main branch]
466 lines
13 KiB
C++
466 lines
13 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AudioMixerPlatformSDL.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "AudioMixer.h"
|
|
#include "AudioMixerDevice.h"
|
|
#include "CoreGlobals.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "AudioDevice.h"
|
|
|
|
#if WITH_ENGINE
|
|
#include "AudioPluginUtilities.h"
|
|
#include "OpusAudioInfo.h"
|
|
#include "VorbisAudioInfo.h"
|
|
#include "ADPCMAudioInfo.h"
|
|
#if WITH_BINK_AUDIO
|
|
#include "BinkAudioInfo.h"
|
|
#endif // WITH_BINK_AUDIO
|
|
|
|
#endif // WITH_ENGINE
|
|
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogAudioMixerSDL, Log, All);
|
|
DEFINE_LOG_CATEGORY(LogAudioMixerSDL);
|
|
|
|
namespace Audio
|
|
{
|
|
// Static callback function used in SDL
|
|
static void OnBufferEnd(void* BufferContext, uint8* OutputBuffer, int32 OutputBufferLength)
|
|
{
|
|
check(BufferContext);
|
|
FMixerPlatformSDL* MixerPlatform = (FMixerPlatformSDL*)BufferContext;
|
|
MixerPlatform->HandleOnBufferEnd(OutputBuffer, OutputBufferLength);
|
|
}
|
|
|
|
FMixerPlatformSDL::FMixerPlatformSDL()
|
|
: AudioDeviceID(INDEX_NONE)
|
|
, OutputBuffer(nullptr)
|
|
, OutputBufferByteLength(0)
|
|
, bSuspended(false)
|
|
, bInitialized(false)
|
|
{
|
|
}
|
|
|
|
FMixerPlatformSDL::~FMixerPlatformSDL()
|
|
{
|
|
if (bInitialized)
|
|
{
|
|
TeardownHardware();
|
|
}
|
|
}
|
|
|
|
bool FMixerPlatformSDL::InitializeHardware()
|
|
{
|
|
if (bInitialized)
|
|
{
|
|
UE_LOG(LogAudioMixerSDL, Error, TEXT("SDL Audio already initialized."));
|
|
return false;
|
|
}
|
|
|
|
int32 Result = SDL_InitSubSystem(SDL_INIT_AUDIO);
|
|
if (Result < 0)
|
|
{
|
|
UE_LOG(LogAudioMixerSDL, Error, TEXT("SDL_InitSubSystem create failed: %d"), Result);
|
|
return false;
|
|
}
|
|
|
|
const char* DriverName = SDL_GetCurrentAudioDriver();
|
|
UE_LOG(LogAudioMixerSDL, Display, TEXT("Initialized SDL using %s platform API backend."), ANSI_TO_TCHAR(DriverName));
|
|
|
|
if (IAudioMixer::ShouldRecycleThreads())
|
|
{
|
|
// Pre-create the null render device thread so we can simple wake it up when we need it.
|
|
// Give it nothing to do, with a slow tick as the default, but ask it to wait for a signal to wake up.
|
|
CreateNullDeviceThread([] {}, 1.0f, true);
|
|
}
|
|
|
|
bInitialized = true;
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::TeardownHardware()
|
|
{
|
|
if(!bInitialized)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
StopAudioStream();
|
|
CloseAudioStream();
|
|
|
|
// this is refcounted
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
bInitialized = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::IsInitialized() const
|
|
{
|
|
return bInitialized;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::GetNumOutputDevices(uint32& OutNumOutputDevices)
|
|
{
|
|
if (!bInitialized)
|
|
{
|
|
UE_LOG(LogAudioMixerSDL, Error, TEXT("SDL2 Audio is not initialized."));
|
|
return false;
|
|
}
|
|
|
|
SDL_bool IsCapture = SDL_FALSE;
|
|
OutNumOutputDevices = SDL_GetNumAudioDevices(IsCapture);
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::GetOutputDeviceInfo(const uint32 InDeviceIndex, FAudioPlatformDeviceInfo& OutInfo)
|
|
{
|
|
// To figure out the output device info, attempt to init at 7.1, and 48k.
|
|
// SDL_OpenAudioDevice will attempt open the audio device with that spec but return what it actually used. We'll report that in OutInfo
|
|
FAudioPlatformSettings PlatformSettings = GetPlatformSettings();
|
|
SDL_AudioSpec DesiredSpec;
|
|
DesiredSpec.freq = PlatformSettings.SampleRate;
|
|
|
|
DesiredSpec.format = GetPlatformAudioFormat();
|
|
DesiredSpec.channels = GetPlatformChannels();
|
|
|
|
DesiredSpec.samples = PlatformSettings.CallbackBufferFrameSize;
|
|
DesiredSpec.callback = OnBufferEnd;
|
|
DesiredSpec.userdata = (void*)this;
|
|
|
|
// It's not possible with SDL to tell whether a given index is default. It only supports directly opening a device handle by passing in a nullptr to SDL_OpenAudioDevice.
|
|
OutInfo.bIsSystemDefault = false;
|
|
|
|
const char* AudioDeviceName = nullptr;
|
|
FString DeviceName;
|
|
|
|
if (InDeviceIndex != AUDIO_MIXER_DEFAULT_DEVICE_INDEX)
|
|
{
|
|
AudioDeviceName = SDL_GetAudioDeviceName(InDeviceIndex, SDL_FALSE);
|
|
DeviceName = ANSI_TO_TCHAR(AudioDeviceName);
|
|
}
|
|
else
|
|
{
|
|
DeviceName = TEXT("Default Audio Device");
|
|
}
|
|
|
|
SDL_AudioSpec ActualSpec;
|
|
SDL_AudioDeviceID TempAudioDeviceID = SDL_OpenAudioDevice(AudioDeviceName, SDL_FALSE, &DesiredSpec, &ActualSpec, SDL_AUDIO_ALLOW_CHANNELS_CHANGE);
|
|
if (!TempAudioDeviceID)
|
|
{
|
|
const char* ErrorText = SDL_GetError();
|
|
UE_LOG(LogAudioMixerSDL, Error, TEXT("%s"), ANSI_TO_TCHAR(ErrorText));
|
|
}
|
|
|
|
// Name and Id are the same for SDL
|
|
OutInfo.DeviceId = DeviceName;
|
|
OutInfo.Name = OutInfo.DeviceId;
|
|
OutInfo.SampleRate = ActualSpec.freq;
|
|
|
|
OutInfo.Format = GetAudioStreamFormat();
|
|
|
|
ensure(ActualSpec.channels <= AUDIO_MIXER_MAX_OUTPUT_CHANNELS);
|
|
OutInfo.NumChannels = FMath::Min<int32>(static_cast<int32>(ActualSpec.channels), AUDIO_MIXER_MAX_OUTPUT_CHANNELS);
|
|
|
|
// Assume default channel map order, SDL doesn't support us querying it directly
|
|
OutInfo.OutputChannelArray.Reset();
|
|
for (int32 i = 0; i < OutInfo.NumChannels; ++i)
|
|
{
|
|
OutInfo.OutputChannelArray.Add(EAudioMixerChannel::Type(i));
|
|
}
|
|
|
|
SDL_CloseAudioDevice(TempAudioDeviceID);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::GetDefaultOutputDeviceIndex(uint32& OutDefaultDeviceIndex) const
|
|
{
|
|
// It's not possible to know what index the default audio device is.
|
|
OutDefaultDeviceIndex = AUDIO_MIXER_DEFAULT_DEVICE_INDEX;
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::OpenAudioStream(const FAudioMixerOpenStreamParams& Params)
|
|
{
|
|
if (!bInitialized || AudioStreamInfo.StreamState != EAudioOutputStreamState::Closed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32 NumOutputDevices = 0;
|
|
if (!GetNumOutputDevices(NumOutputDevices))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OpenStreamParams = Params;
|
|
|
|
AudioStreamInfo.Reset();
|
|
AudioStreamInfo.AudioMixer = OpenStreamParams.AudioMixer;
|
|
AudioStreamInfo.NumOutputFrames = OpenStreamParams.NumFrames;
|
|
AudioStreamInfo.NumBuffers = OpenStreamParams.NumBuffers;
|
|
AudioStreamInfo.DeviceInfo.SampleRate = OpenStreamParams.SampleRate;
|
|
|
|
// If there's an available device, open it.
|
|
if (NumOutputDevices > 0)
|
|
{
|
|
if (!GetOutputDeviceInfo(AudioStreamInfo.OutputDeviceIndex, AudioStreamInfo.DeviceInfo))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AudioStreamInfo.OutputDeviceIndex = OpenStreamParams.OutputDeviceIndex;
|
|
|
|
AudioSpecPrefered.format = GetPlatformAudioFormat();
|
|
AudioSpecPrefered.freq = Params.SampleRate;
|
|
AudioSpecPrefered.channels = AudioStreamInfo.DeviceInfo.NumChannels;
|
|
AudioSpecPrefered.samples = OpenStreamParams.NumFrames;
|
|
AudioSpecPrefered.callback = OnBufferEnd;
|
|
AudioSpecPrefered.userdata = (void*)this;
|
|
|
|
const char* DeviceName = nullptr;
|
|
if (OpenStreamParams.OutputDeviceIndex != AUDIO_MIXER_DEFAULT_DEVICE_INDEX && OpenStreamParams.OutputDeviceIndex < (uint32)SDL_GetNumAudioDevices(0))
|
|
{
|
|
DeviceName = SDL_GetAudioDeviceName(OpenStreamParams.OutputDeviceIndex, 0);
|
|
}
|
|
|
|
// only the default device can be overriden
|
|
FString CurrentDeviceNameString = GetCurrentDeviceName();
|
|
if (OpenStreamParams.OutputDeviceIndex != AUDIO_MIXER_DEFAULT_DEVICE_INDEX || CurrentDeviceNameString.Len() <= 0)
|
|
{
|
|
UE_LOG(LogAudioMixerSDL, Log, TEXT("Opening %s audio device (device index %d)"), DeviceName ? ANSI_TO_TCHAR(DeviceName) : TEXT("default"), OpenStreamParams.OutputDeviceIndex);
|
|
AudioDeviceID = SDL_OpenAudioDevice(DeviceName, 0, &AudioSpecPrefered, &AudioSpecReceived, 0);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogAudioMixerSDL, Log, TEXT("Opening overridden '%s' audio device (device index %d)"), *CurrentDeviceNameString, OpenStreamParams.OutputDeviceIndex);
|
|
AudioDeviceID = SDL_OpenAudioDevice(TCHAR_TO_ANSI(*CurrentDeviceNameString), 0, &AudioSpecPrefered, &AudioSpecReceived, 0);
|
|
}
|
|
|
|
if (!AudioDeviceID)
|
|
{
|
|
const char* ErrorText = SDL_GetError();
|
|
UE_LOG(LogAudioMixerSDL, Error, TEXT("%s"), ANSI_TO_TCHAR(ErrorText));
|
|
return false;
|
|
}
|
|
|
|
// Make sure our device initialized as expected, we should have already filtered this out before this point.
|
|
check(AudioSpecReceived.channels == AudioSpecPrefered.channels);
|
|
check(AudioSpecReceived.samples == OpenStreamParams.NumFrames);
|
|
|
|
// Compute the expected output byte length
|
|
OutputBufferByteLength = OpenStreamParams.NumFrames * AudioStreamInfo.DeviceInfo.NumChannels * GetAudioStreamChannelSize();
|
|
check(OutputBufferByteLength == AudioSpecReceived.size);
|
|
}
|
|
else
|
|
{
|
|
// No devices available, Switch to NULL (Silent) Output.
|
|
AudioStreamInfo.DeviceInfo.OutputChannelArray = { EAudioMixerChannel::FrontLeft, EAudioMixerChannel::FrontRight };
|
|
AudioStreamInfo.DeviceInfo.NumChannels = 2;
|
|
AudioStreamInfo.DeviceInfo.Format = EAudioMixerStreamDataFormat::Float;
|
|
}
|
|
|
|
AudioStreamInfo.StreamState = EAudioOutputStreamState::Open;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::CloseAudioStream()
|
|
{
|
|
if (AudioStreamInfo.StreamState == EAudioOutputStreamState::Closed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!StopAudioStream())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (AudioDeviceID != INDEX_NONE)
|
|
{
|
|
FScopeLock ScopedLock(&OutputBufferMutex);
|
|
|
|
SDL_CloseAudioDevice(AudioDeviceID);
|
|
|
|
OutputBuffer = nullptr;
|
|
OutputBufferByteLength = 0;
|
|
}
|
|
|
|
AudioStreamInfo.StreamState = EAudioOutputStreamState::Closed;
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::StartAudioStream()
|
|
{
|
|
if (!bInitialized || (AudioStreamInfo.StreamState != EAudioOutputStreamState::Open && AudioStreamInfo.StreamState != EAudioOutputStreamState::Stopped))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Start generating audio
|
|
BeginGeneratingAudio();
|
|
|
|
// If we're not using the Null renderer AudioDeviceID will be set to something other than INDEX_NONE.
|
|
if (AudioDeviceID != INDEX_NONE)
|
|
{
|
|
// Unpause audio device to start it rendering audio
|
|
SDL_PauseAudioDevice(AudioDeviceID, 0);
|
|
}
|
|
else
|
|
{
|
|
check(!bIsUsingNullDevice);
|
|
StartRunningNullDevice();
|
|
}
|
|
|
|
AudioStreamInfo.StreamState = EAudioOutputStreamState::Running;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::StopAudioStream()
|
|
{
|
|
if (AudioStreamInfo.StreamState != EAudioOutputStreamState::Stopped && AudioStreamInfo.StreamState != EAudioOutputStreamState::Closed)
|
|
{
|
|
if (AudioDeviceID != INDEX_NONE)
|
|
{
|
|
// Pause the audio device
|
|
SDL_PauseAudioDevice(AudioDeviceID, 1);
|
|
}
|
|
else
|
|
{
|
|
check(bIsUsingNullDevice);
|
|
StopRunningNullDevice();
|
|
}
|
|
|
|
if (AudioStreamInfo.StreamState == EAudioOutputStreamState::Running)
|
|
{
|
|
StopGeneratingAudio();
|
|
}
|
|
|
|
check(AudioStreamInfo.StreamState == EAudioOutputStreamState::Stopped);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FAudioPlatformDeviceInfo FMixerPlatformSDL::GetPlatformDeviceInfo() const
|
|
{
|
|
return AudioStreamInfo.DeviceInfo;
|
|
}
|
|
|
|
void FMixerPlatformSDL::SubmitBuffer(const uint8* Buffer)
|
|
{
|
|
// Need to prevent the case in which we close down the audio stream leaving this point to potentially corrupt the free'ed pointer
|
|
FScopeLock ScopedLock(&OutputBufferMutex);
|
|
|
|
if (OutputBuffer)
|
|
{
|
|
FMemory::Memcpy(OutputBuffer, Buffer, OutputBufferByteLength);
|
|
}
|
|
}
|
|
|
|
void FMixerPlatformSDL::HandleOnBufferEnd(uint8* InOutputBuffer, int32 InOutputBufferByteLength)
|
|
{
|
|
if (!bIsDeviceInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
OutputBuffer = InOutputBuffer;
|
|
check(InOutputBufferByteLength == OutputBufferByteLength);
|
|
|
|
ReadNextBuffer();
|
|
}
|
|
|
|
FName FMixerPlatformSDL::GetRuntimeFormat(const USoundWave* InSoundWave) const
|
|
{
|
|
FName RuntimeFormat = Audio::ToName(InSoundWave->GetSoundAssetCompressionType());
|
|
|
|
if (RuntimeFormat == Audio::NAME_PLATFORM_SPECIFIC)
|
|
{
|
|
if (InSoundWave->IsStreaming(nullptr))
|
|
{
|
|
return Audio::NAME_OPUS;
|
|
}
|
|
else
|
|
{
|
|
return Audio::NAME_OGG;
|
|
}
|
|
}
|
|
|
|
#if !WITH_BINK_AUDIO
|
|
// Some SDL platforms don't yet support bink, so fall back to ADPCM
|
|
if (RuntimeFormat == Audio::NAME_BINKA)
|
|
{
|
|
return Audio::NAME_ADPCM;
|
|
}
|
|
#endif
|
|
|
|
return RuntimeFormat;
|
|
}
|
|
|
|
ICompressedAudioInfo* FMixerPlatformSDL::CreateCompressedAudioInfo(const FName& InRuntimeFormat) const
|
|
{
|
|
ICompressedAudioInfo* Decoder = nullptr;
|
|
|
|
if (InRuntimeFormat == Audio::NAME_OGG)
|
|
{
|
|
Decoder = new FVorbisAudioInfo();
|
|
}
|
|
else if (InRuntimeFormat == Audio::NAME_OPUS)
|
|
{
|
|
Decoder = new FOpusAudioInfo();
|
|
}
|
|
#if WITH_BINK_AUDIO
|
|
else if (InRuntimeFormat == Audio::NAME_BINKA)
|
|
{
|
|
Decoder = new FBinkAudioInfo();
|
|
}
|
|
#endif // WITH_BINK_AUDIO
|
|
else
|
|
{
|
|
Decoder = Audio::CreateSoundAssetDecoder(InRuntimeFormat);
|
|
}
|
|
ensureMsgf(Decoder != nullptr, TEXT("Failed to create a sound asset decoder for compression type: %s"), *InRuntimeFormat.ToString());
|
|
return Decoder;
|
|
}
|
|
|
|
FString FMixerPlatformSDL::GetDefaultDeviceName()
|
|
{
|
|
static FString DefaultName(TEXT("Default SDL Audio Device."));
|
|
return DefaultName;
|
|
}
|
|
|
|
void FMixerPlatformSDL::ResumeContext()
|
|
{
|
|
if (bSuspended)
|
|
{
|
|
SDL_UnlockAudioDevice(AudioDeviceID);
|
|
UE_LOG(LogAudioMixerSDL, Display, TEXT("Resuming Audio"));
|
|
bSuspended = false;
|
|
}
|
|
}
|
|
|
|
void FMixerPlatformSDL::SuspendContext()
|
|
{
|
|
if (!bSuspended)
|
|
{
|
|
SDL_LockAudioDevice(AudioDeviceID);
|
|
UE_LOG(LogAudioMixerSDL, Display, TEXT("Suspending Audio"));
|
|
bSuspended = true;
|
|
}
|
|
}
|
|
|
|
FAudioPlatformSettings FMixerPlatformSDL::GetPlatformSettings() const
|
|
{
|
|
#if PLATFORM_UNIX
|
|
return FAudioPlatformSettings::GetPlatformSettings(FPlatformProperties::GetRuntimeSettingsClassName());
|
|
#else
|
|
// On Windows, use default parameters.
|
|
return FAudioPlatformSettings();
|
|
#endif
|
|
}
|
|
}
|