You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Implemented CookSurround function for AudioFormatOpus, refactoring as much common code from the standard Cook into re-usable functions. Changed OpusAudioInfo class to use a multistream decoder for all sounds, as 1 and 2 channel sounds can still be decoded by it. Just had to create an array of mappings that would take the internal Opus format and give back PCM data in the order that Unreal expects. Also made the OpusDecoderWrapper a true wrapper so that you don't have to use any opus functions outside of it and it will not function if WITH_OPUS is not defined. Added a virtual function to the CompressedAudioInfo interface, to check whether its uncompressed data will be in the Vorbis format. This is to make sure that code currently just checking whether there is a DecompressionState can now be sure that the Vorbis format is being used before routing sound to different speakers. Made sure that you can't cause any problems by adding or removing a SoundWave from the Audio Streaming Manager more than once. Also rearranged where sounds are added to the manager so that it is only done in one place and it's safe when changing quality triggers recompression/splitting. Added a global multidimensional array to store all the Vorbis channel orderings as they needed to be used in multiple places. Added Logging and Memory tracking for the streaming process. [CL 2217839 by Matthew Griffin in Main branch]
465 lines
13 KiB
C++
465 lines
13 KiB
C++
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Core.h"
|
|
#include "ModuleInterface.h"
|
|
#include "ModuleManager.h"
|
|
#include "TargetPlatform.h"
|
|
#include "VorbisAudioInfo.h"
|
|
|
|
|
|
#if WITH_OGGVORBIS
|
|
#pragma pack(push, 8)
|
|
#include "vorbis/vorbisenc.h"
|
|
#include "vorbis/vorbisfile.h"
|
|
#pragma pack(pop)
|
|
#endif
|
|
|
|
static_assert(WITH_OGGVORBIS, "No point in compiling the OGG compressor if we don't have Vorbis.");
|
|
|
|
// Vorbis encoded sound is about 15% better quality than XMA - adjust the quality setting to get consistent cross platform sound quality
|
|
#define VORBIS_QUALITY_MODIFIER -15
|
|
|
|
#define SAMPLES_TO_READ 1024
|
|
#define SAMPLE_SIZE ( ( uint32 )sizeof( short ) )
|
|
|
|
static FName NAME_OGG(TEXT("OGG"));
|
|
/**
|
|
* IAudioFormat, audio compression abstraction
|
|
**/
|
|
class FAudioFormatOgg : public IAudioFormat
|
|
{
|
|
enum
|
|
{
|
|
/** Version for OGG format, this becomes part of the DDC key. */
|
|
UE_AUDIO_OGG_VER = 1,
|
|
};
|
|
|
|
public:
|
|
virtual bool AllowParallelBuild() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual uint16 GetVersion(FName Format) const override
|
|
{
|
|
check(Format == NAME_OGG);
|
|
return UE_AUDIO_OGG_VER;
|
|
}
|
|
|
|
|
|
virtual void GetSupportedFormats(TArray<FName>& OutFormats) const
|
|
{
|
|
OutFormats.Add(NAME_OGG);
|
|
}
|
|
|
|
virtual bool Cook(FName Format, const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo, TArray<uint8>& CompressedDataStore) const
|
|
{
|
|
check(Format == NAME_OGG);
|
|
#if WITH_OGGVORBIS
|
|
{
|
|
|
|
short ReadBuffer[SAMPLES_TO_READ * SAMPLE_SIZE * 2];
|
|
|
|
ogg_stream_state os; // take physical pages, weld into a logical stream of packets
|
|
ogg_page og; // one ogg bitstream page. Vorbis packets are inside
|
|
ogg_packet op; // one raw packet of data for decode
|
|
vorbis_info vi; // struct that stores all the static vorbis bitstream settings
|
|
vorbis_comment vc; // struct that stores all the user comments
|
|
vorbis_dsp_state vd; // central working state for the packet->PCM decoder
|
|
vorbis_block vb; // local working space for packet->PCM decode
|
|
uint32 i;
|
|
bool eos;
|
|
|
|
// Create a buffer to store compressed data
|
|
CompressedDataStore.Empty();
|
|
FMemoryWriter CompressedData( CompressedDataStore );
|
|
uint32 BufferOffset = 0;
|
|
|
|
float CompressionQuality = ( float )( QualityInfo.Quality + VORBIS_QUALITY_MODIFIER ) / 100.0f;
|
|
CompressionQuality = FMath::Clamp( CompressionQuality, -0.1f, 1.0f );
|
|
|
|
vorbis_info_init( &vi );
|
|
|
|
if( vorbis_encode_init_vbr( &vi, QualityInfo.NumChannels, QualityInfo.SampleRate, CompressionQuality ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// add a comment
|
|
vorbis_comment_init( &vc );
|
|
vorbis_comment_add_tag( &vc, "ENCODER", "UnrealEngine4" );
|
|
|
|
// set up the analysis state and auxiliary encoding storage
|
|
vorbis_analysis_init( &vd, &vi );
|
|
vorbis_block_init( &vd, &vb );
|
|
|
|
// set up our packet->stream encoder
|
|
ogg_stream_init( &os, 0 );
|
|
|
|
ogg_packet header;
|
|
ogg_packet header_comm;
|
|
ogg_packet header_code;
|
|
|
|
vorbis_analysis_headerout( &vd, &vc, &header, &header_comm, &header_code);
|
|
ogg_stream_packetin( &os, &header );
|
|
ogg_stream_packetin( &os, &header_comm );
|
|
ogg_stream_packetin( &os, &header_code );
|
|
|
|
// This ensures the actual audio data will start on a new page, as per spec
|
|
while( true )
|
|
{
|
|
int result = ogg_stream_flush( &os, &og );
|
|
if( result == 0 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
CompressedData.Serialize( og.header, og.header_len );
|
|
CompressedData.Serialize( og.body, og.body_len );
|
|
}
|
|
|
|
eos = false;
|
|
while( !eos )
|
|
{
|
|
// Read samples
|
|
uint32 BytesToRead = FMath::Min( SAMPLES_TO_READ * QualityInfo.NumChannels * SAMPLE_SIZE, QualityInfo.SampleDataSize - BufferOffset );
|
|
FMemory::Memcpy( ReadBuffer, SrcBuffer.GetTypedData() + BufferOffset, BytesToRead );
|
|
BufferOffset += BytesToRead;
|
|
|
|
if( BytesToRead == 0)
|
|
{
|
|
// end of file
|
|
vorbis_analysis_wrote( &vd, 0 );
|
|
}
|
|
else
|
|
{
|
|
// expose the buffer to submit data
|
|
float **buffer = vorbis_analysis_buffer( &vd, SAMPLES_TO_READ );
|
|
|
|
if( QualityInfo.NumChannels == 1 )
|
|
{
|
|
for( i = 0; i < BytesToRead / SAMPLE_SIZE; i++ )
|
|
{
|
|
buffer[0][i] = ( ReadBuffer[i] ) / 32768.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; i < BytesToRead / ( SAMPLE_SIZE * 2 ); i++ )
|
|
{
|
|
buffer[0][i] = ( ReadBuffer[i * 2] ) / 32768.0f;
|
|
buffer[1][i] = ( ReadBuffer[i * 2 + 1] ) / 32768.0f;
|
|
}
|
|
}
|
|
|
|
// tell the library how many samples we actually submitted
|
|
vorbis_analysis_wrote( &vd, i );
|
|
}
|
|
|
|
// vorbis does some data preanalysis, then divvies up blocks for more involved (potentially parallel) processing.
|
|
while( vorbis_analysis_blockout( &vd, &vb ) == 1 )
|
|
{
|
|
// analysis, assume we want to use bitrate management
|
|
vorbis_analysis( &vb, NULL );
|
|
vorbis_bitrate_addblock( &vb );
|
|
|
|
while( vorbis_bitrate_flushpacket( &vd, &op ) )
|
|
{
|
|
// weld the packet into the bitstream
|
|
ogg_stream_packetin( &os, &op );
|
|
|
|
// write out pages (if any)
|
|
while( !eos )
|
|
{
|
|
int result = ogg_stream_pageout( &os, &og );
|
|
if( result == 0 )
|
|
{
|
|
break;
|
|
}
|
|
CompressedData.Serialize( og.header, og.header_len );
|
|
CompressedData.Serialize( og.body, og.body_len );
|
|
|
|
// this could be set above, but for illustrative purposes, I do it here (to show that vorbis does know where the stream ends)
|
|
if( ogg_page_eos( &og ) )
|
|
{
|
|
eos = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// clean up and exit. vorbis_info_clear() must be called last
|
|
ogg_stream_clear( &os );
|
|
vorbis_block_clear( &vb );
|
|
vorbis_dsp_clear( &vd );
|
|
vorbis_comment_clear( &vc );
|
|
vorbis_info_clear( &vi );
|
|
// ogg_page and ogg_packet structs always point to storage in libvorbis. They're never freed or manipulated directly
|
|
}
|
|
return CompressedDataStore.Num() > 0;
|
|
#else
|
|
return false;
|
|
#endif // WITH_OGGVOBVIS
|
|
}
|
|
|
|
virtual bool CookSurround(FName Format, const TArray<TArray<uint8> >& SrcBuffers, FSoundQualityInfo& QualityInfo, TArray<uint8>& CompressedDataStore) const
|
|
{
|
|
check(Format == NAME_OGG);
|
|
#if WITH_OGGVORBIS
|
|
{
|
|
|
|
ogg_stream_state os; // take physical pages, weld into a logical stream of packets
|
|
ogg_page og; // one ogg bitstream page. Vorbis packets are inside
|
|
ogg_packet op; // one raw packet of data for decode
|
|
vorbis_info vi; // struct that stores all the static vorbis bitstream settings
|
|
vorbis_comment vc; // struct that stores all the user comments
|
|
vorbis_dsp_state vd; // central working state for the packet->PCM decoder
|
|
vorbis_block vb; // local working space for packet->PCM decode
|
|
bool eos;
|
|
int j;
|
|
|
|
// Create a buffer to store compressed data
|
|
CompressedDataStore.Empty();
|
|
FMemoryWriter CompressedData( CompressedDataStore );
|
|
uint32 BufferOffset = 0;
|
|
|
|
int32 Size = -1;
|
|
for (int32 Index = 0; Index < SrcBuffers.Num(); Index++)
|
|
{
|
|
if (!Index)
|
|
{
|
|
Size = SrcBuffers[Index].Num();
|
|
}
|
|
else
|
|
{
|
|
if (Size != SrcBuffers[Index].Num())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (Size <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Extract the relevant info for compression
|
|
float CompressionQuality = ( float )( QualityInfo.Quality + VORBIS_QUALITY_MODIFIER ) / 100.0f;
|
|
CompressionQuality = FMath::Clamp( CompressionQuality, 0.0f, 1.0f );
|
|
|
|
vorbis_info_init( &vi );
|
|
|
|
if( vorbis_encode_init_vbr( &vi, SrcBuffers.Num(), QualityInfo.SampleRate, CompressionQuality ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// add a comment
|
|
vorbis_comment_init( &vc );
|
|
vorbis_comment_add_tag( &vc, "ENCODER", "UnrealEngine4" );
|
|
|
|
// set up the analysis state and auxiliary encoding storage
|
|
vorbis_analysis_init( &vd, &vi );
|
|
vorbis_block_init( &vd, &vb );
|
|
|
|
// set up our packet->stream encoder
|
|
ogg_stream_init( &os, 0 );
|
|
|
|
ogg_packet header;
|
|
ogg_packet header_comm;
|
|
ogg_packet header_code;
|
|
|
|
vorbis_analysis_headerout( &vd, &vc, &header, &header_comm, &header_code);
|
|
ogg_stream_packetin( &os, &header );
|
|
ogg_stream_packetin( &os, &header_comm );
|
|
ogg_stream_packetin( &os, &header_code );
|
|
|
|
// This ensures the actual audio data will start on a new page, as per spec
|
|
while( true )
|
|
{
|
|
int result = ogg_stream_flush( &os, &og );
|
|
if( result == 0 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
CompressedData.Serialize( og.header, og.header_len );
|
|
CompressedData.Serialize( og.body, og.body_len );
|
|
}
|
|
|
|
TArray<int32> ChannelOrder = GetChannelOrder(SrcBuffers.Num());
|
|
|
|
eos = false;
|
|
while( !eos )
|
|
{
|
|
// Read samples
|
|
uint32 BytesToRead = FMath::Min( SAMPLES_TO_READ * SAMPLE_SIZE, Size - BufferOffset );
|
|
if( BytesToRead == 0)
|
|
{
|
|
// end of file
|
|
vorbis_analysis_wrote( &vd, 0 );
|
|
}
|
|
else
|
|
{
|
|
// expose the buffer to submit data
|
|
float **buffer = vorbis_analysis_buffer( &vd, SAMPLES_TO_READ );
|
|
|
|
uint32 i = 0;
|
|
for( j = 0; j < ChannelOrder.Num(); j++ )
|
|
{
|
|
short* ReadBuffer = ( short* )( SrcBuffers[ChannelOrder[j]].GetTypedData() + BufferOffset );
|
|
for( i = 0; i < BytesToRead / SAMPLE_SIZE; i++ )
|
|
{
|
|
buffer[j][i] = ( ReadBuffer[i] ) / 32768.0f;
|
|
}
|
|
}
|
|
|
|
// tell the library how many samples we actually submitted
|
|
vorbis_analysis_wrote( &vd, i );
|
|
}
|
|
BufferOffset += BytesToRead;
|
|
|
|
// vorbis does some data preanalysis, then divvies up blocks for more involved (potentially parallel) processing.
|
|
while( vorbis_analysis_blockout( &vd, &vb ) == 1 )
|
|
{
|
|
// analysis, assume we want to use bitrate management
|
|
vorbis_analysis( &vb, NULL );
|
|
vorbis_bitrate_addblock( &vb );
|
|
|
|
while( vorbis_bitrate_flushpacket( &vd, &op ) )
|
|
{
|
|
// weld the packet into the bitstream
|
|
ogg_stream_packetin( &os, &op );
|
|
|
|
// write out pages (if any)
|
|
while( !eos )
|
|
{
|
|
int result = ogg_stream_pageout( &os, &og );
|
|
if( result == 0 )
|
|
{
|
|
break;
|
|
}
|
|
CompressedData.Serialize( og.header, og.header_len );
|
|
CompressedData.Serialize( og.body, og.body_len );
|
|
|
|
// this could be set above, but for illustrative purposes, I do it here (to show that vorbis does know where the stream ends)
|
|
if( ogg_page_eos( &og ) )
|
|
{
|
|
eos = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// clean up and exit. vorbis_info_clear() must be called last
|
|
ogg_stream_clear( &os );
|
|
vorbis_block_clear( &vb );
|
|
vorbis_dsp_clear( &vd );
|
|
vorbis_comment_clear( &vc );
|
|
vorbis_info_clear( &vi );
|
|
|
|
// ogg_page and ogg_packet structs always point to storage in libvorbis. They're never freed or manipulated directly
|
|
}
|
|
return CompressedDataStore.Num() > 0;
|
|
#else
|
|
return false;
|
|
#endif // WITH_OGGVOBVIS
|
|
}
|
|
|
|
/**
|
|
* Put channels into the order expected for a multi-channel ogg vorbis file.
|
|
* Ordering taken from http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9
|
|
* Only concerned with 6 channel version because that seems to apply filtering to LFE.
|
|
*/
|
|
TArray<int32> GetChannelOrder(int32 NumChannels) const
|
|
{
|
|
TArray<int32> ChannelOrder;
|
|
|
|
switch(NumChannels)
|
|
{
|
|
case 6:
|
|
{
|
|
// the stream is 5.1 surround. channel order: front left, center, front right, rear left, rear right, LFE
|
|
for (int32 i = 0; i < NumChannels; i++)
|
|
{
|
|
ChannelOrder.Add(VorbisChannelInfo::Order[NumChannels - 1][i]);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
// no special ordering, just put all channels into ordered buffer
|
|
for( int32 i = 0; i < NumChannels; i++ )
|
|
{
|
|
ChannelOrder.Add(i);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ChannelOrder;
|
|
}
|
|
|
|
virtual int32 Recompress(FName Format, const TArray<uint8>& SrcBuffer, FSoundQualityInfo& QualityInfo, TArray<uint8>& OutBuffer) const
|
|
{
|
|
check(Format == NAME_OGG);
|
|
FVorbisAudioInfo AudioInfo;
|
|
int32 CompressedSize = -1;
|
|
|
|
// Cannot quality preview multichannel sounds
|
|
if( QualityInfo.NumChannels > 2 )
|
|
{
|
|
return 0;
|
|
}
|
|
TArray<uint8> CompressedDataStore;
|
|
if( !Cook( Format, SrcBuffer, QualityInfo, CompressedDataStore ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Parse the audio header for the relevant information
|
|
if (!AudioInfo.ReadCompressedInfo(CompressedDataStore.GetTypedData(), CompressedDataStore.Num(), &QualityInfo))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Decompress all the sample data
|
|
OutBuffer.Empty(QualityInfo.SampleDataSize);
|
|
OutBuffer.AddZeroed(QualityInfo.SampleDataSize);
|
|
AudioInfo.ExpandFile(OutBuffer.GetTypedData(), &QualityInfo);
|
|
|
|
return CompressedDataStore.Num();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Module for ogg audio compression
|
|
*/
|
|
|
|
static IAudioFormat* Singleton = NULL;
|
|
|
|
class FAudioPlatformOggModule : public IAudioFormatModule
|
|
{
|
|
public:
|
|
virtual ~FAudioPlatformOggModule()
|
|
{
|
|
delete Singleton;
|
|
Singleton = NULL;
|
|
}
|
|
virtual IAudioFormat* GetAudioFormat()
|
|
{
|
|
if (!Singleton)
|
|
{
|
|
LoadVorbisLibraries();
|
|
Singleton = new FAudioFormatOgg();
|
|
}
|
|
return Singleton;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_MODULE( FAudioPlatformOggModule, AudioFormatOgg);
|