Files
UnrealEngineUWP/Engine/Source/Runtime/Android/AndroidAudio/Private/AndroidAudioSource.cpp
Marc Audy c9cd5d81b9 Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 3090553)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3026802 on 2016/06/24 by Lina.Halper

	#Pose Asset work

	# additive blending change : additive scale is saved to [targetscale/sourcescale - 1] where it used to be [targetscale/sourcescale] since blending doesn't work with it
	 - Blending should work once we save to [targetscale/sourcescale - 1] as normal - i.e. if you blend 0.3, it should not shrink the mesh because you applyed additive to 0.3
	 - When apply the scale to base, it should multiply [additive scale + 1 ]  where additive scale is [targetscale/sourcescale - 1]
	 - Changed FTransform::Blend to FTransform::Lerp since it's literally just Lerp. Name Blend should be used for Accumulate but changing the name now is dangerous, so I'm keeping Accumulate but changed Blend to Lerp

	# pose asset preview fix
	 - made sure it adds to curve, so you don't have to use delegate to apply
	 - PreviewOverride is now added to output curve, so we don't have to apply that curve later
	 - only reason of anim instance delegate is now for normal anim blueprint.

	#pose asset change
	 - Curve extraction happens with ExtractContext, the output curve is stricly output curve
	 - Pose Asset supports Shrink now, but only shrink if full pose
	 - Added PoseHandler to handle most of common stuff between different pose nodes
	 - Still have to work on how to update pose asset - wip
	 - todo: clean up single node player to handle pose asset in a function

	#code review:Martin.Wilson, James.Golding

Change 3027519 on 2016/06/24 by Lina.Halper

	Reverted FTransform name change as that causes compile errors due to lack of deprecated messages

	- not worth to keep the old functions and add new one

	#code review: Martin.Wilson

Change 3060205 on 2016/07/21 by Zak.Middleton

	#ue4 - Don't strip map/package name from shipping on dedicated server in UGameInstance::StartGameInstance(), to allow specifying map name on servers via commandline. Don't ever parse "-replay" on dedicated servers.

	#jira UE-29424
	github #2273

Change 3061365 on 2016/07/22 by Jon.Nabozny

	Fix single frame character flip on death in ShooterGame.

	#jira UE-28053

Change 3061370 on 2016/07/22 by Jon.Nabozny

	Fix PhysX error where CCD is enabled on a Kinematic body.

	#jira UE-33463

Change 3061406 on 2016/07/22 by Lukasz.Furman

	deprecated blueprint interface for avoidance groups, added new set of functions working with FNavAvoidanceMask struct instead of packed flags
	#jira UE-32625

Change 3061847 on 2016/07/22 by Bob.Tellez

	Duplicating CL#3058203 from //Fortnite/Main

	#UE4 Clearing pin links for fixed up BT nodes so they are not asymmetrical by the time the node gets destroyed, causing an ensure to fail. Also removing the replaced nodes so they are no longer saved in the package.

Change 3063080 on 2016/07/25 by Benn.Gallagher

	Fixes to APEX rendering artefacts, by discarding all incoming render data from the APEX asset and skinning our render data to the simulation mesh.
	#jira UEFW-182

Change 3063119 on 2016/07/25 by Ori.Cohen

	Fix constraint index not being assigned correctly. Also expose FindConstraintBoneName to blueprints

	#JIRA UE-33716

Change 3063956 on 2016/07/25 by Ori.Cohen

	Fix SetRootBodyIndex so that it uses world space transform instead of ref skeleton. Fixes a few issues when using ragdolls on skeletal mesh assets that were imported with offset/rotation.
	Also fix crash when opening up old physics asset and changing its mesh.

	#JIRA UE-33753
	#JIRA UE-33754

Change 3064846 on 2016/07/26 by Benn.Gallagher

	Fixed crash when loading skeletal meshes with clothing in -game
	#jira UE-33771

Change 3065237 on 2016/07/26 by Ori.Cohen

	Fix physics handle component not accounting for bone rotation/offset

Change 3065241 on 2016/07/26 by Ori.Cohen

	Fix constraint component not using root body when no bone name is provided.

