506 lines
16 KiB
C++
506 lines
16 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "RadAudioInfo.h"
|
|
#include "Interfaces/IAudioFormat.h"
|
|
|
|
#include "Modules/ModuleInterface.h"
|
|
|
|
#include "rada_file_header.h"
|
|
#include "rada_decode.h"
|
|
|
|
#if !defined(PLATFORM_LITTLE_ENDIAN) || !PLATFORM_LITTLE_ENDIAN
|
|
#error "RAD Audio hasn't been updated for big endian."
|
|
#endif
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogRadAudioDecoder, Log, All);
|
|
#define PTR_ADD(ptr,off) ((void*)(((uint8*)(ptr))+(off)))
|
|
#define Align32( val ) ( ( ( val ) + 31 ) & ~31 )
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// All memory for the decoder is in one contiguous block:
|
|
//
|
|
// RadAudioDecoder structure
|
|
// RadAContainer (i.e. codec space)
|
|
// SeekTable
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
struct RadAudioDecoder
|
|
{
|
|
uint32 SeekTableByteCount;
|
|
|
|
// # of frames we need to eat before outputting data due to a sample
|
|
// accurate seek.
|
|
int32 ConsumeFrameCount;
|
|
uint16 OutputReservoirValidFrames;
|
|
uint16 OutputReservoirReadFrames;
|
|
|
|
RadAContainer* Container()
|
|
{
|
|
return (RadAContainer*)(this + 1);
|
|
}
|
|
|
|
};
|
|
|
|
FRadAudioInfo::FRadAudioInfo()
|
|
{
|
|
}
|
|
|
|
FRadAudioInfo::~FRadAudioInfo()
|
|
{
|
|
|
|
}
|
|
|
|
void FRadAudioInfo::PrepareToLoop()
|
|
{
|
|
#if WITH_RAD_AUDIO
|
|
RadANotifySeek(Decoder->Container());
|
|
#endif
|
|
}
|
|
|
|
uint32 FRadAudioInfo::GetMaxFrameSizeSamples() const
|
|
{
|
|
return RadADecodeBlock_MaxOutputFrames;
|
|
}
|
|
|
|
void FRadAudioInfo::SeekToTime(const float SeekTimeSeconds)
|
|
{
|
|
// If there's no seek table on the header, fall-back to Super implementation.
|
|
const RadAFileHeader* FileHeader = RadAGetFileHeaderFromContainer(Decoder->Container());
|
|
if (FileHeader->seek_table_entry_count == 0)
|
|
{
|
|
Super::SeekToTime(SeekTimeSeconds);
|
|
RadANotifySeek(Decoder->Container());
|
|
return;
|
|
}
|
|
|
|
// convert seconds to frames and call SeekToFrame
|
|
uint32 SeekTimeFrames = 0;
|
|
if (SeekTimeSeconds > 0)
|
|
{
|
|
SeekTimeFrames = (uint32)(SeekTimeSeconds * RadASampleRateFromEnum(FileHeader->sample_rate));
|
|
}
|
|
|
|
SeekToFrame(SeekTimeFrames);
|
|
}
|
|
|
|
void FRadAudioInfo::SeekToFrame(uint32 InSeekTimeFrames)
|
|
{
|
|
// If there's no seek table on the header, fall-back to Super implementation.
|
|
RadAContainer* Container = Decoder->Container();
|
|
const RadAFileHeader* FileHeader = RadAGetFileHeaderFromContainer(Container);
|
|
if (FileHeader->seek_table_entry_count == 0)
|
|
{
|
|
Super::SeekToFrame(InSeekTimeFrames);
|
|
RadANotifySeek(Container);
|
|
return;
|
|
}
|
|
|
|
if (InSeekTimeFrames >= FileHeader->frame_count)
|
|
{
|
|
InSeekTimeFrames = FileHeader->frame_count - 1;
|
|
}
|
|
|
|
// Since we opened the header and passed the overflow check, this is safe.
|
|
this->CurrentSampleCount = InSeekTimeFrames * NumChannels;
|
|
|
|
size_t FrameAtLocation = 0;
|
|
size_t OffsetToBlockS = RadASeekTableLookup(
|
|
Container,
|
|
InSeekTimeFrames,
|
|
&FrameAtLocation,
|
|
nullptr);
|
|
|
|
RadANotifySeek(Container);
|
|
|
|
if (OffsetToBlockS > TNumericLimits<int32>::Max())
|
|
{
|
|
UE_LOG(LogRadAudioDecoder, Error, TEXT("Seek block destination passed frame count caps but offset exceeds int32 limits (got: %zu"), OffsetToBlockS);
|
|
return;
|
|
}
|
|
int32 OffsetToBlock = (int32)OffsetToBlockS;
|
|
|
|
// Block based codec - we start decoding on a block boundary and need
|
|
// to eat frames to get to our actual spot.
|
|
Decoder->ConsumeFrameCount = InSeekTimeFrames - (int32)FrameAtLocation;
|
|
|
|
// After a seek we need to decode anew
|
|
Decoder->OutputReservoirValidFrames = 0;
|
|
Decoder->OutputReservoirReadFrames = 0;
|
|
|
|
//
|
|
// Here we need to set up the data we get to point at the right spot.
|
|
//
|
|
if (StreamingSoundWave == nullptr)
|
|
{
|
|
// If we aren't streaming we can just go directly to the offset we need.
|
|
this->SrcBufferOffset = OffsetToBlock;
|
|
}
|
|
else
|
|
{
|
|
const uint32 TotalStreamingChunks = StreamingSoundWave->GetNumChunks();
|
|
uint32 RemnOffset = OffsetToBlock;
|
|
|
|
bool bFoundBlock = false;
|
|
|
|
uint64 TotalChunkSizesExamined = 0;
|
|
|
|
// Find the chunk and offset to the block we need.
|
|
for (uint32 BlockIndex = 0; BlockIndex < TotalStreamingChunks; ++BlockIndex)
|
|
{
|
|
const uint32 SizeOfChunk = StreamingSoundWave->GetSizeOfChunk(BlockIndex);
|
|
TotalChunkSizesExamined += SizeOfChunk;
|
|
|
|
if (SizeOfChunk > RemnOffset)
|
|
{
|
|
// This is the block we need
|
|
// If we are in the current block *and* the current block doesn't need to be loaded,
|
|
// only then can we set the block offset directly. This is because AudioDecompress.cpp
|
|
// sets SrcBufferOffset to zero when switching to the next block.
|
|
if (this->CurrentChunkIndex != BlockIndex ||
|
|
this->SrcBufferData == nullptr)
|
|
{
|
|
// Need to seek to another block
|
|
this->StreamSeekBlockIndex = BlockIndex;
|
|
this->StreamSeekBlockOffset = RemnOffset;
|
|
}
|
|
else
|
|
{
|
|
// Seek within this block
|
|
this->SrcBufferOffset = RemnOffset;
|
|
}
|
|
bFoundBlock = true;
|
|
break;
|
|
}
|
|
|
|
RemnOffset -= SizeOfChunk;
|
|
}
|
|
|
|
if (bFoundBlock == false)
|
|
{
|
|
UE_LOG(LogRadAudioDecoder, Error, TEXT("Unable to find seek chunk for offset %u: ChunkCount: %d Total Chunk Size: %llu"), OffsetToBlock, TotalStreamingChunks, TotalChunkSizesExamined);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool FRadAudioInfo::ParseHeader(const uint8* InSrcBufferData, uint32 InSrcBufferDataSize, struct FSoundQualityInfo* QualityInfo)
|
|
{
|
|
SrcBufferData = InSrcBufferData;
|
|
SrcBufferDataSize = InSrcBufferDataSize;
|
|
SrcBufferOffset = 0;
|
|
CurrentSampleCount = 0;
|
|
|
|
const RadAFileHeader* FileHeader = RadAGetFileHeader(InSrcBufferData, InSrcBufferDataSize);
|
|
if (FileHeader == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (FPlatformMath::MultiplyAndCheckForOverflow<uint32>(FileHeader->frame_count, FileHeader->channels, TrueSampleCount) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
NumChannels = FileHeader->channels;
|
|
|
|
// seek_table_entry_count is 16 bit, no overflow possible
|
|
uint32 SeekTableSize = FileHeader->seek_table_entry_count * sizeof(uint16);
|
|
if (sizeof(RadAFileHeader) + SeekTableSize > InSrcBufferDataSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Store the offset to where the audio data begins.
|
|
AudioDataOffset = RadAGetBytesToOpen(FileHeader);
|
|
|
|
// Check we have all headers and seek table data in memory.
|
|
if (AudioDataOffset > InSrcBufferDataSize)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Write out the the header info
|
|
if (QualityInfo)
|
|
{
|
|
QualityInfo->SampleRate = RadASampleRateFromEnum(FileHeader->sample_rate);
|
|
QualityInfo->NumChannels = FileHeader->channels;
|
|
QualityInfo->SampleDataSize = FileHeader->frame_count * QualityInfo->NumChannels * sizeof(int16);
|
|
if (QualityInfo->SampleRate)
|
|
{
|
|
QualityInfo->Duration = (float)FileHeader->frame_count / QualityInfo->SampleRate;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
bool FRadAudioInfo::CreateDecoder()
|
|
{
|
|
check(SrcBufferOffset == 0);
|
|
|
|
const RadAFileHeader* FileHeader = RadAGetFileHeader(SrcBufferData, SrcBufferDataSize);
|
|
if (FileHeader == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32 DecoderMemoryNeeded = 0;
|
|
if (RadAGetMemoryNeededToOpen(SrcBufferData, SrcBufferDataSize, &DecoderMemoryNeeded) != 0)
|
|
{
|
|
UE_LOG(LogRadAudioDecoder, Error, TEXT("Invalid/insufficient data in FRadAudioInfo::CreateDecoder - bad buffer passed / bad cook? Size = %d"), SrcBufferDataSize);
|
|
if (SrcBufferDataSize > 8)
|
|
{
|
|
UE_LOG(LogRadAudioDecoder, Error, TEXT("First 8 bytes: 0x%llx"), *(uint64*)SrcBufferData);
|
|
}
|
|
return false; // we should have valid and sufficient data at this point.
|
|
}
|
|
|
|
uint32 TotalMemory = DecoderMemoryNeeded + sizeof(RadAudioDecoder);
|
|
|
|
//
|
|
// Allocate and save offsets
|
|
//
|
|
RawMemory.SetNumZeroed(TotalMemory);
|
|
Decoder = (RadAudioDecoder*)RawMemory.GetData();
|
|
|
|
// See layout discussion in class declaration
|
|
RadAContainer* Container = Decoder->Container();
|
|
|
|
if (RadAOpenDecoder(SrcBufferData, SrcBufferDataSize, Container, DecoderMemoryNeeded) == 0)
|
|
{
|
|
UE_LOG(LogRadAudioDecoder, Error, TEXT("Failed to open decoder, likely corrupted data."));
|
|
RawMemory.Empty();
|
|
return false;
|
|
}
|
|
|
|
// Set our buffer at the start of the audio data.
|
|
SrcBufferOffset = RadAGetBytesToOpen(FileHeader);
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
int32 FRadAudioInfo::GetFrameSize()
|
|
{
|
|
uint32 BufferSizeNeeded = 0;
|
|
RadAExamineBlockResult Result = RadAExamineBlock(Decoder->Container(), SrcBufferData + SrcBufferOffset, SrcBufferDataSize - SrcBufferOffset, &BufferSizeNeeded);
|
|
|
|
if (Result != RadAExamineBlockResult::Valid ||
|
|
BufferSizeNeeded > INT_MAX)
|
|
{
|
|
// Flag this as error so that the owning logic can clean up this decode.
|
|
bErrorStateLatch = true;
|
|
|
|
// Either malformed data, or not enough data. Since we break up the encoded file
|
|
// one block boundary, this should never happen and is considered a failure.
|
|
return 0;
|
|
}
|
|
|
|
return (int32)BufferSizeNeeded;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
FDecodeResult FRadAudioInfo::Decode(const uint8* CompressedData, const int32 CompressedDataSize, uint8* OutPCMData, const int32 OutputPCMDataSize)
|
|
{
|
|
uint32 RemnOutputFrames = OutputPCMDataSize / SampleStride;
|
|
uint32 RemnCompressedDataSize = CompressedDataSize;
|
|
const uint8* CompressedDataEnd = CompressedData + CompressedDataSize;
|
|
|
|
// \todo use a system-wide shared deinterleave buffer since it's virtually guaranteed we won't ever be
|
|
// using them at the same time.
|
|
TArray<float> DeinterleavedDecodeBuffer;
|
|
|
|
// \todo When we are used as a streamed sound the calling code consumes the entirety of the output
|
|
// reservoir in to its own reservoir, so we have duplicate reservoirs. UE-199172
|
|
// This could be done by making the output reservoir use the system wide temp buffer like the deinterleave buffer
|
|
// and just not request it if OutPCMData is big enough to go to directly. When streaming this will never happen,
|
|
// under normal reading it will happen consistently. We don't particularly need the output reservoir to exist
|
|
// next to us.
|
|
while (RemnOutputFrames)
|
|
{
|
|
// Drain the output reservoir before attempting a decode.
|
|
if (Decoder->OutputReservoirReadFrames < Decoder->OutputReservoirValidFrames)
|
|
{
|
|
uint32 AvailableFrames = Decoder->OutputReservoirValidFrames - Decoder->OutputReservoirReadFrames;
|
|
uint32 CopyFrames = FMath::Min(AvailableFrames, RemnOutputFrames);
|
|
|
|
uint32 CopyByteCount = SampleStride * CopyFrames;
|
|
uint32 CopyOffset = SampleStride * Decoder->OutputReservoirReadFrames;
|
|
FMemory::Memcpy(OutPCMData, OutputReservoir.GetData() + CopyOffset, CopyByteCount);
|
|
|
|
Decoder->OutputReservoirReadFrames += CopyFrames;
|
|
RemnOutputFrames -= CopyFrames;
|
|
OutPCMData += CopyByteCount;
|
|
|
|
if (RemnOutputFrames == 0)
|
|
{
|
|
// we filled entirely from the output reservoir
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (RemnCompressedDataSize == 0)
|
|
{
|
|
// This is the normal termination condition
|
|
break;
|
|
}
|
|
|
|
uint32 CompressedBytesNeeded = 0;
|
|
RadAExamineBlockResult BlockResult = RadAExamineBlock(Decoder->Container(), CompressedData, RemnCompressedDataSize, &CompressedBytesNeeded);
|
|
|
|
if (BlockResult != RadAExamineBlockResult::Valid)
|
|
{
|
|
// The splitting system should ensure that we only ever get complete blocks - so this is bizarre.
|
|
UE_LOG(LogRadAudioDecoder, Warning, TEXT("Invalid block in FRadAudioInfo::Decode: Result = %d, RemnSize = %d"), BlockResult, RemnCompressedDataSize);
|
|
if (RemnCompressedDataSize >= 8)
|
|
{
|
|
UE_LOG(LogRadAudioDecoder, Warning, TEXT("First 8 bytes of buffer: 0x%02x 0x%02x 0x%02x 0x%02x:0x%02x 0x%02x 0x%02x 0x%02x"),
|
|
CompressedData[0], CompressedData[1], CompressedData[2], CompressedData[3], CompressedData[4], CompressedData[5], CompressedData[6], CompressedData[7]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// RadAudio outputs deinterleaved 32 bit float buffers - we don't want to carry around
|
|
// those buffers all the time and rad audio uses a pretty healthy amount of stack already
|
|
// so we drop these in the temp buffers.
|
|
if (DeinterleavedDecodeBuffer.Num() == 0)
|
|
{
|
|
DeinterleavedDecodeBuffer.AddUninitialized(RadADecodeBlock_MaxOutputFrames * NumChannels);
|
|
}
|
|
|
|
size_t CompressedDataConsumed = 0;
|
|
int16 DecodeResult = RadADecodeBlock(Decoder->Container(), CompressedData, RemnCompressedDataSize, DeinterleavedDecodeBuffer.GetData(), RadADecodeBlock_MaxOutputFrames, &CompressedDataConsumed);
|
|
if (DecodeResult == RadADecodeBlock_Error)
|
|
{
|
|
UE_LOG(LogRadAudioDecoder, Error, TEXT("Failed to decode block that passed validation checks, corrupted buffer?"));
|
|
bErrorStateLatch = true;
|
|
return FDecodeResult();
|
|
}
|
|
else if (DecodeResult == RadADecodeBlock_Done)
|
|
{
|
|
// There's no more data - return what we have.
|
|
break;
|
|
}
|
|
|
|
CompressedData += CompressedDataConsumed;
|
|
RemnCompressedDataSize -= CompressedDataConsumed;
|
|
|
|
// Where to start reading the decoded results from, for trimming.
|
|
int16 DecodeResultOffset = 0;
|
|
|
|
// Check if we need to eat some frames due to a sample-accurate seek.
|
|
if (Decoder->ConsumeFrameCount)
|
|
{
|
|
int16 ConsumedThisTime = DecodeResult;
|
|
if (Decoder->ConsumeFrameCount < DecodeResult)
|
|
{
|
|
ConsumedThisTime = (int16)Decoder->ConsumeFrameCount;
|
|
}
|
|
|
|
if (ConsumedThisTime)
|
|
{
|
|
DecodeResultOffset = ConsumedThisTime;
|
|
DecodeResult -= ConsumedThisTime;
|
|
}
|
|
Decoder->ConsumeFrameCount -= ConsumedThisTime;
|
|
}
|
|
|
|
// It's entirely valid to get 0 frames after a seek or otherwise - so we just try again.
|
|
if (DecodeResult == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If we can write directly to the output, then do so and avoid ever allocating an output reservoir if possible.
|
|
int16* InterleaveDestination = (int16_t*)OutPCMData;
|
|
if (RemnOutputFrames < RadADecodeBlock_MaxOutputFrames)
|
|
{
|
|
// we need to route through the output reservoir. Always fill to max capacity.
|
|
if (OutputReservoir.Num() == 0)
|
|
{
|
|
OutputReservoir.AddUninitialized(RadADecodeBlock_MaxOutputFrames * SampleStride);
|
|
}
|
|
InterleaveDestination = (int16*)OutputReservoir.GetData();
|
|
}
|
|
|
|
// Interleave in to the output buffer, and convert to 16 bit.
|
|
// The buffers are [0..DecodeResult...1024..1024+DecodeResult...n*1024..n*1024+DecodeResult]
|
|
// We want: [0.1...n, DecodeResult...DecodeResult+n]
|
|
#ifdef RADA_HAS_INTERLEAVING
|
|
const float* DeinterleavedSources[RADA_MAX_CHANNELS];
|
|
for (int32 ChannelIdx = 0; ChannelIdx < NumChannels; ChannelIdx++)
|
|
{
|
|
DeinterleavedSources[ChannelIdx] = DeinterleavedDecodeBuffer.GetData() + RadADecodeBlock_MaxOutputFrames * ChannelIdx + DecodeResultOffset;
|
|
}
|
|
RadAInterleave(InterleaveDestination, DeinterleavedSources, NumChannels, DecodeResult);
|
|
|
|
#else
|
|
for (int32 ChannelIdx = 0; ChannelIdx < NumChannels; ChannelIdx++)
|
|
{
|
|
const float* InBuffer = DeinterleavedDecodeBuffer.GetData() + RadADecodeBlock_MaxOutputFrames*ChannelIdx + DecodeResultOffset;
|
|
|
|
for (int32 SampleIdx = 0; SampleIdx < DecodeResult; SampleIdx++)
|
|
{
|
|
float InBufferFloat = InBuffer[SampleIdx] * 32768.0f;
|
|
if (InBufferFloat > 32767)
|
|
{
|
|
InBufferFloat = 32767;
|
|
}
|
|
else if (InBufferFloat < -32768)
|
|
{
|
|
InBufferFloat = -32768;
|
|
}
|
|
|
|
InterleaveDestination[ChannelIdx + (SampleIdx * NumChannels)] = (int16)InBufferFloat;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (InterleaveDestination == (int16*)OutputReservoir.GetData())
|
|
{
|
|
Decoder->OutputReservoirValidFrames = DecodeResult;
|
|
Decoder->OutputReservoirReadFrames = 0;
|
|
// Fall through to the next loop to copy the decoded pcm data out of the reservoir.
|
|
}
|
|
else
|
|
{
|
|
// we need to update the dest pointer since we went direct.
|
|
RemnOutputFrames -= DecodeResult;
|
|
OutPCMData += DecodeResult*SampleStride;
|
|
}
|
|
|
|
} // while need output pcm data
|
|
|
|
// We get here if we filled the output buffer or not.
|
|
FDecodeResult Result;
|
|
Result.NumPcmBytesProduced = OutputPCMDataSize - (RemnOutputFrames * SampleStride);
|
|
Result.NumAudioFramesProduced = Result.NumPcmBytesProduced / SampleStride;
|
|
Result.NumCompressedBytesConsumed = CompressedDataSize - RemnCompressedDataSize;
|
|
return Result;
|
|
}
|
|
|
|
bool FRadAudioInfo::HasError() const
|
|
{
|
|
return bErrorStateLatch || Super::HasError();
|
|
}
|
|
|
|
class RADAUDIODECODER_API FRadAudioDecoderModule : public IModuleInterface
|
|
{
|
|
public:
|
|
TUniquePtr<IAudioInfoFactory> Factory;
|
|
|
|
virtual void StartupModule() override
|
|
{
|
|
Factory = MakeUnique<FSimpleAudioInfoFactory>([] { return new FRadAudioInfo(); }, Audio::NAME_RADA);
|
|
}
|
|
|
|
virtual void ShutdownModule() override {}
|
|
};
|
|
|
|
IMPLEMENT_MODULE(FRadAudioDecoderModule, RadAudioDecoder)
|