Files
UnrealEngineUWP/Engine/Source/Runtime/Windows/XAudio2/Private/XAudio2Buffer.cpp
maxwell hayes a781a97922 Improvements to Metasound Wave decoding
#jira UE-108501, UE-107548, UEAU-693
#rb Phil.Popp

#ROBOMERGE-SOURCE: CL 15564761 in //UE5/Release-5.0-EarlyAccess/...
#ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v771-15082668)

[CL 15564769 by maxwell hayes in ue5-main branch]
2021-03-01 20:25:28 -04:00

610 lines
17 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 "AudioDecompress.h"
#include "AudioEffect.h"
#include "XAudio2Effects.h"
#include "XAudio2Support.h"
#include "Interfaces/IAudioFormat.h"
#if XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
/**
* Helper structure to access information in raw XMA data.
*/
struct FXMAInfo
{
/**
* Constructor, parsing passed in raw data.
*
* @param RawData raw XMA data
* @param RawDataSize size of raw data in bytes
*/
FXMAInfo( uint8* RawData, uint32 RawDataSize )
{
// Check out XeTools.cpp/dll.
uint32 Offset = 0;
FMemory::Memcpy( &EncodedBufferFormatSize, RawData + Offset, sizeof( uint32 ) );
Offset += sizeof( uint32 );
FMemory::Memcpy( &SeekTableSize, RawData + Offset, sizeof( uint32 ) );
Offset += sizeof( uint32 );
FMemory::Memcpy( &EncodedBufferSize, RawData + Offset, sizeof( uint32 ) );
Offset += sizeof( uint32 );
//@warning EncodedBufferFormat is NOT endian swapped.
EncodedBufferFormat = ( XMA2WAVEFORMATEX* )( RawData + Offset );
Offset += EncodedBufferFormatSize;
SeekTable = ( uint32* )( RawData + Offset );
Offset += SeekTableSize;
EncodedBuffer = RawData + Offset;
Offset += EncodedBufferSize;
check( Offset == RawDataSize );
}
/** Encoded buffer data (allocated via malloc from within XMA encoder) */
void* EncodedBuffer;
/** Size in bytes of encoded buffer */
uint32 EncodedBufferSize;
/** Encoded buffer format (allocated via malloc from within XMA encoder) */
void* EncodedBufferFormat;
/** Size in bytes of encoded buffer format */
uint32 EncodedBufferFormatSize;
/** Seek table (allocated via malloc from within XMA encoder) */
uint32* SeekTable;
/** Size in bytes of seek table */
uint32 SeekTableSize;
};
#endif //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
/*------------------------------------------------------------------------------------
FXAudio2SoundBuffer.
------------------------------------------------------------------------------------*/
FXAudio2SoundBuffer::FXAudio2SoundBuffer( FAudioDevice* InAudioDevice, ESoundFormat InSoundFormat )
: FSoundBuffer(InAudioDevice)
, SoundFormat(InSoundFormat)
, DecompressionState(nullptr)
, RealtimeAsyncHeaderParseTask(nullptr)
, bDynamicResource(false)
{
PCM.PCMData = NULL;
PCM.PCMDataSize = 0;
#if XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
XMA2.XMA2Data = NULL;
XMA2.XMA2DataSize = 0;
#endif //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
XWMA.XWMAData = NULL;
XWMA.XWMADataSize = 0;
XWMA.XWMASeekData = NULL;
XWMA.XWMASeekDataSize = 0;
}
FXAudio2SoundBuffer::~FXAudio2SoundBuffer( void )
{
if( bAllocationInPermanentPool )
{
UE_LOG(LogXAudio2, Fatal, TEXT( "Can't free resource '%s' as it was allocated in permanent pool." ), *ResourceName );
}
check(RealtimeAsyncHeaderParseTask == nullptr);
if( DecompressionState )
{
delete DecompressionState;
}
switch( SoundFormat )
{
case SoundFormat_PCM:
if( PCM.PCMData )
{
FMemory::Free( ( void* )PCM.PCMData );
}
break;
case SoundFormat_PCMPreview:
if( bDynamicResource && PCM.PCMData )
{
FMemory::Free( ( void* )PCM.PCMData );
}
break;
case SoundFormat_PCMRT:
case SoundFormat_Streaming:
// Buffers are freed as part of the ~FSoundSource
break;
case SoundFormat_XMA2:
#if XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
if( XMA2.XMA2Data )
{
// Wave data was kept in pBuffer so we need to free it.
FMemory::Free( ( void* )XMA2.XMA2Data );
}
#endif //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
break;
case SoundFormat_XWMA:
if( XWMA.XWMAData )
{
// Wave data was kept in pBuffer so we need to free it.
FMemory::Free( ( void* )XWMA.XWMAData );
}
if( XWMA.XWMASeekData )
{
// Wave data was kept in pBuffer so we need to free it.
FMemory::Free( ( void* )XWMA.XWMASeekData );
}
break;
}
}
int32 FXAudio2SoundBuffer::GetSize( void )
{
int32 TotalSize = 0;
switch( SoundFormat )
{
case SoundFormat_PCM:
TotalSize = PCM.PCMDataSize;
break;
case SoundFormat_PCMPreview:
TotalSize = PCM.PCMDataSize;
break;
case SoundFormat_PCMRT:
TotalSize = (DecompressionState ? DecompressionState->GetSourceBufferSize() : 0) + ( MONO_PCM_BUFFER_SIZE * 2 * NumChannels );
break;
case SoundFormat_XMA2:
#if XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
TotalSize = XMA2.XMA2DataSize;
#else //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
TotalSize = 0;
#endif //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
break;
case SoundFormat_XWMA:
TotalSize = XWMA.XWMADataSize + XWMA.XWMASeekDataSize;
break;
case SoundFormat_Streaming:
TotalSize = ( MONO_PCM_BUFFER_SIZE * 2 * NumChannels );
break;
}
return( TotalSize );
}
int32 FXAudio2SoundBuffer::GetCurrentChunkIndex() const
{
if (DecompressionState)
{
return DecompressionState->GetCurrentChunkIndex();
}
return INDEX_NONE;
}
int32 FXAudio2SoundBuffer::GetCurrentChunkOffset() const
{
if (DecompressionState)
{
return DecompressionState->GetCurrentChunkOffset();
}
return INDEX_NONE;
}
bool FXAudio2SoundBuffer::IsRealTimeSourceReady()
{
// If we have a realtime async header parse task, then we check if its done
if (RealtimeAsyncHeaderParseTask)
{
bool bIsDone = RealtimeAsyncHeaderParseTask->IsDone();
if (bIsDone)
{
delete RealtimeAsyncHeaderParseTask;
RealtimeAsyncHeaderParseTask = nullptr;
}
return bIsDone;
}
// Otherwise, we weren't a real time decoding sound buffer (or we've already asked and it was ready)
return true;
}
void FXAudio2SoundBuffer::EnsureRealtimeTaskCompletion()
{
if (RealtimeAsyncHeaderParseTask)
{
RealtimeAsyncHeaderParseTask->EnsureCompletion();
delete RealtimeAsyncHeaderParseTask;
RealtimeAsyncHeaderParseTask = nullptr;
}
}
void FXAudio2SoundBuffer::InitWaveFormatEx( uint16 Format, USoundWave* Wave, bool bCheckPCMData )
{
// Setup the format structure required for XAudio2
PCM.PCMFormat.wFormatTag = Format;
PCM.PCMFormat.nChannels = Wave->NumChannels;
PCM.PCMFormat.nSamplesPerSec = Wave->GetSampleRateForCurrentPlatform();
PCM.PCMFormat.wBitsPerSample = 16;
PCM.PCMFormat.cbSize = 0;
// Set the number of channels - 0 channels means there has been an error
NumChannels = Wave->NumChannels;
if( bCheckPCMData )
{
if( PCM.PCMData == NULL || PCM.PCMDataSize == 0 )
{
NumChannels = 0;
UE_LOG(LogXAudio2, Warning, TEXT( "Failed to create audio buffer for '%s'" ), *Wave->GetFullName() );
}
}
PCM.PCMFormat.nBlockAlign = NumChannels * sizeof( int16 );
PCM.PCMFormat.nAvgBytesPerSec = NumChannels * sizeof( int16 ) * Wave->GetSampleRateForCurrentPlatform();
}
void FXAudio2SoundBuffer::InitXMA2( FXAudio2Device* XAudio2Device, USoundWave* Wave, FXMAInfo* XMAInfo )
{
#if XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
SoundFormat = SoundFormat_XMA2;
FMemory::Memcpy( &XMA2, XMAInfo->EncodedBufferFormat, XMAInfo->EncodedBufferFormatSize );
NumChannels = XMA2.XMA2Format.wfx.nChannels;
// Allocate the audio data in physical memory
XMA2.XMA2DataSize = XMAInfo->EncodedBufferSize;
if( Wave->IsRooted() )
{
// Allocate from permanent pool and mark buffer as non destructible.
bool AllocatedInPool = true;
XMA2.XMA2Data = ( uint8* )XAudio2Device->AllocatePermanentMemory( XMA2.XMA2DataSize, /*OUT*/ AllocatedInPool );
bAllocationInPermanentPool = AllocatedInPool;
}
else
{
// Allocate via normal allocator.
XMA2.XMA2Data = ( uint8* )FMemory::Malloc( XMA2.XMA2DataSize );
}
FMemory::Memcpy( ( void* )XMA2.XMA2Data, XMAInfo->EncodedBuffer, XMAInfo->EncodedBufferSize );
#else //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
checkf(0, TEXT("XMA2 not supported!"));
#endif //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
}
void FXAudio2SoundBuffer::InitXWMA( USoundWave* Wave, FXMAInfo* XMAInfo )
{
#if XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
SoundFormat = SoundFormat_XWMA;
FMemory::Memcpy( &XWMA.XWMAFormat, XMAInfo->EncodedBufferFormat, XMAInfo->EncodedBufferFormatSize );
NumChannels = XWMA.XWMAFormat.Format.nChannels;
// Allocate the audio data in physical memory
XWMA.XWMADataSize = XMAInfo->EncodedBufferSize;
// Allocate via normal allocator.
XWMA.XWMAData = ( uint8* )FMemory::Malloc( XWMA.XWMADataSize );
FMemory::Memcpy( ( void* )XWMA.XWMAData, XMAInfo->EncodedBuffer, XMAInfo->EncodedBufferSize );
XWMA.XWMASeekDataSize = XMAInfo->SeekTableSize;
XWMA.XWMASeekData = ( UINT32* )FMemory::Malloc( XWMA.XWMASeekDataSize );
FMemory::Memcpy( ( void* )XWMA.XWMASeekData, XMAInfo->SeekTable, XMAInfo->SeekTableSize );
#else //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
checkf(0, TEXT("XMA2WAVEFORMATEX not supported!"));
#endif //XAUDIO_SUPPORTS_XMA2WAVEFORMATEX
}
bool FXAudio2SoundBuffer::ReadCompressedInfo(USoundWave* SoundWave)
{
if (!DecompressionState)
{
UE_LOG(LogXAudio2, Warning, TEXT("Attempting to read compressed info without a compression state instance for resource '%s'"), *ResourceName);
return false;
}
return DecompressionState->ReadCompressedInfo(SoundWave->ResourceData, SoundWave->ResourceSize, nullptr);
}
bool FXAudio2SoundBuffer::ReadCompressedData( uint8* Destination, int32 NumFramesToDecode, bool bLooping )
{
if (!DecompressionState)
{
UE_LOG(LogXAudio2, Warning, TEXT( "Attempting to read compressed data without a compression state instance for resource '%s'" ), *ResourceName );
return false;
}
int32 NumBytesToDecode = NumFramesToDecode * sizeof(int16) * NumChannels;
int32 NumBytesDecoded = NumBytesToDecode;
if (SoundFormat == SoundFormat_Streaming)
{
return DecompressionState->StreamCompressedData(Destination, bLooping, NumBytesToDecode, NumBytesDecoded);
}
else
{
return DecompressionState->ReadCompressedData(Destination, bLooping, NumBytesToDecode);
}
}
void FXAudio2SoundBuffer::Seek( const float SeekTime )
{
if (DecompressionState)
{
DecompressionState->SeekToTime(SeekTime);
}
}
FXAudio2SoundBuffer* FXAudio2SoundBuffer::CreateQueuedBuffer( FXAudio2Device* XAudio2Device, USoundWave* Wave )
{
check(XAudio2Device);
check(Wave);
check(Wave->GetPrecacheState() == ESoundWavePrecacheState::Done);
// Always create a new buffer for real time decompressed sounds
FXAudio2SoundBuffer* Buffer = new FXAudio2SoundBuffer( XAudio2Device, SoundFormat_PCMRT );
// If the buffer was precached as native, the resource data will have been lost and we need to re-initialize it
if (Wave->ResourceData == nullptr)
{
Wave->InitAudioResource(XAudio2Device->GetRuntimeFormat(Wave));
}
Buffer->DecompressionState = XAudio2Device->CreateCompressedAudioInfo(Wave);
if (Buffer->DecompressionState)
{
// Start the async task that parses the decompressed asset header info
// Doing this step synchronously causes huge main-thread hitches for large ogg-vorbis files
check(Buffer->RealtimeAsyncHeaderParseTask == nullptr);
Buffer->RealtimeAsyncHeaderParseTask = new FAsyncRealtimeAudioTask(Buffer, Wave);
Buffer->RealtimeAsyncHeaderParseTask->StartBackgroundTask();
// Clear out any dangling pointers
Buffer->PCM.PCMData = NULL;
Buffer->PCM.PCMDataSize = 0;
Buffer->InitWaveFormatEx( WAVE_FORMAT_PCM, Wave, false );
}
else
{
Wave->DecompressionType = DTYPE_Invalid;
Wave->NumChannels = 0;
Wave->RemoveAudioResource();
}
return Buffer;
}
FXAudio2SoundBuffer* FXAudio2SoundBuffer::CreateProceduralBuffer( FXAudio2Device* XAudio2Device, USoundWave* Wave )
{
// Always create a new buffer for real time decompressed sounds
FXAudio2SoundBuffer* Buffer = new FXAudio2SoundBuffer( XAudio2Device, SoundFormat_PCMRT );
// Clear out any dangling pointers
Buffer->DecompressionState = NULL;
Buffer->PCM.PCMData = NULL;
Buffer->PCM.PCMDataSize = 0;
Buffer->InitWaveFormatEx( WAVE_FORMAT_PCM, Wave, false );
// No tracking of this resource as it's temporary
Buffer->ResourceID = 0;
Wave->ResourceID = 0;
return( Buffer );
}
FXAudio2SoundBuffer* FXAudio2SoundBuffer::CreatePreviewBuffer( FXAudio2Device* XAudio2Device, USoundWave* Wave, FXAudio2SoundBuffer* Buffer )
{
FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();
check(AudioDeviceManager != nullptr);
if (Buffer)
{
AudioDeviceManager->FreeBufferResource(Buffer);
}
// Create new buffer.
Buffer = new FXAudio2SoundBuffer( XAudio2Device, SoundFormat_PCMPreview );
check(!Wave->RawPCMData || Wave->RawPCMDataSize);
// Take ownership the PCM data
Buffer->PCM.PCMData = Wave->RawPCMData;
Buffer->PCM.PCMDataSize = Wave->RawPCMDataSize;
Wave->RawPCMData = NULL;
// Copy over whether this data should be freed on delete
Buffer->bDynamicResource = Wave->bDynamicResource;
Buffer->InitWaveFormatEx( WAVE_FORMAT_PCM, Wave, true );
AudioDeviceManager->TrackResource(Wave, Buffer);
return( Buffer );
}
FXAudio2SoundBuffer* FXAudio2SoundBuffer::CreateNativeBuffer( FXAudio2Device* XAudio2Device, USoundWave* Wave )
{
check(Wave->GetPrecacheState() == ESoundWavePrecacheState::Done);
// Create new buffer.
FXAudio2SoundBuffer* Buffer = new FXAudio2SoundBuffer( XAudio2Device, SoundFormat_PCM );
check(!Wave->RawPCMData || Wave->RawPCMDataSize);
// Take ownership the PCM data
Buffer->PCM.PCMData = Wave->RawPCMData;
Buffer->PCM.PCMDataSize = Wave->RawPCMDataSize;
Wave->RawPCMData = NULL;
// Keep track of associated resource name.
Buffer->InitWaveFormatEx( WAVE_FORMAT_PCM, Wave, true );
FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();
check(AudioDeviceManager != nullptr);
AudioDeviceManager->TrackResource(Wave, Buffer);
Wave->RemoveAudioResource();
return( Buffer );
}
FXAudio2SoundBuffer* FXAudio2SoundBuffer::CreateStreamingBuffer( FXAudio2Device* XAudio2Device, USoundWave* Wave )
{
// Always create a new buffer for streaming sounds
FXAudio2SoundBuffer* Buffer = new FXAudio2SoundBuffer(XAudio2Device, SoundFormat_Streaming);
// Prime the first two buffers and prepare the decompression
FSoundQualityInfo QualityInfo = { 0 };
Buffer->DecompressionState = XAudio2Device->CreateCompressedAudioInfo(Wave);
if (Buffer->DecompressionState->StreamCompressedInfo(Wave, &QualityInfo))
{
// Refresh the wave data
Wave->SetSampleRate(QualityInfo.SampleRate);
Wave->NumChannels = QualityInfo.NumChannels;
if (QualityInfo.SampleDataSize != 0)
{
Wave->RawPCMDataSize = QualityInfo.SampleDataSize;
}
if (QualityInfo.Duration != 0.0f)
{
Wave->Duration = QualityInfo.Duration;
}
Buffer->InitWaveFormatEx(WAVE_FORMAT_PCM, Wave, false);
// Clear out any dangling pointers
Buffer->PCM.PCMData = NULL;
Buffer->PCM.PCMDataSize = 0;
return Buffer;
}
else
{
Wave->DecompressionType = DTYPE_Invalid;
Wave->NumChannels = 0;
delete Buffer;
Buffer = nullptr;
return nullptr;
}
}
FXAudio2SoundBuffer* FXAudio2SoundBuffer::Init( FAudioDevice* AudioDevice, USoundWave* Wave, bool bForceRealTime )
{
// Can't create a buffer without any source data
if( Wave == NULL || Wave->NumChannels == 0 )
{
return( NULL );
}
#if WITH_EDITOR
Wave->InvalidateSoundWaveIfNeccessary();
#endif // WITH_EDITOR
FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager();
FXAudio2Device* XAudio2Device = ( FXAudio2Device* )AudioDevice;
FXAudio2SoundBuffer* Buffer = NULL;
// Allow the precache to happen if necessary
EDecompressionType DecompressionType = Wave->DecompressionType;
if (bForceRealTime && DecompressionType != DTYPE_Setup && DecompressionType != DTYPE_Streaming)
{
DecompressionType = DTYPE_RealTime;
}
switch( DecompressionType )
{
case DTYPE_Setup:
// Has circumvented precache mechanism - precache now
AudioDevice->Precache(Wave, true, false);
// if it didn't change, we will recurse forever
check(Wave->DecompressionType != DTYPE_Setup);
// Recall this function with new decompression type
return( Init( AudioDevice, Wave, bForceRealTime ) );
case DTYPE_Preview:
// Find the existing buffer if any
if( Wave->ResourceID )
{
Buffer = (FXAudio2SoundBuffer*)AudioDeviceManager->GetSoundBufferForResourceID(Wave->ResourceID);
}
// Override with any new PCM data even if some already exists.
if( Wave->RawPCMData )
{
// Upload the preview PCM data to it
Buffer = CreatePreviewBuffer( XAudio2Device, Wave, Buffer );
}
break;
case DTYPE_Procedural:
// Always create a new buffer for streaming procedural data
Buffer = CreateProceduralBuffer( XAudio2Device, Wave );
break;
case DTYPE_RealTime:
// Always create a new buffer for streaming ogg vorbis data
Buffer = CreateQueuedBuffer( XAudio2Device, Wave );
break;
case DTYPE_Native:
// Upload entire wav to XAudio2
if( Wave->ResourceID )
{
Buffer = (FXAudio2SoundBuffer*)AudioDeviceManager->GetSoundBufferForResourceID(Wave->ResourceID);
}
if( Buffer == NULL )
{
Buffer = CreateNativeBuffer( XAudio2Device, Wave );
}
break;
case DTYPE_Streaming:
// Always create a new buffer for streaming sounds
Buffer = CreateStreamingBuffer( XAudio2Device, Wave );
break;
case DTYPE_Invalid:
default:
// Invalid will be set if the wave cannot be played
break;
}
return( Buffer );
}