You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3118534 on 2016/09/08 by Steve.Cano Certain non-looping SFX would not properly fire "Stop" events when the effect was finished, and therefore Sound Mixes that should end at the end of a SFX (such as ducking the BGM volume) were not properly finishing. Fixing the IsSourceFinished call to check the Position of the player to determine if we are actually done playing, which was not working properly before for PCM sounds. #jira UE-35016 #ue4 #android Change 3119125 on 2016/09/09 by Dmitriy.Dyomin Mobile launcher profile wizard: Fixed case where DLC will not be built if user selects non Development build configuration Fixed case where project maps will be empty if UE4 and project located on different drives Change 3122584 on 2016/09/13 by Allan.Bentham Add simple clip plane for planar reflections. #jira UE-32449 Change 3129390 on 2016/09/16 by Chris.Babcock Fixed ES 3.1 detection to also support devices returning ES 3.2 support (eg Note 7) #jira UE-35598 Change 3129867 on 2016/09/18 by Jack.Porter Fixed ES 3.1 detection to also support devices returning ES 3.2 support (eg Note 7) #jira UE-35598 Change 3131961 on 2016/09/20 by Allan.Bentham Fix missing editor widgets and gamma incorrectness when mobileHDR == false. Fix editor widget rendering when hdr encoding is active. #jira UE-34281 Change 3132717 on 2016/09/20 by Chris.Babcock Add $S(ProjectDir) to UPL #jira UE-35483 #ue4 Change 3132940 on 2016/09/20 by Chris.Babcock Corrected case for some include files (contributed by Yukariin) #jira UE-33816 #PR #2636 #ue4 #android Change 3134098 on 2016/09/21 by Allan.Bentham Mobile CSM shadow quality controllable via quality levels. #jira UEMOB-74 Change 3134931 on 2016/09/21 by Chris.Babcock Allow Windows types in vulkan.h #jira UE-36270 #ue4 #vulkan Change 3135380 on 2016/09/21 by Dmitriy.Dyomin Plugin which exposes some of BuildPatchServices functionality to BP. Inteded to be used on mobile platforms for donwloading game content. Right now misses: IOS download directory and iOS WiFi detection #jira UEMOB-157 Change 3136004 on 2016/09/22 by Allan.Bentham Add project option to disable vertex fog on mobile. Vertex fog is now enabled even when mobile HDR is not. #jira UEMOB-148 Change 3137377 on 2016/09/22 by Dmitriy.Dyomin Fix compile error from CL# 3135380 Change 3139571 on 2016/09/26 by Jack.Porter Applied deferred change CL 3101462 to mobile to make planar reflections no longer update GPU particles Change 3139663 on 2016/09/26 by Jack.Porter Include Android shader cache files when packaging Change 3142839 on 2016/09/28 by Dmitriy.Dyomin Added WiFi connection detection on iOS Change 3142845 on 2016/09/28 by Jack.Porter Fixed various issues with TcpMessageTransport discovered when transferring automation testing screenshots from mobile devices - socket not readable or writable is not an error condition if output buffer is full - messages were previously limited to 64kb but screenshots overflowed this - messages over 8kb were not reliably received as the inbound buffer was full so the available bytes was always less than the message length - sending large messages was not reliable due to the output buffer being full Change 3143280 on 2016/09/28 by Jack.Porter Clear out UnbuiltInstanceBoundsList when async building a tree with no instances Change 3143282 on 2016/09/28 by Jack.Porter Fix issue where client functional tests in the current map do not appear on clients running with cooked content. Problem is that the AssetRegistry uses in-memory metadata created on load for currently-loaded assets, but cooked content only has the serialized AssetRegistry and individual assets do not contain any metadata. Change 3143808 on 2016/09/28 by Steve.Cano Assume that the app starts in focus at startup and don't wait for an "APP_EVENT_STATE_WINDOW_GAINED_FOCUS" event to fire, as this event will not come down from SplashActivity since it is not a NativeActivity. If the user then rotates the device in Sensor or FullSensor orientation during SplashActivity and forces an eglSurface recreation, the initial Create will properly execute if we're "in focus". Previously, the create-destroy-create cycle would not properly execute due to the EventManager thinking the app was not yet in focus, and would cause the second create to get a 0x3003 error (EGL_BAD_ALLOC) #jira UE-35004 #ue4 #android Change 3144880 on 2016/09/29 by Jack.Porter Merging //UE4/Dev-Main to Dev-Mobile (//UE4/Dev-Mobile) Change 3146220 on 2016/09/30 by Dmitriy.Dyomin Adjusted device button style to make it more readable. #jira UE-21881 Change3146280on 2016/09/30 by Dmitriy.Dyomin Replaced IBuildManifest::ComputeDownloadSize with a new function Change 3146302 on 2016/09/30 by Allan.Bentham Added more stringent checks for ES3.1 compatibility #jira UE-36241 Change 3146435 on 2016/09/30 by Jack.Porter Prevent landscape grass being duplicated for PIE, causing ensure #jira UE-36531 Change 3147532 on 2016/09/30 by Chris.Babcock Use .sh extension for Android install scripts on Linux #jira UE-36669 #ue4 #android #linux Change 3149851 on 2016/10/04 by Dmitriy.Dyomin Mobile: Added custom depth rendering Mobile: Added support for CustomDepth and SceneDepth in post-process materails Change 3149852 on 2016/10/04 by Dmitriy.Dyomin Fixed comments for SortBasePass console variable Change 3149857 on 2016/10/04 by Jack.Porter Remove dead code in ProceduralFoliageComponentDetails.cpp Change 3149863 on 2016/10/04 by Jack.Porter Merging //UE4/Dev-Main to Dev-Mobile (//UE4/Dev-Mobile) Change 3149896 on 2016/10/04 by Dmitriy.Dyomin Fixed: SkyLight makes level entire green on Android devices #jira UE-34469 Change 3150102 on 2016/10/04 by Jack.Porter Bring Protostar 4.13 fixes back to Dev-Mobile Engine - MaxDescriptorSets = 16384 to prevent crash on Mali - Include texture formats in FRenderTargetLayoutHashableStruct to solve RT aliasing issue - Use ERenderTargetLoadAction::EClear for planar reflection target to work around Adreno issue - Default Adreno to SPIR-V Contents - Disable fog, reduce CSM shadow quality, fix device profiles - Add PSO cache Change 3150113 on 2016/10/04 by Jack.Porter Ensure automation testing screenshots have Alpha=255 (fixes automation screenshots on Mobile) Change 3150231 on 2016/10/04 by Jack.Porter Use a new SessionID GUID each time you use the launcher to launch a session. Change 3150608 on 2016/10/04 by Jack.Porter Changes for automated testing screenshots on Android. - Prevent automation screenshots from changing resolution on platforms with fixed resolution - Set GRHIAdapterInternalDriverVersion for OpenGL and Vulkan - Parse ImgTec/ARM/Qualcomm GRHIVendorId on OpenGL - Added helper to convert GRHIVendorId to string Change 3151318 on 2016/10/04 by Jack.Porter Fixed compile error with AdapterVendor Change 3151366 on 2016/10/04 by Jack.Porter Prevent FTcpMessageTransportConnection deadlock on device disconnect Change 3151397 on 2016/10/05 by Dmitriy.Dyomin More consistent BP categories for Mobile Patching utils Change 3151576 on 2016/10/05 by Dmitriy.Dyomin Added on screen warning for invalid reflection captures, can be seen only in game running with FeatureLevel < SM4 and no valid capture data Change 3151795 on 2016/10/05 by Dmitry.Rekman Linux: update UBT to use a v8 multiarch toolchain. - Also added toolchain build scripts and ct-ng configs. Change 3151966 on 2016/10/05 by Allan.Bentham Add mobile support for inverse opacity to mobile scene captures as well as SCS_SceneColorSceneDepth and SCS_SceneDepth. #jira UEMOB-106 Change 3152664 on 2016/10/05 by Chris.Babcock Merging //UE4/Main to Dev-Mobile (//UE4/Dev-Mobile) Change 3152675 on 2016/10/05 by Will.Fissler Fixed patching so that it searches for pak files as well as pkg files. #test Patch for QAGame Change 3152728 on 2016/10/05 by Chris.Babcock Update ReflectionCaptureDDCVer (need to resave maps) Change 3152910 on 2016/10/05 by Dmitry.Rekman Linux: Fix toolchain for non-AutoSDKs (github) case (UE-36899). Change 3152966 on 2016/10/05 by Dmitry.Rekman Linux: Fix test for the installed SDK (UE-36899). Change 3153004 on 2016/10/05 by Dmitry.Rekman Linux: fix CIS (UT server case-sens errors). Change 3153694 on 2016/10/06 by Jack.Porter Rollback ReflectionCaptureDDCVer change as bug intended to fix UE-36919 does not repro Change 3154766 on 2016/10/07 by Jack.Porter Merging //UE4/Dev-Main to Dev-Mobile (//UE4/Dev-Mobile) Change 3154833 on 2016/10/07 by Jack.Porter Fix merge error of MobileShadingRenderer.cpp Change 3154848 on 2016/10/07 by Allan.Bentham Fix mobile scene capture's clear code Change 3154875 on 2016/10/07 by Allan.Bentham fix vk build issues Change 3154941 on 2016/10/07 by Allan.Bentham Fix gearvr build fail Change 3154950 on 2016/10/07 by Allan.Bentham Fix shadowed local variable vk build warning on android. Change3155909on 2016/10/07 by Ben.Marsh UBT: Attempt to work around C1076 error ("internal heap limit reached: use /Zm to specify a higher limit"), encountered when building with XGE. Specify the AutoReserveMemory attribute on XGE tool tasks that manipulate precompiled headers. [CL 3155988 by Chris Babcock in Main branch]
634 lines
19 KiB
C++
634 lines
19 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;
|
|
bHasPositionUpdated = 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;
|
|
bHasPositionUpdated = 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),
|
|
bHasPositionUpdated(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;
|
|
}
|
|
|
|
if (WaveInstance && WaveInstance->LoopingMode == LOOP_Never)
|
|
{
|
|
// if it wasn't that simple, see if we're at the end position
|
|
SLmillisecond PositionMs;
|
|
SLmillisecond DurationMs;
|
|
|
|
result = (*SL_PlayerPlayInterface)->GetPosition(SL_PlayerPlayInterface, &PositionMs);
|
|
check(SL_RESULT_SUCCESS == result);
|
|
|
|
result = (*SL_PlayerPlayInterface)->GetDuration(SL_PlayerPlayInterface, &DurationMs);
|
|
check(SL_RESULT_SUCCESS == result);
|
|
|
|
// on some android devices, the value for GetPosition wraps back to 0 when the playback is done, however it's very possible
|
|
// for us to try to check for IsSourceFinished when the Position is genuinely "0". Therefore, we'll flip bHasPositionUpdated once
|
|
// we've actually started the sound to denote a wrap-back 0 position versus a real 0 position
|
|
if ((DurationMs != SL_TIME_UNKNOWN && PositionMs == DurationMs) || (PositionMs == 0 && bHasPositionUpdated))
|
|
{
|
|
return true;
|
|
}
|
|
else if (!bHasPositionUpdated && PositionMs > 0)
|
|
{
|
|
bHasPositionUpdated = 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;
|
|
}
|