Change 3069508 on 2016/07/28 by Aaron.McLeran

	Adding debug exec commands to help with debugging audio

	Solos sounds when using the following exec commands:

	- AudioSoloSoundWave
	  Solos sounds with the given wave name

	- AudioSoloSoundClass
	Solos sounds with the given sound class

	- AudioSoloSoundCue
	Solos sounds with the given sound cue name

	-All solo commands can be active at the same time and will solo sounds that match all their substring name searches.
	- Only active in non-shipping builds

Change 3070857 on 2016/07/29 by Ori.Cohen

	Expose Grab Rotation for physics handle component. Also brought over changes from Jeff.F where we can now specify hard vs soft physics handle

Change 3072817 on 2016/08/01 by Wes.Hunt

	turn off optimizations to speed up build times. ~45s->~5sec apiece.

Change 3073855 on 2016/08/02 by Zak.Middleton

	#ue4 - Fix tooltip text for p.NetEnableListenServerSmoothing command.

Change 3074325 on 2016/08/02 by Aaron.McLeran

	UE-34081 Procedural Sound Wave Fails to Play when returning 0 bytes in GeneratePCMData callback

	- Returning 0 bytes in GeneratePCMData results in the procedural sound wave not continuing to play audio. Instead of returning 0, this change returns an empty buffer if the procedural sound wave doesn't have audio ready to generate (due to loading or some other issue).
	- Change also fixes a threading issue with QueueAudio queing audio on game thread but being consumed by audio device thread.

	- Implementing 3003851 from UT into Dev-Framework.

Change 3075259 on 2016/08/03 by James.Golding

	Fix up my ProcMeshComp test BPs

Change 3076637 on 2016/08/03 by Aaron.McLeran

	UE-34081 Adjustments to procedural sound wave implementation

	- Cleaned up base class implementation of GeneratePCMData
	   - Calls bound callback function first if there aren't enough samples available, then pumps the enqueued audio buffers
	   - Wrote a prototype gameplay C++ class that implements this to test procedural sound wave generation with a sine tone and to test not immediately returning audio when called back (to test the empty buffer copy code).

Change 3077340 on 2016/08/04 by Marc.Audy

	Minor header cleanups and readability changes

Change 3079669 on 2016/08/05 by Aaron.McLeran

	OR-26580 Implementing 3071258 to Dev-Framework

Change 3080958 on 2016/08/08 by Aaron.McLeran

	UE-34325 In process audio resource is corrupted during level change.

	- When a sound buffer is flushed from audio device manager and tries to stop sounds using a resource, was possible for the async header parse task to be in-flight, which would cause a crash
	- Fix is to bring back the code to call EnsureCompletion on tasks in the FreeResoruces function of the sound source object. This will potentially encure a slight perf increase when stopping a sound but audio engine is now going to run on a separate thread, so shouldn't have a game-thread impact in non-editor builds.

	#tests ran repro case described in bug several times without crashing (was previous 100% repro)

Change 3081314 on 2016/08/08 by Marc.Audy

	Trim DSO components that no longer have an archetype in the class hierarchy
	#jira UE-28374

Change 3082356 on 2016/08/09 by Marc.Audy

	Always clear lifespan timers on EndPlay

Change 3082680 on 2016/08/09 by Lukasz.Furman

	fixed gameplay debugger extensions activating during simulate in editor
	#jira UE-33343

Change 3082731 on 2016/08/09 by Aaron.McLeran

	UE-30629 "FXAudio2SoundSource contains NaN in channel" occur in test ShooterGame build

	- Issue was inverse transform was uninitialized in first update tick of the audio device. If a sound is trying to play in the first update tick, it'd generate NaNs as it tries to transform sound positions relative to an unitialized inverse listener transform
	- Fix is to update listener transform BEFORE updating sounds.
	- Also added an initialization to the inverse listener transform for good measure

Change 3082732 on 2016/08/09 by Aaron.McLeran

	Removing log spam on local player node

Change 3083051 on 2016/08/09 by Marc.Audy

	FLocalPlayerContext functions no longer check by default
	#jira UE-33370

Change 3083271 on 2016/08/09 by Dan.Reynolds

	Checking in QA test map for procedural sound waves

	-Added code for a UQAProceudralSoundWave that generates a sine tone that can be set by hz or midi note
	- Added BP code which uses the procedural sound wave and does some melody generation.

Change 3083947 on 2016/08/10 by Lukasz.Furman

	fixed navigation modifier export for capsule components
	#ue4

Change 3084594 on 2016/08/10 by Marc.Audy

	Enable threaded audio again

