You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Removed redundant private include paths from build.cs files. Fixed include paths to be relative to the private or public folders. Hid or removed includes that reached into other private module folders. Updated PublicInclude paths when necessary. #jira #preflight 631e281694758d0bf2ea1399 [CL 21960082 by bryan sefcik in ue5-main branch]
578 lines
18 KiB
C++
578 lines
18 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AudioFormatOpus.h"
|
|
#include "Serialization/MemoryWriter.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Interfaces/IAudioFormat.h"
|
|
#include "Interfaces/IAudioFormatModule.h"
|
|
#include "OpusAudioInfo.h"
|
|
#include "VorbisAudioInfo.h"
|
|
|
|
// Need to define this so that resampler.h compiles - probably a way around this somehow
|
|
#define OUTSIDE_SPEEX
|
|
|
|
THIRD_PARTY_INCLUDES_START
|
|
#include "ThirdParty/libOpus/opus-1.3.1-12/include/opus_multistream.h"
|
|
#include "ThirdParty/libOpus/opus-1.3.1-12/include/speex_resampler.h"
|
|
THIRD_PARTY_INCLUDES_END
|
|
|
|
/** Use UE memory allocation or Opus */
|
|
#define USE_UE_MEM_ALLOC 1
|
|
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
|
|
|
|
static FName NAME_OPUS(TEXT("OPUS"));
|
|
|
|
/**
|
|
* IAudioFormat, audio compression abstraction
|
|
**/
|
|
class FAudioFormatOpus : public IAudioFormat
|
|
{
|
|
enum
|
|
{
|
|
/** Version for OPUS format, this becomes part of the DDC key. */
|
|
UE_AUDIO_OPUS_VER = 7,
|
|
};
|
|
|
|
public:
|
|
virtual bool AllowParallelBuild() const override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual uint16 GetVersion(FName Format) const override
|
|
{
|
|
check(Format == NAME_OPUS);
|
|
return UE_AUDIO_OPUS_VER;
|
|
}
|
|
|
|
|
|
virtual void GetSupportedFormats(TArray<FName>& OutFormats) const override
|
|
{
|
|
OutFormats.Add(NAME_OPUS);
|
|
}
|
|
|
|
virtual bool Cook(FName Format, const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo, TArray<uint8>& CompressedDataStore) const override
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FAudioFormatOpus::Cook);
|
|
check(Format == NAME_OPUS);
|
|
|
|
// Get best compatible sample rate
|
|
const uint16 kOpusSampleRate = GetBestOutputSampleRate(QualityInfo.SampleRate);
|
|
// Frame size must be one of 2.5, 5, 10, 20, 40 or 60 ms
|
|
const int32 kOpusFrameSizeMs = 60;
|
|
// Calculate frame size required by Opus
|
|
const int32 kOpusFrameSizeSamples = (kOpusSampleRate * kOpusFrameSizeMs) / 1000;
|
|
const uint32 kSampleStride = SAMPLE_SIZE * QualityInfo.NumChannels;
|
|
const int32 kBytesPerFrame = kOpusFrameSizeSamples * kSampleStride;
|
|
|
|
// Check whether source has compatible sample rate
|
|
TArray<uint8> SrcBufferCopy;
|
|
if (QualityInfo.SampleRate != kOpusSampleRate)
|
|
{
|
|
if (!ResamplePCM(QualityInfo.NumChannels, SrcBuffer, QualityInfo.SampleRate, SrcBufferCopy, kOpusSampleRate))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Take a copy of the source regardless
|
|
SrcBufferCopy = SrcBuffer;
|
|
}
|
|
|
|
// Initialise the Opus encoder
|
|
OpusEncoder* Encoder = NULL;
|
|
int32 EncError = 0;
|
|
#if USE_UE_MEM_ALLOC
|
|
int32 EncSize = opus_encoder_get_size(QualityInfo.NumChannels);
|
|
Encoder = (OpusEncoder*)FMemory::Malloc(EncSize);
|
|
EncError = opus_encoder_init(Encoder, kOpusSampleRate, QualityInfo.NumChannels, OPUS_APPLICATION_AUDIO);
|
|
#else
|
|
Encoder = opus_encoder_create(kOpusSampleRate, QualityInfo.NumChannels, OPUS_APPLICATION_AUDIO, &EncError);
|
|
#endif
|
|
if (EncError != OPUS_OK)
|
|
{
|
|
Destroy(Encoder);
|
|
return false;
|
|
}
|
|
|
|
int32 BitRate = GetBitRateFromQuality(QualityInfo);
|
|
opus_encoder_ctl(Encoder, OPUS_SET_BITRATE(BitRate));
|
|
|
|
// Create a buffer to store compressed data
|
|
CompressedDataStore.Empty();
|
|
FMemoryWriter CompressedData(CompressedDataStore);
|
|
int32 SrcBufferOffset = 0;
|
|
|
|
// Calc frame and sample count
|
|
int32 FramesToEncode = SrcBufferCopy.Num() / kBytesPerFrame;
|
|
uint32 TrueSampleCount = SrcBufferCopy.Num() / kSampleStride;
|
|
|
|
// Pad the end of data with zeroes if it isn't exactly the size of a frame.
|
|
if (SrcBufferCopy.Num() % kBytesPerFrame != 0)
|
|
{
|
|
int32 FrameDiff = kBytesPerFrame - (SrcBufferCopy.Num() % kBytesPerFrame);
|
|
SrcBufferCopy.AddZeroed(FrameDiff);
|
|
FramesToEncode++;
|
|
}
|
|
|
|
check(QualityInfo.NumChannels <= MAX_uint8);
|
|
check(FramesToEncode <= MAX_uint16);
|
|
SerializeHeaderData(CompressedData, kOpusSampleRate, TrueSampleCount, QualityInfo.NumChannels, FramesToEncode);
|
|
|
|
// Temporary storage with more than enough to store any compressed frame
|
|
TArray<uint8> TempCompressedData;
|
|
TempCompressedData.AddUninitialized(kBytesPerFrame);
|
|
|
|
while (SrcBufferOffset < SrcBufferCopy.Num())
|
|
{
|
|
int32 CompressedLength = opus_encode(Encoder, (const opus_int16*)(SrcBufferCopy.GetData() + SrcBufferOffset), kOpusFrameSizeSamples, TempCompressedData.GetData(), TempCompressedData.Num());
|
|
|
|
if (CompressedLength < 0)
|
|
{
|
|
const char* ErrorStr = opus_strerror(CompressedLength);
|
|
UE_LOG(LogAudio, Warning, TEXT("Failed to encode: [%d] %s"), CompressedLength, ANSI_TO_TCHAR(ErrorStr));
|
|
|
|
Destroy(Encoder);
|
|
|
|
CompressedDataStore.Empty();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Store frame length and copy compressed data before incrementing pointers
|
|
check(CompressedLength < MAX_uint16);
|
|
SerialiseFrameData(CompressedData, TempCompressedData.GetData(), CompressedLength);
|
|
SrcBufferOffset += kBytesPerFrame;
|
|
}
|
|
}
|
|
|
|
Destroy(Encoder);
|
|
|
|
return CompressedDataStore.Num() > 0;
|
|
}
|
|
|
|
virtual bool CookSurround(FName Format, const TArray<TArray<uint8> >& SrcBuffers, FSoundQualityInfo& QualityInfo, TArray<uint8>& CompressedDataStore) const override
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FAudioFormatOpus::CookSurround);
|
|
check(Format == NAME_OPUS);
|
|
|
|
// Get best compatible sample rate
|
|
const uint16 kOpusSampleRate = GetBestOutputSampleRate(QualityInfo.SampleRate);
|
|
// Frame size must be one of 2.5, 5, 10, 20, 40 or 60 ms
|
|
const int32 kOpusFrameSizeMs = 60;
|
|
// Calculate frame size required by Opus
|
|
const int32 kOpusFrameSizeSamples = (kOpusSampleRate * kOpusFrameSizeMs) / 1000;
|
|
const uint32 kSampleStride = SAMPLE_SIZE * QualityInfo.NumChannels;
|
|
const int32 kBytesPerFrame = kOpusFrameSizeSamples * kSampleStride;
|
|
|
|
// Check whether source has compatible sample rate
|
|
TArray<TArray<uint8>> SrcBufferCopies;
|
|
if (QualityInfo.SampleRate != kOpusSampleRate)
|
|
{
|
|
for (int32 Index = 0; Index < SrcBuffers.Num(); Index++)
|
|
{
|
|
TArray<uint8>& NewCopy = *new (SrcBufferCopies) TArray<uint8>;
|
|
if (!ResamplePCM(1, SrcBuffers[Index], QualityInfo.SampleRate, NewCopy, kOpusSampleRate))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SrcBufferCopies.Reset();
|
|
SrcBufferCopies.AddDefaulted(SrcBuffers.Num());
|
|
|
|
// Take a copy of the source regardless
|
|
for (int32 Index = 0; Index < SrcBuffers.Num(); Index++)
|
|
{
|
|
SrcBufferCopies[Index] = SrcBuffers[Index];
|
|
}
|
|
}
|
|
|
|
// Ensure that all channels are the same length
|
|
int32 SourceSize = -1;
|
|
for (int32 Index = 0; Index < SrcBufferCopies.Num(); Index++)
|
|
{
|
|
if (!Index)
|
|
{
|
|
SourceSize = SrcBufferCopies[Index].Num();
|
|
}
|
|
else
|
|
{
|
|
if (SourceSize != SrcBufferCopies[Index].Num())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (SourceSize <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Initialise the Opus multistream encoder
|
|
OpusMSEncoder* Encoder = NULL;
|
|
int32 EncError = 0;
|
|
int32 streams = 0;
|
|
int32 coupled_streams = 0;
|
|
// mapping_family not documented but figured out: 0 = 1 or 2 channels, 1 = 1 to 8 channel surround sound, 255 = up to 255 channels with no surround processing
|
|
int32 mapping_family = 1;
|
|
TArray<uint8> mapping;
|
|
mapping.AddUninitialized(QualityInfo.NumChannels);
|
|
#if USE_UE_MEM_ALLOC
|
|
int32 EncSize = opus_multistream_surround_encoder_get_size(QualityInfo.NumChannels, mapping_family);
|
|
Encoder = (OpusMSEncoder*)FMemory::Malloc(EncSize);
|
|
EncError = opus_multistream_surround_encoder_init(Encoder, kOpusSampleRate, QualityInfo.NumChannels, mapping_family, &streams, &coupled_streams, mapping.GetData(), OPUS_APPLICATION_AUDIO);
|
|
#else
|
|
Encoder = opus_multistream_surround_encoder_create(kOpusSampleRate, QualityInfo.NumChannels, mapping_family, &streams, &coupled_streams, mapping.GetData(), OPUS_APPLICATION_AUDIO, &EncError);
|
|
#endif
|
|
if (EncError != OPUS_OK)
|
|
{
|
|
Destroy(Encoder);
|
|
return false;
|
|
}
|
|
|
|
int32 BitRate = GetBitRateFromQuality(QualityInfo);
|
|
opus_multistream_encoder_ctl(Encoder, OPUS_SET_BITRATE(BitRate));
|
|
|
|
// Create a buffer to store compressed data
|
|
CompressedDataStore.Empty();
|
|
FMemoryWriter CompressedData(CompressedDataStore);
|
|
int32 SrcBufferOffset = 0;
|
|
|
|
// Calc frame and sample count
|
|
int32 FramesToEncode = SourceSize / (kOpusFrameSizeSamples * SAMPLE_SIZE);
|
|
uint32 TrueSampleCount = SourceSize / SAMPLE_SIZE;
|
|
|
|
// Add another frame if Source does not divide into an equal number of frames
|
|
if (SourceSize % (kOpusFrameSizeSamples * SAMPLE_SIZE) != 0)
|
|
{
|
|
FramesToEncode++;
|
|
}
|
|
|
|
check(QualityInfo.NumChannels <= MAX_uint8);
|
|
check(FramesToEncode <= MAX_uint16);
|
|
SerializeHeaderData(CompressedData, kOpusSampleRate, TrueSampleCount, QualityInfo.NumChannels, FramesToEncode);
|
|
|
|
// Temporary storage for source data in an interleaved format
|
|
TArray<uint8> TempInterleavedSrc;
|
|
TempInterleavedSrc.AddUninitialized(kBytesPerFrame);
|
|
|
|
// Temporary storage with more than enough to store any compressed frame
|
|
TArray<uint8> TempCompressedData;
|
|
TempCompressedData.AddUninitialized(kBytesPerFrame);
|
|
|
|
while (SrcBufferOffset < SourceSize)
|
|
{
|
|
// Read a frames worth of data from the source and pack it into interleaved temporary storage
|
|
for (int32 SampleIndex = 0; SampleIndex < kOpusFrameSizeSamples; ++SampleIndex)
|
|
{
|
|
int32 CurrSrcOffset = SrcBufferOffset + SampleIndex*SAMPLE_SIZE;
|
|
int32 CurrInterleavedOffset = SampleIndex*kSampleStride;
|
|
if (CurrSrcOffset < SourceSize)
|
|
{
|
|
check(QualityInfo.NumChannels <= 8); // Static analysis fix: warning C6385: Reading invalid data from 'Order': the readable size is '256' bytes, but '8160' bytes may be read.
|
|
for (uint32 ChannelIndex = 0; ChannelIndex < QualityInfo.NumChannels; ++ChannelIndex)
|
|
{
|
|
// Interleave the channels in the Vorbis format, so that the correct channel is used for LFE
|
|
int32 OrderedChannelIndex = VorbisChannelInfo::Order[QualityInfo.NumChannels - 1][ChannelIndex];
|
|
int32 CurrInterleavedIndex = CurrInterleavedOffset + ChannelIndex*SAMPLE_SIZE;
|
|
|
|
// Copy both bytes that make up a single sample
|
|
TempInterleavedSrc[CurrInterleavedIndex] = SrcBufferCopies[OrderedChannelIndex][CurrSrcOffset];
|
|
TempInterleavedSrc[CurrInterleavedIndex + 1] = SrcBufferCopies[OrderedChannelIndex][CurrSrcOffset + 1];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Zero the rest of the temp buffer to make it an exact frame
|
|
FMemory::Memzero(TempInterleavedSrc.GetData() + CurrInterleavedOffset, kBytesPerFrame - CurrInterleavedOffset);
|
|
SampleIndex = kOpusFrameSizeSamples;
|
|
}
|
|
}
|
|
|
|
int32 CompressedLength = opus_multistream_encode(Encoder, (const opus_int16*)(TempInterleavedSrc.GetData()), kOpusFrameSizeSamples, TempCompressedData.GetData(), TempCompressedData.Num());
|
|
|
|
if (CompressedLength < 0)
|
|
{
|
|
const char* ErrorStr = opus_strerror(CompressedLength);
|
|
UE_LOG(LogAudio, Warning, TEXT("Failed to encode: [%d] %s"), CompressedLength, ANSI_TO_TCHAR(ErrorStr));
|
|
|
|
Destroy(Encoder);
|
|
|
|
CompressedDataStore.Empty();
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Store frame length and copy compressed data before incrementing pointers
|
|
check(CompressedLength < MAX_uint16);
|
|
SerialiseFrameData(CompressedData, TempCompressedData.GetData(), CompressedLength);
|
|
SrcBufferOffset += kOpusFrameSizeSamples * SAMPLE_SIZE;
|
|
}
|
|
}
|
|
|
|
Destroy(Encoder);
|
|
|
|
return CompressedDataStore.Num() > 0;
|
|
}
|
|
|
|
virtual int32 Recompress(FName Format, const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo, TArray<uint8>& OutBuffer) const override
|
|
{
|
|
check(Format == NAME_OPUS);
|
|
FOpusAudioInfo AudioInfo;
|
|
|
|
// Cannot quality preview multichannel sounds
|
|
if( QualityInfo.NumChannels > 2 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
TArray<uint8> CompressedDataStore;
|
|
if( !Cook( Format, SrcBuffer, QualityInfo, CompressedDataStore ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Parse the opus header for the relevant information
|
|
if( !AudioInfo.ReadCompressedInfo( CompressedDataStore.GetData(), CompressedDataStore.Num(), &QualityInfo ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Decompress all the sample data
|
|
OutBuffer.Empty(QualityInfo.SampleDataSize);
|
|
OutBuffer.AddZeroed(QualityInfo.SampleDataSize);
|
|
AudioInfo.ExpandFile( OutBuffer.GetData(), &QualityInfo );
|
|
|
|
return CompressedDataStore.Num();
|
|
}
|
|
|
|
virtual int32 GetMinimumSizeForInitialChunk(FName Format, const TArray<uint8>& SrcBuffer) const override
|
|
{
|
|
// Since UE uses it's own version of the header, we hardcode the size of that here. See SplitDataForStreaming below to see our initial info.
|
|
return FCStringAnsi::Strlen(OPUS_ID_STRING) + 1 // Format identifier
|
|
+ sizeof(uint16) // Sample Rate
|
|
+ sizeof(uint32) // True Sample Count
|
|
+ sizeof(uint8) // Number of Channels
|
|
+ sizeof(uint16); // Serialized Frames
|
|
}
|
|
|
|
virtual bool SplitDataForStreaming(const TArray<uint8>& SrcBuffer, TArray<TArray<uint8>>& OutBuffers, const int32 MaxInitialChunkSize, const int32 MaxChunkSize) const override
|
|
{
|
|
if (SrcBuffer.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32 ReadOffset = 0;
|
|
uint32 WriteOffset = 0;
|
|
uint16 ProcessedFrames = 0;
|
|
const uint8* LockedSrc = SrcBuffer.GetData();
|
|
|
|
// Read Identifier, True Sample Count, Number of channels and Frames to Encode first
|
|
if (FCStringAnsi::Strcmp((char*)LockedSrc, OPUS_ID_STRING) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
ReadOffset += FCStringAnsi::Strlen(OPUS_ID_STRING) + 1;
|
|
uint16 SampleRate = *((uint16*)(LockedSrc + ReadOffset));
|
|
ReadOffset += sizeof(uint16);
|
|
uint32 TrueSampleCount = *((uint32*)(LockedSrc + ReadOffset));
|
|
ReadOffset += sizeof(uint32);
|
|
uint8 NumChannels = *(LockedSrc + ReadOffset);
|
|
ReadOffset += sizeof(uint8);
|
|
uint16 SerializedFrames = *((uint16*)(LockedSrc + ReadOffset));
|
|
ReadOffset += sizeof(uint16);
|
|
|
|
// Should always be able to store basic info in a single chunk
|
|
check(ReadOffset - WriteOffset <= (uint32)MaxInitialChunkSize);
|
|
|
|
int32 ChunkSize = MaxInitialChunkSize;
|
|
|
|
while (ProcessedFrames < SerializedFrames)
|
|
{
|
|
uint16 FrameSize = *((uint16*)(LockedSrc + ReadOffset));
|
|
|
|
if ( (ReadOffset + sizeof(uint16) + FrameSize) - WriteOffset >= ChunkSize)
|
|
{
|
|
WriteOffset += AddDataChunk(OutBuffers, LockedSrc + WriteOffset, ReadOffset - WriteOffset);
|
|
}
|
|
|
|
ReadOffset += sizeof(uint16) + FrameSize;
|
|
ProcessedFrames++;
|
|
|
|
ChunkSize = MaxChunkSize;
|
|
}
|
|
if (WriteOffset < ReadOffset)
|
|
{
|
|
WriteOffset += AddDataChunk(OutBuffers, LockedSrc + WriteOffset, ReadOffset - WriteOffset);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Calculate the best sample rate for the output opus data
|
|
*/
|
|
static uint16 GetBestOutputSampleRate(int32 SampleRate)
|
|
{
|
|
static const uint16 ValidSampleRates[] =
|
|
{
|
|
0, // not really valid, but simplifies logic below
|
|
8000,
|
|
12000,
|
|
16000,
|
|
24000,
|
|
48000,
|
|
};
|
|
|
|
// look for the next highest valid rate
|
|
for (int32 Index = UE_ARRAY_COUNT(ValidSampleRates) - 2; Index >= 0; Index--)
|
|
{
|
|
if (SampleRate > ValidSampleRates[Index])
|
|
{
|
|
return ValidSampleRates[Index + 1];
|
|
}
|
|
}
|
|
// this should never get here!
|
|
check(0);
|
|
return 0;
|
|
}
|
|
|
|
bool ResamplePCM(uint32 NumChannels, const TArray<uint8>& InBuffer, uint32 InSampleRate, TArray<uint8>& OutBuffer, uint32 OutSampleRate) const
|
|
{
|
|
// Initialize resampler to convert to desired rate for Opus
|
|
int32 err = 0;
|
|
SpeexResamplerState* resampler = speex_resampler_init(NumChannels, InSampleRate, OutSampleRate, SPEEX_RESAMPLER_QUALITY_DESKTOP, &err);
|
|
if (err != RESAMPLER_ERR_SUCCESS)
|
|
{
|
|
speex_resampler_destroy(resampler);
|
|
return false;
|
|
}
|
|
|
|
// Calculate extra space required for sample rate
|
|
const uint32 SampleStride = SAMPLE_SIZE * NumChannels;
|
|
const float Duration = (float)InBuffer.Num() / (InSampleRate * SampleStride);
|
|
const int32 SafeCopySize = (Duration + 1) * OutSampleRate * SampleStride;
|
|
OutBuffer.Empty(SafeCopySize);
|
|
OutBuffer.AddUninitialized(SafeCopySize);
|
|
uint32 InSamples = InBuffer.Num() / SampleStride;
|
|
uint32 OutSamples = OutBuffer.Num() / SampleStride;
|
|
|
|
// Do resampling and check results
|
|
if (NumChannels == 1)
|
|
{
|
|
err = speex_resampler_process_int(resampler, 0, (const short*)(InBuffer.GetData()), &InSamples, (short*)(OutBuffer.GetData()), &OutSamples);
|
|
}
|
|
else
|
|
{
|
|
err = speex_resampler_process_interleaved_int(resampler, (const short*)(InBuffer.GetData()), &InSamples, (short*)(OutBuffer.GetData()), &OutSamples);
|
|
}
|
|
|
|
speex_resampler_destroy(resampler);
|
|
if (err != RESAMPLER_ERR_SUCCESS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// reduce the size of Out Buffer if more space than necessary was allocated
|
|
const int32 WrittenBytes = (int32)(OutSamples * SampleStride);
|
|
if (WrittenBytes < OutBuffer.Num())
|
|
{
|
|
OutBuffer.SetNum(WrittenBytes, true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int32 GetBitRateFromQuality(FSoundQualityInfo& QualityInfo) const
|
|
{
|
|
// There is no perfect way to map Vorbis' Quality setting to an Opus bitrate but this
|
|
// will use it as a multiplier to decide how much smaller than the original the
|
|
// compressed data should be
|
|
int32 OriginalBitRate = QualityInfo.SampleRate * QualityInfo.NumChannels * SAMPLE_SIZE * 8;
|
|
return (float)OriginalBitRate * FMath::GetMappedRangeValueClamped(FVector2f(1, 100), FVector2f(0.04, 0.25), (float)QualityInfo.Quality);
|
|
}
|
|
|
|
void SerializeHeaderData(FMemoryWriter& CompressedData, uint16 SampleRate, uint32 TrueSampleCount, uint8 NumChannels, uint16 NumFrames) const
|
|
{
|
|
const char* OpusIdentifier = OPUS_ID_STRING;
|
|
CompressedData.Serialize((void*)OpusIdentifier, FCStringAnsi::Strlen(OpusIdentifier) + 1);
|
|
CompressedData.Serialize(&SampleRate, sizeof(uint16));
|
|
CompressedData.Serialize(&TrueSampleCount, sizeof(uint32));
|
|
CompressedData.Serialize(&NumChannels, sizeof(uint8));
|
|
CompressedData.Serialize(&NumFrames, sizeof(uint16));
|
|
}
|
|
|
|
void SerialiseFrameData(FMemoryWriter& CompressedData, uint8* FrameData, uint16 FrameSize) const
|
|
{
|
|
CompressedData.Serialize(&FrameSize, sizeof(uint16));
|
|
CompressedData.Serialize(FrameData, FrameSize);
|
|
}
|
|
|
|
void Destroy(OpusEncoder* Encoder) const
|
|
{
|
|
#if USE_UE_MEM_ALLOC
|
|
FMemory::Free(Encoder);
|
|
#else
|
|
opus_encoder_destroy(Encoder);
|
|
#endif
|
|
}
|
|
|
|
void Destroy(OpusMSEncoder* Encoder) const
|
|
{
|
|
#if USE_UE_MEM_ALLOC
|
|
FMemory::Free(Encoder);
|
|
#else
|
|
opus_multistream_encoder_destroy(Encoder);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Adds a new chunk of data to the array
|
|
*
|
|
* @param OutBuffers Array of buffers to add to
|
|
* @param ChunkData Pointer to chunk data
|
|
* @param ChunkSize How much data to write
|
|
* @return How many bytes were written
|
|
*/
|
|
int32 AddDataChunk(TArray<TArray<uint8>>& OutBuffers, const uint8* ChunkData, int32 ChunkSize) const
|
|
{
|
|
TArray<uint8>& NewBuffer = *new (OutBuffers) TArray<uint8>;
|
|
NewBuffer.Empty(ChunkSize);
|
|
NewBuffer.AddUninitialized(ChunkSize);
|
|
FMemory::Memcpy(NewBuffer.GetData(), ChunkData, ChunkSize);
|
|
return ChunkSize;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Module for opus audio compression
|
|
*/
|
|
|
|
static IAudioFormat* Singleton = NULL;
|
|
|
|
class FAudioPlatformOpusModule : public IAudioFormatModule
|
|
{
|
|
public:
|
|
virtual ~FAudioPlatformOpusModule()
|
|
{
|
|
delete Singleton;
|
|
Singleton = NULL;
|
|
}
|
|
virtual IAudioFormat* GetAudioFormat()
|
|
{
|
|
if (!Singleton)
|
|
{
|
|
Singleton = new FAudioFormatOpus();
|
|
}
|
|
return Singleton;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_MODULE( FAudioPlatformOpusModule, AudioFormatOpus);
|