You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Integration touches several places in the engine: 1) SoundWave -- A check box enables Bink Audio as the codec of choice for that sound wave. 2) Decoder - Each supported platform's AudioMixer now returns BINK if the soundwave requests it. Additionally, the TargetPlatform returns BINK as an available codec, and returns it to the cooking code if the sound wave requests it. 3) Encode - TargetPlatform.Build.cs adds the encoder to the editor dependencies, and it gets picked up in the TPMM formats search. [CL 16682710 by Dan Thompson in ue5-main branch]
467 lines
12 KiB
C++
467 lines
12 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"
|
|
|
|
#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));
|
|
|
|
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;
|
|
}
|
|
|
|
OpenStreamParams = Params;
|
|
|
|
AudioStreamInfo.Reset();
|
|
AudioStreamInfo.OutputDeviceIndex = OpenStreamParams.OutputDeviceIndex;
|
|
AudioStreamInfo.NumOutputFrames = OpenStreamParams.NumFrames;
|
|
AudioStreamInfo.NumBuffers = OpenStreamParams.NumBuffers;
|
|
AudioStreamInfo.AudioMixer = OpenStreamParams.AudioMixer;
|
|
|
|
if (!GetOutputDeviceInfo(AudioStreamInfo.OutputDeviceIndex, AudioStreamInfo.DeviceInfo))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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 CurrentDeviceName = GetCurrentDeviceName();
|
|
if (OpenStreamParams.OutputDeviceIndex != AUDIO_MIXER_DEFAULT_DEVICE_INDEX || CurrentDeviceName.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)"), *CurrentDeviceName, OpenStreamParams.OutputDeviceIndex);
|
|
AudioDeviceID = SDL_OpenAudioDevice(TCHAR_TO_ANSI(*CurrentDeviceName), 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);
|
|
|
|
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();
|
|
|
|
// Unpause audio device to start it rendering audio
|
|
SDL_PauseAudioDevice(AudioDeviceID, 0);
|
|
|
|
AudioStreamInfo.StreamState = EAudioOutputStreamState::Running;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformSDL::StopAudioStream()
|
|
{
|
|
if (AudioStreamInfo.StreamState != EAudioOutputStreamState::Stopped && AudioStreamInfo.StreamState != EAudioOutputStreamState::Closed)
|
|
{
|
|
// Pause the audio device
|
|
SDL_PauseAudioDevice(AudioDeviceID, 1);
|
|
|
|
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(USoundWave* InSoundWave)
|
|
{
|
|
#if WITH_ENGINE
|
|
static FName NAME_OGG(TEXT("OGG"));
|
|
static FName NAME_OPUS(TEXT("OPUS"));
|
|
static FName NAME_ADPCM(TEXT("ADPCM"));
|
|
|
|
#if WITH_BINK_AUDIO
|
|
static FName NAME_BINKA(TEXT("BINKA"));
|
|
if (InSoundWave->bUseBinkAudio)
|
|
{
|
|
return NAME_BINKA;
|
|
}
|
|
#endif // WITH_BINK_AUDIO
|
|
if (InSoundWave->IsStreaming(nullptr))
|
|
{
|
|
if (InSoundWave->IsSeekableStreaming())
|
|
{
|
|
return NAME_ADPCM;
|
|
}
|
|
|
|
return NAME_OPUS;
|
|
}
|
|
return NAME_OGG;
|
|
#else
|
|
checkNoEntry();
|
|
return FName();
|
|
#endif // WITH_ENGINE
|
|
}
|
|
|
|
bool FMixerPlatformSDL::HasCompressedAudioInfoClass(USoundWave* InSoundWave)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
ICompressedAudioInfo* FMixerPlatformSDL::CreateCompressedAudioInfo(USoundWave* InSoundWave)
|
|
{
|
|
#if WITH_ENGINE
|
|
check(InSoundWave);
|
|
|
|
#if WITH_BINK_AUDIO
|
|
if (InSoundWave->bUseBinkAudio)
|
|
{
|
|
return new FBinkAudioInfo();
|
|
}
|
|
#endif // WITH_BINK_AUDIO
|
|
|
|
if (InSoundWave->IsStreaming())
|
|
{
|
|
if (InSoundWave->IsSeekableStreaming())
|
|
{
|
|
return new FADPCMAudioInfo();
|
|
}
|
|
|
|
return new FOpusAudioInfo();
|
|
}
|
|
|
|
static FName NAME_OGG(TEXT("OGG"));
|
|
if (InSoundWave->HasCompressedData(NAME_OGG))
|
|
{
|
|
return new FVorbisAudioInfo();
|
|
}
|
|
|
|
return new FADPCMAudioInfo();
|
|
#else
|
|
checkNoEntry();
|
|
return nullptr;
|
|
#endif // WITH_ENGINE
|
|
}
|
|
|
|
ICompressedAudioInfo* FMixerPlatformSDL::CreateCompressedAudioInfo(const FSoundWaveProxyPtr& InSoundWave)
|
|
{
|
|
#if WITH_ENGINE
|
|
|
|
#if WITH_BINK_AUDIO
|
|
if (InSoundWave->UseBinkAudio())
|
|
{
|
|
return new FBinkAudioInfo();
|
|
}
|
|
#endif // WITH_BINK_AUDIO
|
|
|
|
if (InSoundWave->IsStreaming())
|
|
{
|
|
if (InSoundWave->IsSeekableStreaming())
|
|
{
|
|
return new FADPCMAudioInfo();
|
|
}
|
|
|
|
return new FOpusAudioInfo();
|
|
}
|
|
|
|
return nullptr;
|
|
#else
|
|
checkNoEntry();
|
|
return nullptr;
|
|
#endif // WITH_ENGINE
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|