Change 3086237 on 2016/08/11 by Marc.Audy

	PR #2579: New feature: client-side level streaming (Contributed by slonopotamus)
	#jira UE-32896

Change 3086544 on 2016/08/11 by Michael.Noland

	Paper2D: Fixed a crash when mutating grouped sprite components at runtime, and cleaned up how collision rendering is done for grouped sprite components addressing some other issues as well
	#jira UE-34223
	#tests Tested with repro project from JIRA, as well as adding/removing instances in the editor and with some sprites in the group having collision and others not, clearing collision on the component itself, etc...

Change 3087280 on 2016/08/12 by Marc.Audy

	Fatal error including the path if a physics module fails to load

Change 3088105 on 2016/08/12 by Marc.Audy

	Strip native components that no longer exist from BP CDOs as well.
	#jira UE-28374

[CL 3090563 by Marc Audy in Main branch]
2016-08-16 12:05:05 -04:00

605 lines
18 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
/*------------------------------------------------------------------------------------
FSLESSoundSource.
------------------------------------------------------------------------------------*/
#include "AndroidAudioDevice.h"
#include "AudioDecompress.h"
#include "Engine.h"
// Callback that is registered if the source needs to loop
void OpenSLBufferQueueCallback( SLAndroidSimpleBufferQueueItf InQueueInterface, void* pContext )
{
FSLESSoundSource* SoundSource = (FSLESSoundSource*)pContext;
if( SoundSource )
{
SoundSource->OnRequeueBufferCallback( InQueueInterface );
}
}
// Requeues buffer to loop Sound Source
void FSLESSoundSource::OnRequeueBufferCallback( SLAndroidSimpleBufferQueueItf InQueueInterface )
{
if (!bStreamedSound)
{
SLresult result = (*SL_PlayerBufferQueue)->Enqueue(SL_PlayerBufferQueue, Buffer->AudioData, Buffer->GetSize() );
if(result != SL_RESULT_SUCCESS)
{
UE_LOG( LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER Enqueue SL_PlayerBufferQueue (Requeing)"));
}
bHasLooped = true;
}
else
{
// Enqueue the previously decoded buffer
if (RealtimeAsyncTask)
{
RealtimeAsyncTask->EnsureCompletion();
switch(RealtimeAsyncTask->GetTask().GetTaskType())
{
case ERealtimeAudioTaskType::Decompress:
bHasLooped = RealtimeAsyncTask->GetTask().GetBufferLooped();
break;
case ERealtimeAudioTaskType::Procedural:
AudioBuffers[BufferInUse].AudioDataSize = RealtimeAsyncTask->GetTask().GetBytesWritten();
break;
}
delete RealtimeAsyncTask;
RealtimeAsyncTask = nullptr;
}
// Sound decoding is complete, just waiting to finish playing
if (bBuffersToFlush)
{
// set the player's state to stopped
SLresult result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_STOPPED);
check(SL_RESULT_SUCCESS == result);
return;
}
SLresult result = (*SL_PlayerBufferQueue)->Enqueue(SL_PlayerBufferQueue, AudioBuffers[BufferInUse].AudioData, AudioBuffers[BufferInUse].AudioDataSize );
if(result != SL_RESULT_SUCCESS)
{
UE_LOG( LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER Enqueue SL_PlayerBufferQueue (Requeing)"));
}
// Switch to the next buffer and decode for the next time the callback fires if we didn't just get the last buffer
BufferInUse = !BufferInUse;
if (bHasLooped == false || WaveInstance->LoopingMode != LOOP_Never)
{
// Do this in the callback thread instead of creating an asynchronous task (thread id from callback is not consistent and use of TLS for stats causes issues)
if (ReadMorePCMData(BufferInUse, EDataReadMode::Synchronous))
{
// If this is a synchronous source we may get notified immediately that we have looped
bHasLooped = true;
}
}
}
}
bool FSLESSoundSource::CreatePlayer()
{
// data info
SLDataLocator_AndroidSimpleBufferQueue LocationBuffer = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
// PCM Info
SLDataFormat_PCM PCM_Format = { SL_DATAFORMAT_PCM, SLuint32(Buffer->NumChannels), SLuint32( Buffer->SampleRate * 1000 ),
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
Buffer->NumChannels == 2 ? ( SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT ) : SL_SPEAKER_FRONT_CENTER,
SL_BYTEORDER_LITTLEENDIAN };
SLDataSource SoundDataSource = { &LocationBuffer, &PCM_Format};
// configure audio sink
SLDataLocator_OutputMix Output_Mix = { SL_DATALOCATOR_OUTPUTMIX, ((FSLESAudioDevice *)AudioDevice)->SL_OutputMixObject};
SLDataSink AudioSink = { &Output_Mix, NULL};
// create audio player
const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*Device->SL_EngineEngine)->CreateAudioPlayer( Device->SL_EngineEngine, &SL_PlayerObject,
&SoundDataSource, &AudioSink, sizeof(ids) / sizeof(SLInterfaceID), ids, req );
if(result != SL_RESULT_SUCCESS)
{
UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER CreateAudioPlayer 0x%x"), result);
return false;
}
bool bFailedSetup = false;
// realize the player
result = (*SL_PlayerObject)->Realize(SL_PlayerObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) { UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER Realize 0x%x"), result); return false; }
// get the play interface
result = (*SL_PlayerObject)->GetInterface(SL_PlayerObject, SL_IID_PLAY, &SL_PlayerPlayInterface);
if (result != SL_RESULT_SUCCESS) { UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER GetInterface SL_IID_PLAY 0x%x"), result); bFailedSetup |= true; }
// volume
result = (*SL_PlayerObject)->GetInterface(SL_PlayerObject, SL_IID_VOLUME, &SL_VolumeInterface);
if (result != SL_RESULT_SUCCESS) { UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER GetInterface SL_IID_VOLUME 0x%x"), result); bFailedSetup |= true; }
// buffer system
result = (*SL_PlayerObject)->GetInterface(SL_PlayerObject, SL_IID_BUFFERQUEUE, &SL_PlayerBufferQueue);
if (result != SL_RESULT_SUCCESS) { UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER GetInterface SL_IID_BUFFERQUEUE 0x%x"), result); bFailedSetup |= true; }
return bFailedSetup == false;
}
void FSLESSoundSource::DestroyPlayer()
{
if( SL_PlayerObject )
{
// close it down...
(*SL_PlayerObject)->Destroy(SL_PlayerObject);
SL_PlayerObject = NULL;
SL_PlayerPlayInterface = NULL;
SL_PlayerBufferQueue = NULL;
SL_VolumeInterface = NULL;
}
}
bool FSLESSoundSource::EnqueuePCMBuffer( bool bLoop)
{
SLresult result;
// If looping, register a callback to requeue the buffer
if( bLoop )
{
result = (*SL_PlayerBufferQueue)->RegisterCallback(SL_PlayerBufferQueue, OpenSLBufferQueueCallback, (void*)this);
if (result != SL_RESULT_SUCCESS) { UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER QUEUE RegisterCallback 0x%x "), result); return false; }
}
result = (*SL_PlayerBufferQueue)->Enqueue(SL_PlayerBufferQueue, Buffer->AudioData, Buffer->GetSize() );
if (result != SL_RESULT_SUCCESS) {
UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER Enqueue SL_PlayerBufferQueue 0x%x params( %p, %d)"), result, Buffer->AudioData, int32(Buffer->GetSize()));
if (bLoop)
{
result = (*SL_PlayerBufferQueue)->RegisterCallback(SL_PlayerBufferQueue, NULL, NULL);
}
return false;
}
bStreamedSound = false;
bHasLooped = false;
bBuffersToFlush = false;
return true;
}
bool FSLESSoundSource::ReadMorePCMData(const int32 BufferIndex, EDataReadMode DataReadMode)
{
USoundWave* WaveData = WaveInstance->WaveData;
if (WaveData && WaveData->bProcedural)
{
const int32 MaxSamples = BufferSize / sizeof(int16);
if (DataReadMode == EDataReadMode::Synchronous || WaveData->bCanProcessAsync == false)
{
const int32 BytesWritten = WaveData->GeneratePCMData(AudioBuffers[BufferIndex].AudioData, MaxSamples);
AudioBuffers[BufferIndex].AudioDataSize = BytesWritten;
}
else
{
RealtimeAsyncTask = new FAsyncRealtimeAudioTask(WaveData, AudioBuffers[BufferIndex].AudioData, MaxSamples);
RealtimeAsyncTask->StartBackgroundTask();
}
// we're never actually "looping" here.
return false;
}
else
{
if (DataReadMode == EDataReadMode::Synchronous)
{
return Buffer->ReadCompressedData(AudioBuffers[BufferIndex].AudioData, WaveInstance->LoopingMode != LOOP_Never);
}
else
{
RealtimeAsyncTask = new FAsyncRealtimeAudioTask(Buffer, AudioBuffers[BufferIndex].AudioData, WaveInstance->LoopingMode != LOOP_Never, DataReadMode == EDataReadMode::AsynchronousSkipFirstFrame);
RealtimeAsyncTask->StartBackgroundTask();
return false;
}
}
}
bool FSLESSoundSource::EnqueuePCMRTBuffer( bool bLoop )
{
if (AudioBuffers[0].AudioData || AudioBuffers[1].AudioData)
{
UE_LOG( LogAndroidAudio, Warning, TEXT("Enqueue PCMRT with buffers already allocated"));
}
FMemory::Memzero( AudioBuffers, sizeof( SLESAudioBuffer ) * 2 );
// Set up double buffer area to decompress to
BufferSize = Buffer->GetRTBufferSize() * Buffer->NumChannels;
AudioBuffers[0].AudioData = (uint8*)FMemory::Malloc(BufferSize);
AudioBuffers[0].AudioDataSize = BufferSize;
AudioBuffers[1].AudioData = (uint8*)FMemory::Malloc(BufferSize);
AudioBuffers[1].AudioDataSize = BufferSize;
// Only use the cached data if we're starting from the beginning, otherwise we'll have to take a synchronous hit
if (WaveInstance->WaveData && WaveInstance->WaveData->CachedRealtimeFirstBuffer && WaveInstance->StartTime == 0.f)
{
FMemory::Memcpy((uint8*)AudioBuffers[0].AudioData, WaveInstance->WaveData->CachedRealtimeFirstBuffer, BufferSize);
ReadMorePCMData(1, EDataReadMode::AsynchronousSkipFirstFrame);
}
else
{
ReadMorePCMData(0, EDataReadMode::Synchronous);
ReadMorePCMData(1, EDataReadMode::Asynchronous);
}
SLresult result;
// callback is used to submit and decompress next buffer
result = (*SL_PlayerBufferQueue)->RegisterCallback(SL_PlayerBufferQueue, OpenSLBufferQueueCallback, (void*)this);
// queue one sound buffer, as that is all Android will accept
if(result == SL_RESULT_SUCCESS)
{
result = (*SL_PlayerBufferQueue)->Enqueue(SL_PlayerBufferQueue, AudioBuffers[0].AudioData, AudioBuffers[0].AudioDataSize );
if (result != SL_RESULT_SUCCESS) { UE_LOG(LogAndroidAudio, Warning, TEXT("FAILED OPENSL BUFFER Enqueue SL_PlayerBufferQueue 0x%x params( %p, %d)"), result, Buffer->AudioData, int32(Buffer->GetSize())); return false; }
}
else
{
return false;
}
bStreamedSound = true;
bHasLooped = false;
bBuffersToFlush = false;
BufferInUse = 1;
return true;
}
/**
* Initializes a source with a given wave instance and prepares it for playback.
*
* @param WaveInstance wave instance being primed for playback
* @return TRUE if initialization was successful, FALSE otherwise
*/
bool FSLESSoundSource::Init( FWaveInstance* InWaveInstance )
{
// don't do anything if no volume! THIS APPEARS TO HAVE THE VOLUME IN TIME, CHECK HERE THOUGH IF ISSUES
if( InWaveInstance && ( InWaveInstance->Volume * InWaveInstance->VolumeMultiplier ) <= 0 )
{
return false;
}
if (Buffer && Buffer->ResourceID == 0)
{
UE_LOG( LogAndroidAudio, Warning, TEXT(" InitSoundSouce with Buffer already allocated"));
delete Buffer;
Buffer = 0;
}
if (SL_PlayerObject)
{
UE_LOG( LogAndroidAudio, Warning, TEXT(" InitSoundSouce with PlayerObject not NULL, possible leak"));
}
// Find matching buffer.
Buffer = FSLESSoundBuffer::Init( (FSLESAudioDevice *)AudioDevice, InWaveInstance->WaveData );
if( Buffer && InWaveInstance->WaveData->NumChannels <= 2 && InWaveInstance->WaveData->SampleRate <= 48000 )
{
SCOPE_CYCLE_COUNTER( STAT_AudioSourceInitTime );
bool bFailedSetup = false;
if (CreatePlayer())
{
WaveInstance = InWaveInstance;
if (WaveInstance->StartTime > 0.f)
{
Buffer->Seek(WaveInstance->StartTime);
}
switch( Buffer->Format)
{
case SoundFormat_PCM:
bFailedSetup |= !EnqueuePCMBuffer( InWaveInstance->LoopingMode != LOOP_Never );
break;
case SoundFormat_PCMRT:
bFailedSetup |= !EnqueuePCMRTBuffer( InWaveInstance->LoopingMode != LOOP_Never );
break;
default:
bFailedSetup = true;
}
}
else
{
bFailedSetup = true;
}
// clean up the madness if anything we need failed
if( bFailedSetup )
{
UE_LOG( LogAndroidAudio, Warning, TEXT(" Setup failed %s"), *InWaveInstance->WaveData->GetName());
DestroyPlayer();
return false;
}
Update();
// Initialization was successful.
return true;
}
else
{
// Failed to initialize source.
// These occurences appear to potentially lead to leaks
UE_LOG( LogAndroidAudio, Warning, TEXT("Init SoundSource failed on %s"), *InWaveInstance->WaveData->GetName());
UE_LOG( LogAndroidAudio, Warning, TEXT(" SampleRate %d"), InWaveInstance->WaveData->SampleRate);
UE_LOG( LogAndroidAudio, Warning, TEXT(" Channels %d"), InWaveInstance->WaveData->NumChannels);
if (Buffer && Buffer->ResourceID == 0)
{
delete Buffer;
Buffer = 0;
}
}
return false;
}
FSLESSoundSource::FSLESSoundSource( class FAudioDevice* InAudioDevice )
: FSoundSource( InAudioDevice ),
Device((FSLESAudioDevice *)InAudioDevice),
Buffer( NULL ),
bStreamedSound(false),
bBuffersToFlush(false),
BufferSize(0),
BufferInUse(0),
VolumePreviousUpdate(-1.0f),
bHasLooped(false),
SL_PlayerObject(NULL),
SL_PlayerPlayInterface(NULL),
SL_PlayerBufferQueue(NULL),
SL_VolumeInterface(NULL),
RealtimeAsyncTask(NULL)
{
FMemory::Memzero( AudioBuffers, sizeof( AudioBuffers ) );
}
/**
* Clean up any hardware referenced by the sound source
*/
FSLESSoundSource::~FSLESSoundSource( void )
{
DestroyPlayer();
ReleaseResources();
}
void FSLESSoundSource::ReleaseResources()
{
if (RealtimeAsyncTask)
{
RealtimeAsyncTask->EnsureCompletion();
delete RealtimeAsyncTask;
RealtimeAsyncTask = nullptr;
}
FMemory::Free( AudioBuffers[0].AudioData);
FMemory::Free( AudioBuffers[1].AudioData);
FMemory::Memzero( AudioBuffers, sizeof( AudioBuffers ) );
if (Buffer && Buffer->ResourceID == 0)
{
delete Buffer;
}
Buffer = NULL;
}
/**
* Updates the source specific parameter like e.g. volume and pitch based on the associated
* wave instance.
*/
void FSLESSoundSource::Update( void )
{
SCOPE_CYCLE_COUNTER( STAT_AudioUpdateSources );
if( !WaveInstance || Paused )
{
return;
}
float Volume = WaveInstance->Volume * WaveInstance->VolumeMultiplier;
if (SetStereoBleed())
{
// Emulate the bleed to rear speakers followed by stereo fold down
Volume *= 1.25f;
}
Volume *= AudioDevice->GetPlatformAudioHeadroom();
Volume = FMath::Clamp(Volume, 0.0f, MAX_VOLUME);
Volume = FSoundSource::GetDebugVolume(Volume);
const float Pitch = FMath::Clamp<float>(WaveInstance->Pitch, MIN_PITCH, MAX_PITCH);
// Set whether to apply reverb
SetReverbApplied(true);
SetFilterFrequency();
FVector Location;
FVector Velocity;
// See file header for coordinate system explanation.
Location.X = WaveInstance->Location.X;
Location.Y = WaveInstance->Location.Z; // Z/Y swapped to match UE coordinate system
Location.Z = WaveInstance->Location.Y; // Z/Y swapped to match UE coordinate system
Velocity.X = WaveInstance->Velocity.X;
Velocity.Y = WaveInstance->Velocity.Z; // Z/Y swapped to match UE coordinate system
Velocity.Z = WaveInstance->Velocity.Y; // Z/Y swapped to match UE coordinate system
// We're using a relative coordinate system for un- spatialized sounds.
if( !WaveInstance->bUseSpatialization )
{
Location = FVector( 0.f, 0.f, 0.f );
}
// Set volume (Pitch changes are not supported on current Android platforms!)
// also Location & Velocity
// Avoid doing the log calculation each update by only doing it if the volume changed
if (Volume != VolumePreviousUpdate)
{
VolumePreviousUpdate = Volume;
static const int64 MinVolumeMillibel = -12000;
if (Volume > 0.0f)
{
// Convert volume to millibels.
SLmillibel MaxMillibel = 0;
(*SL_VolumeInterface)->GetMaxVolumeLevel(SL_VolumeInterface, &MaxMillibel);
SLmillibel VolumeMillibel = (SLmillibel)FMath::Clamp<int64>((int64)(2000.0f * log10f(Volume)), MinVolumeMillibel, (int64)MaxMillibel);
SLresult result = (*SL_VolumeInterface)->SetVolumeLevel(SL_VolumeInterface, VolumeMillibel);
check(SL_RESULT_SUCCESS == result);
}
else
{
SLresult result = (*SL_VolumeInterface)->SetVolumeLevel(SL_VolumeInterface, MinVolumeMillibel);
check(SL_RESULT_SUCCESS == result);
}
}
}
/**
* Plays the current wave instance.
*/
void FSLESSoundSource::Play( void )
{
if( WaveInstance )
{
// Reset the previous volume on play so it can be set at least once in the update function
VolumePreviousUpdate = -1.0f;
// Update volume now before starting play
Paused = false;
Update();
// set the player's state to playing
SLresult result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_PLAYING);
check(SL_RESULT_SUCCESS == result);
Playing = true;
}
}
/**
* Stops the current wave instance and detaches it from the source.
*/
void FSLESSoundSource::Stop( void )
{
if( WaveInstance )
{
// set the player's state to stopped
SLresult result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_STOPPED);
check(SL_RESULT_SUCCESS == result);
// Unregister looping callback
if( WaveInstance->LoopingMode != LOOP_Never )
{
result = (*SL_PlayerBufferQueue)->RegisterCallback(SL_PlayerBufferQueue, NULL, NULL);
}
DestroyPlayer();
ReleaseResources();
Paused = false;
Playing = false;
Buffer = NULL;
}
FSoundSource::Stop();
}
/**
* Pauses playback of current wave instance.
*/
void FSLESSoundSource::Pause( void )
{
if( WaveInstance )
{
Paused = true;
// set the player's state to paused
SLresult result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_PAUSED);
check(SL_RESULT_SUCCESS == result);
}
}
/**
* Returns TRUE if the source has finished playing
*/
bool FSLESSoundSource::IsSourceFinished( void )
{
SLuint32 PlayState;
// set the player's state to playing
SLresult result = (*SL_PlayerPlayInterface)->GetPlayState(SL_PlayerPlayInterface, &PlayState);
check(SL_RESULT_SUCCESS == result);
if( PlayState == SL_PLAYSTATE_STOPPED )
{
return true;
}
return false;
}
/**
* Queries the status of the currently associated wave instance.
*
* @return TRUE if the wave instance/ source has finished playback and FALSE if it is
* currently playing or paused.
*/
bool FSLESSoundSource::IsFinished( void )
{
if( WaveInstance )
{
// Check for a non starved, stopped source
if( IsSourceFinished() )
{
// Notify the wave instance that it has finished playing
WaveInstance->NotifyFinished();
return true;
}
else
{
if (bHasLooped)
{
switch (WaveInstance->LoopingMode)
{
case LOOP_Forever:
bHasLooped = false;
break;
case LOOP_Never:
bBuffersToFlush = true;
break;
case LOOP_WithNotification:
bHasLooped = false;
// Notify the wave instance that it has finished playing.
WaveInstance->NotifyFinished();
break;
};
}
}
return false;
}
return true;
}