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]
736 lines
22 KiB
C++
736 lines
22 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
XeAudioDevice.cpp: Unreal XAudio2 Audio interface object.
|
|
|
|
Unreal is RHS with Y and Z swapped (or technically LHS with flipped axis)
|
|
|
|
=============================================================================*/
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
Audio includes.
|
|
------------------------------------------------------------------------------------*/
|
|
|
|
#include "XAudio2Device.h"
|
|
#include "AudioEffect.h"
|
|
#include "AudioPluginUtilities.h"
|
|
#include "OpusAudioInfo.h"
|
|
#include "VorbisAudioInfo.h"
|
|
#include "XAudio2Effects.h"
|
|
#include "Interfaces/IAudioFormat.h"
|
|
#include "HAL/PlatformAffinity.h"
|
|
#if PLATFORM_MICROSOFT
|
|
#include "Windows/WindowsHWrapper.h"
|
|
#include "Windows/AllowWindowsPlatformTypes.h"
|
|
#include "Windows/AllowWindowsPlatformAtomics.h"
|
|
THIRD_PARTY_INCLUDES_START
|
|
#include <xapobase.h>
|
|
#include <xapofx.h>
|
|
#include <xaudio2fx.h>
|
|
THIRD_PARTY_INCLUDES_END
|
|
#include "Windows/HideWindowsPlatformAtomics.h"
|
|
#include "Windows/HideWindowsPlatformTypes.h"
|
|
#endif
|
|
#include "XAudio2Support.h"
|
|
#include "Runtime/HeadMountedDisplay/Public/IHeadMountedDisplayModule.h"
|
|
|
|
#if WITH_XMA2
|
|
#include "XMAAudioInfo.h"
|
|
#endif
|
|
|
|
#include "ADPCMAudioInfo.h"
|
|
#include "BinkAudioInfo.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogXAudio2);
|
|
|
|
class FXAudio2DeviceModule : public IAudioDeviceModule
|
|
{
|
|
public:
|
|
|
|
/** Creates a new instance of the audio device implemented by the module. */
|
|
virtual FAudioDevice* CreateAudioDevice() override
|
|
{
|
|
return new FXAudio2Device;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_MODULE(FXAudio2DeviceModule, XAudio2);
|
|
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
Static variables from the early init
|
|
------------------------------------------------------------------------------------*/
|
|
|
|
// The number of speakers producing sound (stereo or 5.1)
|
|
int32 FXAudioDeviceProperties::NumSpeakers = 0;
|
|
const float* FXAudioDeviceProperties::OutputMixMatrix = NULL;
|
|
#if XAUDIO_SUPPORTS_DEVICE_DETAILS
|
|
XAUDIO2_DEVICE_DETAILS FXAudioDeviceProperties::DeviceDetails;
|
|
#endif //XAUDIO_SUPPORTS_DEVICE_DETAILS
|
|
|
|
#if PLATFORM_WINDOWS
|
|
HMODULE FXAudioDeviceProperties::XAudio2Dll = nullptr;
|
|
#endif
|
|
|
|
/*------------------------------------------------------------------------------------
|
|
FAudioDevice Interface.
|
|
------------------------------------------------------------------------------------*/
|
|
|
|
#define DEBUG_XAUDIO2 0
|
|
|
|
void FXAudio2Device::UpdateDeviceDeltaTime()
|
|
{
|
|
DeviceDeltaTime = GetGameDeltaTime();
|
|
}
|
|
|
|
void FXAudio2Device::GetAudioDeviceList(TArray<FString>& OutAudioDeviceNames) const
|
|
{
|
|
DeviceProperties->GetAudioDeviceList(OutAudioDeviceNames);
|
|
}
|
|
|
|
bool FXAudio2Device::InitializeHardware()
|
|
{
|
|
bIsAudioDeviceHardwareInitialized = false;
|
|
|
|
bHardwareChanged = false;
|
|
|
|
if (IsRunningDedicatedServer())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Create a new DeviceProperties object
|
|
DeviceProperties = new FXAudioDeviceProperties;
|
|
// null out the non-static data
|
|
DeviceProperties->XAudio2 = nullptr;
|
|
DeviceProperties->MasteringVoice = nullptr;
|
|
|
|
#if WITH_OGGVORBIS
|
|
// Load ogg and vorbis dlls if they haven't been loaded yet
|
|
LoadVorbisLibraries();
|
|
#endif
|
|
|
|
SampleRate = UE_XAUDIO2_SAMPLERATE;
|
|
|
|
#if PLATFORM_WINDOWS || PLATFORM_HOLOLENS
|
|
bComInitialized = FPlatformMisc::CoInitialize();
|
|
#if PLATFORM_64BITS && !PLATFORM_HOLOLENS
|
|
// Work around the fact the x64 version of XAudio2_7.dll does not properly ref count
|
|
// by forcing it to be always loaded
|
|
|
|
// Load the xaudio2 library and keep a handle so we can free it on teardown
|
|
// Note: windows internally ref-counts the library per call to load library so
|
|
// when we call FreeLibrary, it will only free it once the refcount is zero
|
|
// It's important that this dll stay around for the duration of the application's lifetime;
|
|
// attempting to free the library and losing our final ref when we change devices still exhibits the bad behavior.
|
|
|
|
// Work around a known XAudio 2.7 issue: https://blogs.msdn.microsoft.com/chuckw/2015/10/09/known-issues-xaudio-2-7/
|
|
if (FXAudioDeviceProperties::XAudio2Dll == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Verbose, TEXT("Loading XAudio2 dll"));
|
|
|
|
#if defined(XAUDIO2_DLL_A) //is this ref counting fix still necessary on post- XAudio2_7 ?
|
|
FXAudioDeviceProperties::XAudio2Dll = LoadLibraryA(XAUDIO2_DLL_A);
|
|
#else
|
|
FXAudioDeviceProperties::XAudio2Dll = LoadLibraryA("XAudio2_7.dll");
|
|
#endif
|
|
|
|
// returning null means we failed to load XAudio2, which means everything will fail
|
|
if (FXAudioDeviceProperties::XAudio2Dll == nullptr)
|
|
{
|
|
UE_LOG(LogInit, Error, TEXT("Failed to load XAudio2 dll"));
|
|
return false;
|
|
}
|
|
}
|
|
#endif //PLATFORM_64BITS && !PLATFORM_HOLOLENS
|
|
#endif //PLATFORM_WINDOWS || PLATFORM_HOLOLENS
|
|
|
|
#if DEBUG_XAUDIO2
|
|
uint32 Flags = XAUDIO2_DEBUG_ENGINE;
|
|
#else
|
|
uint32 Flags = 0;
|
|
#endif
|
|
|
|
#if WITH_XMA2
|
|
// We don't use all of the SHAPE processor, so this flag prevents wasted resources
|
|
Flags |= XAUDIO2_DO_NOT_USE_SHAPE;
|
|
#endif
|
|
|
|
XAUDIO2_PROCESSOR ProcessorsToUse = (XAUDIO2_PROCESSOR)FPlatformAffinity::GetAudioThreadMask();
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/xaudio2/nf-xaudio2-xaudio2create
|
|
// Warning If you specify XAUDIO2_ANY_PROCESSOR, the system will use all of the device's processors and, as noted above, create a worker thread for each processor.
|
|
// We certainly don't want to use all available CPU. XAudio threads are time critical priority and wake up every 10 ms, they may cause lots of unwarranted context switches.
|
|
// In case no specific affinity is specified, let XAudio choose the default processor. It should allocate a single thread and should be enough.
|
|
if (ProcessorsToUse == XAUDIO2_ANY_PROCESSOR)
|
|
{
|
|
#ifdef XAUDIO2_USE_DEFAULT_PROCESSOR
|
|
ProcessorsToUse = XAUDIO2_USE_DEFAULT_PROCESSOR;
|
|
#else
|
|
ProcessorsToUse = XAUDIO2_DEFAULT_PROCESSOR;
|
|
#endif
|
|
}
|
|
|
|
// Create a new XAudio2 device object instance
|
|
if (XAudio2Create(&DeviceProperties->XAudio2, Flags, ProcessorsToUse) != S_OK)
|
|
{
|
|
UE_LOG(LogInit, Log, TEXT("Failed to create XAudio2 interface"));
|
|
return(false);
|
|
}
|
|
|
|
check(DeviceProperties->XAudio2 != nullptr);
|
|
|
|
#if XAUDIO_SUPPORTS_DEVICE_DETAILS
|
|
UINT32 DeviceCount = 0;
|
|
ValidateAPICall(TEXT("GetDeviceCount"),
|
|
DeviceProperties->XAudio2->GetDeviceCount(&DeviceCount));
|
|
if( DeviceCount < 1 )
|
|
{
|
|
UE_LOG(LogInit, Log, TEXT( "No audio devices found!" ) );
|
|
DeviceProperties->XAudio2->Release();
|
|
DeviceProperties->XAudio2 = nullptr;
|
|
return( false );
|
|
}
|
|
|
|
// Initialize the audio device index to 0, which is the windows default device index
|
|
UINT32 DeviceIndex = 0;
|
|
|
|
#if XAUDIO_SUPPORTS_DEVICE_DETAILS
|
|
FString WindowsAudioDeviceName;
|
|
GConfig->GetString(TEXT("/Script/WindowsTargetPlatform.WindowsTargetSettings"), TEXT("AudioDevice"), WindowsAudioDeviceName, GEngineIni);
|
|
|
|
// Allow HMD to specify audio device, if one was not specified in settings
|
|
if (WindowsAudioDeviceName.IsEmpty() && FAudioDevice::CanUseVRAudioDevice() && IHeadMountedDisplayModule::IsAvailable())
|
|
{
|
|
WindowsAudioDeviceName = IHeadMountedDisplayModule::Get().GetAudioOutputDevice();
|
|
}
|
|
|
|
// If an audio device was specified, try to find it
|
|
if (!WindowsAudioDeviceName.IsEmpty())
|
|
{
|
|
uint32 NumDevices = 0;
|
|
DeviceProperties->XAudio2->GetDeviceCount(&NumDevices);
|
|
|
|
for (uint32 i = 0; i < NumDevices; ++i)
|
|
{
|
|
XAUDIO2_DEVICE_DETAILS Details;
|
|
DeviceProperties->XAudio2->GetDeviceDetails(i, &Details);
|
|
|
|
if (FString(Details.DeviceID) == WindowsAudioDeviceName || FString(Details.DisplayName) == WindowsAudioDeviceName)
|
|
{
|
|
DeviceIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get the details of the desired device index (0 is default)
|
|
if (!ValidateAPICall(TEXT("GetDeviceDetails"),
|
|
DeviceProperties->XAudio2->GetDeviceDetails(DeviceIndex, &FXAudioDeviceProperties::DeviceDetails)))
|
|
{
|
|
UE_LOG(LogInit, Log, TEXT("Failed to get DeviceDetails for XAudio2"));
|
|
DeviceProperties->XAudio2 = nullptr;
|
|
return(false);
|
|
}
|
|
#endif // #if XAUDIO_SUPPORTS_DEVICE_DETAILS
|
|
|
|
#if DEBUG_XAUDIO2
|
|
XAUDIO2_DEBUG_CONFIGURATION DebugConfig = {0};
|
|
DebugConfig.TraceMask = XAUDIO2_LOG_WARNINGS | XAUDIO2_LOG_DETAIL;
|
|
DebugConfig.BreakMask = XAUDIO2_LOG_ERRORS;
|
|
DeviceProperties->XAudio2->SetDebugConfiguration(&DebugConfig);
|
|
#endif // #if DEBUG_XAUDIO2
|
|
|
|
FXAudioDeviceProperties::NumSpeakers = UE_XAUDIO2_NUMCHANNELS;
|
|
SampleRate = FXAudioDeviceProperties::DeviceDetails.OutputFormat.Format.nSamplesPerSec;
|
|
|
|
// Clamp the output frequency to the limits of the reverb XAPO
|
|
if( SampleRate > XAUDIO2FX_REVERB_MAX_FRAMERATE )
|
|
{
|
|
SampleRate = XAUDIO2FX_REVERB_MAX_FRAMERATE;
|
|
FXAudioDeviceProperties::DeviceDetails.OutputFormat.Format.nSamplesPerSec = SampleRate;
|
|
}
|
|
|
|
{
|
|
const TCHAR* DisplayName = FXAudioDeviceProperties::DeviceDetails.DisplayName;
|
|
int32 BitsPerSample = int32(FXAudioDeviceProperties::DeviceDetails.OutputFormat.Format.wBitsPerSample);
|
|
UE_LOG(LogInit, Log, TEXT( "XAudio2 using '%s' : %d channels at %g kHz using %d bits per sample (channel mask 0x%x)" ),
|
|
DisplayName,
|
|
FXAudioDeviceProperties::NumSpeakers,
|
|
( float )SampleRate / 1000.0f,
|
|
BitsPerSample,
|
|
(uint32)UE_XAUDIO2_CHANNELMASK );
|
|
}
|
|
|
|
if( !GetOutputMatrix( UE_XAUDIO2_CHANNELMASK, FXAudioDeviceProperties::NumSpeakers ) )
|
|
{
|
|
UE_LOG(LogInit, Log, TEXT( "Unsupported speaker configuration for this number of channels" ) );
|
|
DeviceProperties->XAudio2 = NULL;
|
|
return( false );
|
|
}
|
|
|
|
// Create the final output voice with either 2 or 6 channels
|
|
if (!ValidateAPICall(TEXT("CreateMasteringVoice"),
|
|
DeviceProperties->XAudio2->CreateMasteringVoice(&DeviceProperties->MasteringVoice, FXAudioDeviceProperties::NumSpeakers, SampleRate, 0, DeviceIndex, nullptr)))
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT( "Failed to create the mastering voice for XAudio2" ) );
|
|
DeviceProperties->XAudio2 = nullptr;
|
|
return( false );
|
|
}
|
|
#else // #if XAUDIO_SUPPORTS_DEVICE_DETAILS
|
|
// Create the final output voice
|
|
if (!ValidateAPICall(TEXT("CreateMasteringVoice"),
|
|
DeviceProperties->XAudio2->CreateMasteringVoice(&DeviceProperties->MasteringVoice, UE_XAUDIO2_NUMCHANNELS, UE_XAUDIO2_SAMPLERATE, 0, 0, nullptr )))
|
|
{
|
|
UE_LOG(LogInit, Warning, TEXT( "Failed to create the mastering voice for XAudio2" ) );
|
|
DeviceProperties->XAudio2 = nullptr;
|
|
return false;
|
|
}
|
|
#endif // #else // #if XAUDIO_SUPPORTS_DEVICE_DETAILS
|
|
|
|
DeviceProperties->SpatializationHelper.Init();
|
|
|
|
// Set that we initialized our hardware audio device ok so we should use real voices.
|
|
bIsAudioDeviceHardwareInitialized = true;
|
|
|
|
#if WITH_XMA2
|
|
XMA2_INFO_CALL(FXMAAudioInfo::Initialize());
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void FXAudio2Device::TeardownHardware()
|
|
{
|
|
if (DeviceProperties)
|
|
{
|
|
delete DeviceProperties;
|
|
DeviceProperties = nullptr;
|
|
}
|
|
|
|
#if WITH_XMA2
|
|
XMA2_INFO_CALL(FXMAAudioInfo::Shutdown());
|
|
#endif
|
|
|
|
#if PLATFORM_WINDOWS
|
|
if (bComInitialized)
|
|
{
|
|
FPlatformMisc::CoUninitialize();
|
|
bComInitialized = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FXAudio2Device::UpdateHardware()
|
|
{
|
|
#if WITH_XMA2
|
|
XMA2_INFO_CALL(FXMAAudioInfo::Tick());
|
|
#endif //WITH_XMA2
|
|
|
|
// If the audio device changed, we need to tear down and restart the audio engine state
|
|
if (DeviceProperties && DeviceProperties->DidAudioDeviceChange())
|
|
{
|
|
//Cache the current audio clock.
|
|
CachedAudioClockStartTime = GetAudioClock();
|
|
|
|
// Flush stops all sources so sources can be safely deleted below.
|
|
Flush(nullptr);
|
|
|
|
// Remove the effects manager
|
|
if (Effects)
|
|
{
|
|
delete Effects;
|
|
Effects = nullptr;
|
|
}
|
|
|
|
// Teardown hardware
|
|
TeardownHardware();
|
|
|
|
// Restart the hardware
|
|
InitializeHardware();
|
|
|
|
// Recreate the effects manager
|
|
Effects = CreateEffectsManager();
|
|
|
|
// Now reset and restart the sound source objects
|
|
FreeSources.Reset();
|
|
Sources.Reset();
|
|
|
|
InitSoundSources();
|
|
}
|
|
}
|
|
|
|
FAudioEffectsManager* FXAudio2Device::CreateEffectsManager()
|
|
{
|
|
// Create the effects subsystem (reverb, EQ, etc.)
|
|
return new FXAudio2EffectsManager(this);
|
|
}
|
|
|
|
FSoundSource* FXAudio2Device::CreateSoundSource()
|
|
{
|
|
// create source source object
|
|
return new FXAudio2SoundSource(this);
|
|
}
|
|
|
|
bool FXAudio2Device::HasCompressedAudioInfoClass(USoundWave* SoundWave)
|
|
{
|
|
#if WITH_OGGVORBIS || WITH_XMA2
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
class ICompressedAudioInfo* FXAudio2Device::CreateCompressedAudioInfo(USoundWave* SoundWave)
|
|
{
|
|
check(SoundWave);
|
|
|
|
if (SoundWave->bUseBinkAudio)
|
|
{
|
|
return new FBinkAudioInfo();
|
|
}
|
|
|
|
if (SoundWave->IsStreaming(nullptr))
|
|
{
|
|
if (SoundWave->IsSeekableStreaming())
|
|
{
|
|
return new FADPCMAudioInfo();
|
|
}
|
|
|
|
#if WITH_XMA2 && USE_XMA2_FOR_STREAMING
|
|
if (SoundWave->NumChannels <= 2 )
|
|
{
|
|
ICompressedAudioInfo* CompressedInfo = XMA2_INFO_NEW();
|
|
|
|
if (!CompressedInfo)
|
|
{
|
|
UE_LOG(LogAudio, Error, TEXT("Failed to create new FXMAAudioInfo for streaming SoundWave %s: out of memory."), *SoundWave->GetName());
|
|
return nullptr;
|
|
}
|
|
return CompressedInfo;
|
|
}
|
|
#endif
|
|
|
|
#if USE_VORBIS_FOR_STREAMING
|
|
return new FVorbisAudioInfo();
|
|
#else
|
|
return new FOpusAudioInfo();
|
|
#endif
|
|
}
|
|
|
|
#if WITH_OGGVORBIS
|
|
static const FName NAME_OGG(TEXT("OGG"));
|
|
if (FPlatformProperties::RequiresCookedData() ? SoundWave->HasCompressedData(NAME_OGG) : (SoundWave->GetCompressedData(NAME_OGG) != nullptr))
|
|
{
|
|
ICompressedAudioInfo* CompressedInfo = new FVorbisAudioInfo();
|
|
if (!CompressedInfo)
|
|
{
|
|
UE_LOG(LogAudio, Error, TEXT("Failed to create new FVorbisAudioInfo for SoundWave %s: out of memory."), *SoundWave->GetName());
|
|
return nullptr;
|
|
}
|
|
return CompressedInfo;
|
|
}
|
|
#endif
|
|
|
|
#if WITH_XMA2
|
|
static const FName NAME_XMA(TEXT("XMA"));
|
|
if (FPlatformProperties::RequiresCookedData() ? SoundWave->HasCompressedData(NAME_XMA) : (SoundWave->GetCompressedData(NAME_XMA) != nullptr))
|
|
{
|
|
ICompressedAudioInfo* CompressedInfo = XMA2_INFO_NEW();
|
|
if (!CompressedInfo)
|
|
{
|
|
UE_LOG(LogAudio, Error, TEXT("Failed to create new FXMAAudioInfo for SoundWave %s: out of memory."), *SoundWave->GetName());
|
|
return nullptr;
|
|
}
|
|
return CompressedInfo;
|
|
}
|
|
#endif
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Check for errors and output a human readable string
|
|
*/
|
|
bool FXAudio2Device::ValidateAPICall( const TCHAR* Function, uint32 ErrorCode )
|
|
{
|
|
return DeviceProperties->Validate(Function, ErrorCode);
|
|
}
|
|
|
|
/**
|
|
* Derives the output matrix to use based on the channel mask and the number of channels
|
|
*/
|
|
const float OutputMatrixMono[SPEAKER_COUNT] =
|
|
{
|
|
0.7f, 0.7f, 0.5f, 0.0f, 0.5f, 0.5f
|
|
};
|
|
|
|
const float OutputMatrix2dot0[SPEAKER_COUNT * 2] =
|
|
{
|
|
1.0f, 0.0f, 0.7f, 0.0f, 1.25f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.7f, 0.0f, 0.0f, 1.25f // FR
|
|
};
|
|
|
|
// const float OutputMatrixDownMix[SPEAKER_COUNT * 2] =
|
|
// {
|
|
// 1.0f, 0.0f, 0.7f, 0.0f, 0.87f, 0.49f,
|
|
// 0.0f, 1.0f, 0.7f, 0.0f, 0.49f, 0.87f
|
|
// };
|
|
|
|
const float OutputMatrix2dot1[SPEAKER_COUNT * 3] =
|
|
{
|
|
// Same as stereo, but also passing in LFE signal
|
|
1.0f, 0.0f, 0.7f, 0.0f, 1.25f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.7f, 0.0f, 0.0f, 1.25f, // FR
|
|
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // LFE
|
|
};
|
|
|
|
const float OutputMatrix4dot0s[SPEAKER_COUNT * 4] =
|
|
{
|
|
// Combine both rear channels to make a rear center channel
|
|
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // FC
|
|
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f // RC
|
|
};
|
|
|
|
const float OutputMatrix4dot0[SPEAKER_COUNT * 4] =
|
|
{
|
|
// Split the center channel to the front two speakers
|
|
1.0f, 0.0f, 0.7f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.7f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, // RL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f // RR
|
|
};
|
|
|
|
const float OutputMatrix4dot1[SPEAKER_COUNT * 5] =
|
|
{
|
|
// Split the center channel to the front two speakers
|
|
1.0f, 0.0f, 0.7f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.7f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // LFE
|
|
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, // RL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f // RR
|
|
};
|
|
|
|
const float OutputMatrix5dot0[SPEAKER_COUNT * 5] =
|
|
{
|
|
// Split the center channel to the front two speakers
|
|
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // FC
|
|
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, // SL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f // SR
|
|
};
|
|
|
|
const float OutputMatrix5dot1[SPEAKER_COUNT * 6] =
|
|
{
|
|
// Classic 5.1 setup
|
|
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // FC
|
|
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // LFE
|
|
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, // RL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f // RR
|
|
};
|
|
|
|
const float OutputMatrix5dot1s[SPEAKER_COUNT * 6] =
|
|
{
|
|
// Classic 5.1 setup
|
|
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // FC
|
|
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // LFE
|
|
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, // SL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f // SR
|
|
};
|
|
|
|
const float OutputMatrix6dot1[SPEAKER_COUNT * 7] =
|
|
{
|
|
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // FC
|
|
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // LFE
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, // RL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.8f, // RR
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, // RC
|
|
};
|
|
|
|
const float OutputMatrix7dot1[SPEAKER_COUNT * 8] =
|
|
{
|
|
0.7f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 0.7f, 0.0f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 0.7f, 0.0f, 0.0f, 0.0f, // FC
|
|
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // LFE
|
|
0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, // RL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, // RR
|
|
0.7f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, // FCL
|
|
0.0f, 0.7f, 0.5f, 0.0f, 0.0f, 0.0f // FCR
|
|
};
|
|
|
|
const float OutputMatrix7dot1s[SPEAKER_COUNT * 8] =
|
|
{
|
|
// Split the rear channels evenly between side and rear
|
|
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FL
|
|
0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // FR
|
|
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // FC
|
|
0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // LFE
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.7f, 0.0f, // RL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.7f, // RR
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.7f, 0.0f, // SL
|
|
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.7f // SR
|
|
};
|
|
|
|
typedef struct SOuputMapping
|
|
{
|
|
uint32 NumChannels;
|
|
uint32 SpeakerMask;
|
|
const float* OuputMatrix;
|
|
} TOuputMapping;
|
|
|
|
TOuputMapping OutputMappings[] =
|
|
{
|
|
{ 1, SPEAKER_MONO, OutputMatrixMono },
|
|
{ 2, SPEAKER_STEREO, OutputMatrix2dot0 },
|
|
{ 3, SPEAKER_2POINT1, OutputMatrix2dot1 },
|
|
{ 4, SPEAKER_SURROUND, OutputMatrix4dot0s },
|
|
{ 4, SPEAKER_QUAD, OutputMatrix4dot0 },
|
|
{ 5, SPEAKER_4POINT1, OutputMatrix4dot1 },
|
|
{ 5, SPEAKER_5POINT0, OutputMatrix5dot0 },
|
|
{ 6, SPEAKER_5POINT1, OutputMatrix5dot1 },
|
|
{ 6, SPEAKER_5POINT1_SURROUND, OutputMatrix5dot1s },
|
|
{ 7, SPEAKER_6POINT1, OutputMatrix6dot1 },
|
|
{ 8, SPEAKER_7POINT1, OutputMatrix7dot1 },
|
|
{ 8, SPEAKER_7POINT1_SURROUND, OutputMatrix7dot1s }
|
|
};
|
|
|
|
bool FXAudio2Device::GetOutputMatrix( uint32 ChannelMask, uint32 NumChannels )
|
|
{
|
|
// Default is vanilla stereo
|
|
FXAudioDeviceProperties::OutputMixMatrix = OutputMatrix2dot0;
|
|
|
|
// Find the best match
|
|
for( int32 MappingIndex = 0; MappingIndex < sizeof( OutputMappings ) / sizeof( TOuputMapping ); MappingIndex++ )
|
|
{
|
|
if( NumChannels != OutputMappings[MappingIndex].NumChannels )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if( ( ChannelMask & OutputMappings[MappingIndex].SpeakerMask ) != ChannelMask )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FXAudioDeviceProperties::OutputMixMatrix = OutputMappings[MappingIndex].OuputMatrix;
|
|
break;
|
|
}
|
|
|
|
return( FXAudioDeviceProperties::OutputMixMatrix != NULL );
|
|
}
|
|
|
|
|
|
/** Test decompress a vorbis file */
|
|
void FXAudio2Device::TestDecompressOggVorbis( USoundWave* Wave )
|
|
{
|
|
#if WITH_OGGVORBIS
|
|
FVorbisAudioInfo OggInfo;
|
|
FSoundQualityInfo QualityInfo = { 0 };
|
|
|
|
// Parse the ogg vorbis header for the relevant information
|
|
if( OggInfo.ReadCompressedInfo( Wave->ResourceData, Wave->ResourceSize, &QualityInfo ) )
|
|
{
|
|
// Extract the data
|
|
Wave->SetSampleRate(QualityInfo.SampleRate);
|
|
Wave->NumChannels = QualityInfo.NumChannels;
|
|
Wave->RawPCMDataSize = QualityInfo.SampleDataSize;
|
|
Wave->Duration = QualityInfo.Duration;
|
|
|
|
Wave->RawPCMData = ( uint8* )FMemory::Malloc( Wave->RawPCMDataSize );
|
|
|
|
// Decompress all the sample data (and preallocate memory)
|
|
OggInfo.ExpandFile( Wave->RawPCMData, &QualityInfo );
|
|
|
|
FMemory::Free( Wave->RawPCMData );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
/** Decompress a wav a number of times for profiling purposes */
|
|
void FXAudio2Device::TimeTest( FOutputDevice& Ar, const TCHAR* WaveAssetName )
|
|
{
|
|
USoundWave* Wave = LoadObject<USoundWave>( NULL, WaveAssetName, NULL, LOAD_NoWarn, NULL );
|
|
if( Wave )
|
|
{
|
|
// Wait for initial decompress
|
|
if (Wave->AudioDecompressor)
|
|
{
|
|
while (!Wave->AudioDecompressor->IsDone())
|
|
{
|
|
}
|
|
|
|
delete Wave->AudioDecompressor;
|
|
Wave->AudioDecompressor = NULL;
|
|
}
|
|
|
|
// If the wave loaded in fine, time the decompression
|
|
Wave->InitAudioResource(GetRuntimeFormat(Wave));
|
|
|
|
double Start = FPlatformTime::Seconds();
|
|
|
|
for( int32 i = 0; i < 1000; i++ )
|
|
{
|
|
TestDecompressOggVorbis( Wave );
|
|
}
|
|
|
|
double Duration = FPlatformTime::Seconds() - Start;
|
|
Ar.Logf( TEXT( "%s: %g ms - %g ms per second per channel" ), WaveAssetName, Duration, Duration / ( Wave->Duration * Wave->NumChannels ) );
|
|
|
|
Wave->RemoveAudioResource();
|
|
}
|
|
else
|
|
{
|
|
Ar.Logf( TEXT( "Failed to find test file '%s' to decompress" ), WaveAssetName );
|
|
}
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
/**
|
|
* Exec handler used to parse console commands.
|
|
*
|
|
* @param InWorld World context
|
|
* @param Cmd Command to parse
|
|
* @param Ar Output device to use in case the handler prints anything
|
|
* @return true if command was handled, false otherwise
|
|
*/
|
|
bool FXAudio2Device::Exec( UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar )
|
|
{
|
|
if( FAudioDevice::Exec( InWorld, Cmd, Ar ) )
|
|
{
|
|
return( true );
|
|
}
|
|
#if !UE_BUILD_SHIPPING
|
|
else if( FParse::Command( &Cmd, TEXT( "TestVorbisDecompressionSpeed" ) ) )
|
|
{
|
|
|
|
TimeTest( Ar, TEXT( "TestSounds.44Mono_TestWeaponSynthetic" ) );
|
|
TimeTest( Ar, TEXT( "TestSounds.44Mono_TestDialogFemale" ) );
|
|
TimeTest( Ar, TEXT( "TestSounds.44Mono_TestDialogMale" ) );
|
|
|
|
TimeTest( Ar, TEXT( "TestSounds.22Mono_TestWeaponSynthetic" ) );
|
|
TimeTest( Ar, TEXT( "TestSounds.22Mono_TestDialogFemale" ) );
|
|
TimeTest( Ar, TEXT( "TestSounds.22Mono_TestDialogMale" ) );
|
|
|
|
TimeTest( Ar, TEXT( "TestSounds.22Stereo_TestMusicAcoustic" ) );
|
|
TimeTest( Ar, TEXT( "TestSounds.44Stereo_TestMusicAcoustic" ) );
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
return( false );
|
|
}
|
|
|
|
FAudioPlatformSettings FXAudio2Device::GetPlatformSettings() const
|
|
{
|
|
return FAudioPlatformSettings::GetPlatformSettings(FPlatformProperties::GetRuntimeSettingsClassName());
|
|
}
|