You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Integration touches several places in the engine: 1) SoundWave -- A check box enables Bink Audio as the codec of choice for that sound wave. 2) Decoder - Each supported platform's AudioMixer now returns BINK if the soundwave requests it. Additionally, the TargetPlatform returns BINK as an available codec, and returns it to the cooking code if the sound wave requests it. 3) Encode - TargetPlatform.Build.cs adds the encoder to the editor dependencies, and it gets picked up in the TPMM formats search. #ROBOMERGE-SOURCE: CL 16682710 in //UE5/Main/... #ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v835-16672529) [CL 16682731 by dan thompson in ue5-release-engine-test branch]
518 lines
15 KiB
C++
518 lines
15 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AudioMixerPlatformAndroid.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "AudioMixer.h"
|
|
#include "CoreGlobals.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/ScopeLock.h"
|
|
|
|
#if WITH_ENGINE
|
|
#include "VorbisAudioInfo.h"
|
|
#include "ADPCMAudioInfo.h"
|
|
#include "BinkAudioInfo.h"
|
|
#include "AudioPluginUtilities.h"
|
|
#endif
|
|
|
|
|
|
#include <SLES/OpenSLES.h>
|
|
#include "SLES/OpenSLES_Android.h"
|
|
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogAudioMixerAndroid, Log, All);
|
|
DEFINE_LOG_CATEGORY(LogAudioMixerAndroid);
|
|
|
|
#define UNREAL_AUDIO_TEST_WHITE_NOISE 0
|
|
|
|
// Macro to check result for XAudio2 failure, get string version, log, and return false
|
|
#define OPENSLES_RETURN_ON_FAIL(Result) \
|
|
if (Result != SL_RESULT_SUCCESS) \
|
|
{ \
|
|
const TCHAR* ErrorString = GetErrorString(Result); \
|
|
AUDIO_PLATFORM_ERROR(ErrorString); \
|
|
return false; \
|
|
}
|
|
|
|
#define OPENSLES_CHECK_ON_FAIL(Result) \
|
|
if (Result != SL_RESULT_SUCCESS) \
|
|
{ \
|
|
const TCHAR* ErrorString = GetErrorString(Result); \
|
|
AUDIO_PLATFORM_ERROR(ErrorString); \
|
|
check(false); \
|
|
}
|
|
|
|
#define OPENSLES_LOG_ON_FAIL(Result) \
|
|
if (Result != SL_RESULT_SUCCESS) \
|
|
{ \
|
|
const TCHAR* ErrorString = GetErrorString(Result); \
|
|
AUDIO_PLATFORM_ERROR(ErrorString); \
|
|
}
|
|
|
|
#if USE_ANDROID_JNI
|
|
extern int32 AndroidThunkCpp_GetMetaDataInt(const FString& Key);
|
|
#endif
|
|
|
|
namespace Audio
|
|
{
|
|
FMixerPlatformAndroid::FMixerPlatformAndroid()
|
|
: bSuspended(false)
|
|
, bInitialized(false)
|
|
, bInCallback(false)
|
|
, NumSamplesPerRenderCallback(0)
|
|
, NumSamplesPerDeviceCallback(0)
|
|
{
|
|
}
|
|
|
|
FMixerPlatformAndroid::~FMixerPlatformAndroid()
|
|
{
|
|
if (bInitialized)
|
|
{
|
|
TeardownHardware();
|
|
}
|
|
}
|
|
|
|
const TCHAR* FMixerPlatformAndroid::GetErrorString(SLresult Result)
|
|
{
|
|
switch (Result)
|
|
{
|
|
case SL_RESULT_PRECONDITIONS_VIOLATED: return TEXT("SL_RESULT_PRECONDITIONS_VIOLATED");
|
|
case SL_RESULT_PARAMETER_INVALID: return TEXT("SL_RESULT_PARAMETER_INVALID");
|
|
case SL_RESULT_MEMORY_FAILURE: return TEXT("SL_RESULT_MEMORY_FAILURE");
|
|
case SL_RESULT_RESOURCE_ERROR: return TEXT("SL_RESULT_RESOURCE_ERROR");
|
|
case SL_RESULT_RESOURCE_LOST: return TEXT("SL_RESULT_RESOURCE_LOST");
|
|
case SL_RESULT_IO_ERROR: return TEXT("SL_RESULT_IO_ERROR");
|
|
case SL_RESULT_BUFFER_INSUFFICIENT: return TEXT("SL_RESULT_BUFFER_INSUFFICIENT");
|
|
case SL_RESULT_CONTENT_CORRUPTED: return TEXT("SL_RESULT_CONTENT_CORRUPTED");
|
|
case SL_RESULT_CONTENT_UNSUPPORTED: return TEXT("SL_RESULT_CONTENT_UNSUPPORTED");
|
|
case SL_RESULT_CONTENT_NOT_FOUND: return TEXT("SL_RESULT_CONTENT_NOT_FOUND");
|
|
case SL_RESULT_PERMISSION_DENIED: return TEXT("SL_RESULT_PERMISSION_DENIED");
|
|
case SL_RESULT_FEATURE_UNSUPPORTED: return TEXT("SL_RESULT_FEATURE_UNSUPPORTED");
|
|
case SL_RESULT_INTERNAL_ERROR: return TEXT("SL_RESULT_INTERNAL_ERROR");
|
|
case SL_RESULT_OPERATION_ABORTED: return TEXT("SL_RESULT_OPERATION_ABORTED");
|
|
case SL_RESULT_CONTROL_LOST: return TEXT("SL_RESULT_CONTROL_LOST");
|
|
|
|
default:
|
|
case SL_RESULT_UNKNOWN_ERROR: return TEXT("SL_RESULT_UNKNOWN_ERROR");
|
|
}
|
|
}
|
|
|
|
int32 FMixerPlatformAndroid::GetDeviceBufferSize(int32 RenderCallbackSize) const
|
|
{
|
|
#if USE_ANDROID_JNI
|
|
// Override with platform-specific frames per buffer size
|
|
int32 MinFramesPerBuffer = AndroidThunkCpp_GetMetaDataInt(TEXT("audiomanager.framesPerBuffer"));
|
|
|
|
int32 BufferSizeToUse = MinFramesPerBuffer;
|
|
while (BufferSizeToUse < RenderCallbackSize)
|
|
{
|
|
BufferSizeToUse += MinFramesPerBuffer;
|
|
}
|
|
|
|
return BufferSizeToUse;
|
|
#else
|
|
ensureMsgf(false, TEXT("JNI not supported on this platform. Audio output may be broken."));
|
|
return 1024;
|
|
#endif
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::InitializeHardware()
|
|
{
|
|
if (bInitialized)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SLresult Result;
|
|
SLEngineOption EngineOption[] = { {(SLuint32) SL_ENGINEOPTION_THREADSAFE, (SLuint32) SL_BOOLEAN_TRUE} };
|
|
|
|
// Create engine
|
|
Result = slCreateEngine( &SL_EngineObject, 1, EngineOption, 0, NULL, NULL);
|
|
OPENSLES_CHECK_ON_FAIL(Result);
|
|
|
|
// Realize the engine
|
|
Result = (*SL_EngineObject)->Realize(SL_EngineObject, SL_BOOLEAN_FALSE);
|
|
OPENSLES_CHECK_ON_FAIL(Result);
|
|
|
|
// get the engine interface, which is needed in order to create other objects
|
|
Result = (*SL_EngineObject)->GetInterface(SL_EngineObject, SL_IID_ENGINE, &SL_EngineEngine);
|
|
OPENSLES_CHECK_ON_FAIL(Result);
|
|
|
|
// create output mix
|
|
Result = (*SL_EngineEngine)->CreateOutputMix(SL_EngineEngine, &SL_OutputMixObject, 0, NULL, NULL );
|
|
OPENSLES_CHECK_ON_FAIL(Result);
|
|
|
|
// realize the output mix
|
|
Result = (*SL_OutputMixObject)->Realize(SL_OutputMixObject, SL_BOOLEAN_FALSE);
|
|
OPENSLES_CHECK_ON_FAIL(Result);
|
|
|
|
bInitialized = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::TeardownHardware()
|
|
{
|
|
if(!bInitialized)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Teardown OpenSLES..
|
|
// Destroy the SLES objects in reverse order of creation:
|
|
if (SL_OutputMixObject)
|
|
{
|
|
(*SL_OutputMixObject)->Destroy(SL_OutputMixObject);
|
|
SL_OutputMixObject = nullptr;
|
|
}
|
|
|
|
if (SL_EngineObject)
|
|
{
|
|
(*SL_EngineObject)->Destroy(SL_EngineObject);
|
|
|
|
SL_EngineObject = nullptr;
|
|
SL_EngineEngine = nullptr;
|
|
}
|
|
|
|
bInitialized = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::IsInitialized() const
|
|
{
|
|
return bInitialized;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::GetNumOutputDevices(uint32& OutNumOutputDevices)
|
|
{
|
|
OutNumOutputDevices = 1;
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::GetOutputDeviceInfo(const uint32 InDeviceIndex, FAudioPlatformDeviceInfo& OutInfo)
|
|
{
|
|
#if USE_ANDROID_JNI
|
|
OutInfo.Name = TEXT("Android Audio Device");
|
|
OutInfo.DeviceId = 0;
|
|
OutInfo.bIsSystemDefault = true;
|
|
OutInfo.SampleRate = AndroidThunkCpp_GetMetaDataInt(TEXT("audiomanager.optimalSampleRate"));
|
|
OutInfo.NumChannels = 2; // Android doesn't support surround sound
|
|
OutInfo.Format = EAudioMixerStreamDataFormat::Int16;
|
|
OutInfo.OutputChannelArray.SetNum(2);
|
|
OutInfo.OutputChannelArray[0] = EAudioMixerChannel::FrontLeft;
|
|
OutInfo.OutputChannelArray[1] = EAudioMixerChannel::FrontRight;
|
|
return true;
|
|
#else
|
|
// @todo Lumin: implement this function
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::GetDefaultOutputDeviceIndex(uint32& OutDefaultDeviceIndex) const
|
|
{
|
|
OutDefaultDeviceIndex = 0;
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::OpenAudioStream(const FAudioMixerOpenStreamParams& Params)
|
|
{
|
|
if (!bInitialized || AudioStreamInfo.StreamState != EAudioOutputStreamState::Closed)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OpenStreamParams = Params;
|
|
|
|
AudioStreamInfo.Reset();
|
|
|
|
AudioStreamInfo.OutputDeviceIndex = 0;
|
|
AudioStreamInfo.NumOutputFrames = OpenStreamParams.NumFrames;
|
|
AudioStreamInfo.NumBuffers = FMath::Max(OpenStreamParams.NumBuffers, 4);
|
|
AudioStreamInfo.AudioMixer = OpenStreamParams.AudioMixer;
|
|
|
|
if (!GetOutputDeviceInfo(AudioStreamInfo.OutputDeviceIndex, AudioStreamInfo.DeviceInfo))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AudioStreamInfo.DeviceInfo.SampleRate = OpenStreamParams.SampleRate;
|
|
|
|
SLresult Result;
|
|
|
|
FAudioPlatformSettings PlatformSettings = GetPlatformSettings();
|
|
|
|
// Set up circular buffer between our rendering buffer size and the device's buffer size.
|
|
// Since we are only using this circular buffer on a single thread, we do not need to add extra slack.
|
|
NumSamplesPerRenderCallback = OpenStreamParams.NumFrames * AudioStreamInfo.DeviceInfo.NumChannels;
|
|
NumSamplesPerDeviceCallback = PlatformSettings.CallbackBufferFrameSize * AudioStreamInfo.DeviceInfo.NumChannels;
|
|
const int32 MaxCircularBufferCapacity = FMath::Max<int32>(NumSamplesPerRenderCallback, NumSamplesPerDeviceCallback) * 2;
|
|
CircularOutputBuffer.SetCapacity(MaxCircularBufferCapacity);
|
|
|
|
DeviceBuffer.Reset();
|
|
DeviceBuffer.AddUninitialized(NumSamplesPerDeviceCallback);
|
|
|
|
// Data Info:
|
|
SLDataLocator_AndroidSimpleBufferQueue LocationBuffer = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1};
|
|
|
|
// PCM Info
|
|
SLDataFormat_PCM PCM_Format = {
|
|
SL_DATAFORMAT_PCM,
|
|
(SLuint32)AudioStreamInfo.DeviceInfo.NumChannels,
|
|
(SLuint32)(AudioStreamInfo.DeviceInfo.SampleRate * 1000), // NOTE: OpenSLES has sample rates specified in millihertz.
|
|
SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
SL_PCMSAMPLEFORMAT_FIXED_16,
|
|
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
|
SL_BYTEORDER_LITTLEENDIAN
|
|
};
|
|
|
|
SLDataSource SoundDataSource = { &LocationBuffer, &PCM_Format };
|
|
|
|
// configure audio sink
|
|
SLDataLocator_OutputMix OutputMix = { SL_DATALOCATOR_OUTPUTMIX, SL_OutputMixObject };
|
|
SLDataSink AudioSink = { &OutputMix, nullptr };
|
|
|
|
// create audio player
|
|
const SLInterfaceID InterfaceIds[] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
|
|
const SLboolean Req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
|
|
|
|
Result = (*SL_EngineEngine)->CreateAudioPlayer(SL_EngineEngine, &SL_PlayerObject, &SoundDataSource, &AudioSink, sizeof(InterfaceIds) / sizeof(SLInterfaceID), InterfaceIds, Req);
|
|
OPENSLES_RETURN_ON_FAIL(Result);
|
|
|
|
// realize the player
|
|
Result = (*SL_PlayerObject)->Realize(SL_PlayerObject, SL_BOOLEAN_FALSE);
|
|
OPENSLES_RETURN_ON_FAIL(Result);
|
|
|
|
// get the play interface
|
|
Result = (*SL_PlayerObject)->GetInterface(SL_PlayerObject, SL_IID_PLAY, &SL_PlayerPlayInterface);
|
|
OPENSLES_RETURN_ON_FAIL(Result);
|
|
|
|
// buffer system
|
|
Result = (*SL_PlayerObject)->GetInterface(SL_PlayerObject, SL_IID_BUFFERQUEUE, &SL_PlayerBufferQueue);
|
|
OPENSLES_RETURN_ON_FAIL(Result);
|
|
|
|
Result = (*SL_PlayerBufferQueue)->RegisterCallback(SL_PlayerBufferQueue, OpenSLBufferQueueCallback, (void*)this);
|
|
OPENSLES_RETURN_ON_FAIL(Result);
|
|
|
|
AudioStreamInfo.StreamState = EAudioOutputStreamState::Open;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::CloseAudioStream()
|
|
{
|
|
if (!bInitialized || (AudioStreamInfo.StreamState != EAudioOutputStreamState::Open && AudioStreamInfo.StreamState != EAudioOutputStreamState::Stopped))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SLresult Result =(*SL_PlayerBufferQueue)->RegisterCallback(SL_PlayerBufferQueue, nullptr, nullptr);
|
|
|
|
(*SL_PlayerObject)->Destroy(SL_PlayerObject);
|
|
|
|
SL_PlayerObject = nullptr;
|
|
SL_PlayerPlayInterface = nullptr;
|
|
SL_PlayerBufferQueue = nullptr;
|
|
|
|
AudioStreamInfo.StreamState = EAudioOutputStreamState::Closed;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::StartAudioStream()
|
|
{
|
|
BeginGeneratingAudio();
|
|
|
|
// set the player's state to playing
|
|
SLresult Result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_PLAYING);
|
|
OPENSLES_CHECK_ON_FAIL(Result);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::StopAudioStream()
|
|
{
|
|
if(!bInitialized || AudioStreamInfo.StreamState != EAudioOutputStreamState::Running)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (AudioStreamInfo.StreamState != EAudioOutputStreamState::Stopped)
|
|
{
|
|
// set the player's state to stopped
|
|
SLresult Result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_STOPPED);
|
|
OPENSLES_CHECK_ON_FAIL(Result);
|
|
|
|
if (AudioStreamInfo.StreamState == EAudioOutputStreamState::Running)
|
|
{
|
|
StopGeneratingAudio();
|
|
}
|
|
|
|
check(AudioStreamInfo.StreamState == EAudioOutputStreamState::Stopped);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FAudioPlatformDeviceInfo FMixerPlatformAndroid::GetPlatformDeviceInfo() const
|
|
{
|
|
return AudioStreamInfo.DeviceInfo;
|
|
}
|
|
|
|
FAudioPlatformSettings FMixerPlatformAndroid::GetPlatformSettings() const
|
|
{
|
|
#if WITH_ENGINE
|
|
FAudioPlatformSettings PlatformSettings = FAudioPlatformSettings::GetPlatformSettings(FPlatformProperties::GetRuntimeSettingsClassName());
|
|
#else
|
|
FAudioPlatformSettings PlatformSettings = FAudioPlatformSettings();
|
|
#endif // WITH_ENGINE
|
|
|
|
PlatformSettings.CallbackBufferFrameSize = GetDeviceBufferSize(PlatformSettings.CallbackBufferFrameSize);
|
|
return PlatformSettings;
|
|
}
|
|
|
|
void FMixerPlatformAndroid::SuspendContext()
|
|
{
|
|
FScopeLock ScopeLock(&SuspendedCriticalSection);
|
|
|
|
if (!bSuspended)
|
|
{
|
|
UE_LOG(LogAudioMixerAndroid, Display, TEXT("Suspending android audio renderer"));
|
|
|
|
// set the player's state to paused
|
|
SLresult result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_PAUSED);
|
|
check(SL_RESULT_SUCCESS == result);
|
|
|
|
bSuspended = true;
|
|
}
|
|
}
|
|
|
|
void FMixerPlatformAndroid::ResumeContext()
|
|
{
|
|
FScopeLock ScopeLock(&SuspendedCriticalSection);
|
|
|
|
// set the player's state to paused
|
|
if (bSuspended)
|
|
{
|
|
UE_LOG(LogAudioMixerAndroid, Display, TEXT("Resuming android audio renderer"));
|
|
|
|
SLresult result = (*SL_PlayerPlayInterface)->SetPlayState(SL_PlayerPlayInterface, SL_PLAYSTATE_PLAYING);
|
|
check(SL_RESULT_SUCCESS == result);
|
|
|
|
bSuspended = false;
|
|
}
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::SupportsRealtimeDecompression() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FMixerPlatformAndroid::SubmitBuffer(const uint8* Buffer)
|
|
{
|
|
check(DeviceBuffer.Num() == NumSamplesPerDeviceCallback);
|
|
|
|
int32 PushResult = CircularOutputBuffer.Push((const int16*)Buffer, NumSamplesPerRenderCallback);
|
|
check(PushResult == NumSamplesPerRenderCallback)
|
|
|
|
while (CircularOutputBuffer.Num() >= NumSamplesPerDeviceCallback)
|
|
{
|
|
int32 PopResult = CircularOutputBuffer.Pop(DeviceBuffer.GetData(), NumSamplesPerDeviceCallback);
|
|
check(PopResult == NumSamplesPerDeviceCallback);
|
|
|
|
const auto BufferSize = NumSamplesPerDeviceCallback * sizeof(int16);
|
|
SLresult Result = (*SL_PlayerBufferQueue)->Enqueue(SL_PlayerBufferQueue, Buffer, BufferSize);
|
|
OPENSLES_LOG_ON_FAIL(Result);
|
|
}
|
|
}
|
|
|
|
FName FMixerPlatformAndroid::GetRuntimeFormat(USoundWave* InSoundWave)
|
|
{
|
|
#if WITH_ENGINE
|
|
static FName NAME_ADPCM(TEXT("ADPCM"));
|
|
static const FName NAME_BINKA(TEXT("BINKA"));
|
|
|
|
if (InSoundWave->bUseBinkAudio)
|
|
{
|
|
return NAME_BINKA;
|
|
}
|
|
|
|
if (InSoundWave->IsSeekableStreaming())
|
|
{
|
|
return NAME_ADPCM;
|
|
}
|
|
|
|
#if WITH_OGGVORBIS
|
|
static FName NAME_OGG(TEXT("OGG"));
|
|
if (InSoundWave->HasCompressedData(NAME_OGG))
|
|
{
|
|
return NAME_OGG;
|
|
}
|
|
#endif // WITH_OGGVORBIS
|
|
return NAME_ADPCM;
|
|
#else
|
|
return FName(TEXT("None"));
|
|
#endif //WITH_ENGINE
|
|
}
|
|
|
|
bool FMixerPlatformAndroid::HasCompressedAudioInfoClass(USoundWave* InSoundWave)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
ICompressedAudioInfo* FMixerPlatformAndroid::CreateCompressedAudioInfo(USoundWave* InSoundWave)
|
|
{
|
|
#if WITH_ENGINE
|
|
if (InSoundWave->bUseBinkAudio)
|
|
{
|
|
return new FBinkAudioInfo();
|
|
}
|
|
|
|
if (InSoundWave->IsSeekableStreaming())
|
|
{
|
|
return new FADPCMAudioInfo();
|
|
}
|
|
|
|
return new FVorbisAudioInfo();
|
|
#else
|
|
return nullptr;
|
|
#endif // WITH_ENGINE
|
|
}
|
|
|
|
ICompressedAudioInfo* FMixerPlatformAndroid::CreateCompressedAudioInfo(const FSoundWaveProxyPtr& InSoundWave)
|
|
{
|
|
if (!ensure(InSoundWave.IsValid() && InSoundWave->IsStreaming()))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
if (InSoundWave->UseBinkAudio())
|
|
{
|
|
return new FBinkAudioInfo();
|
|
}
|
|
|
|
if (InSoundWave->IsSeekableStreaming())
|
|
{
|
|
return new FADPCMAudioInfo();
|
|
}
|
|
|
|
return new FVorbisAudioInfo();
|
|
#endif // WITH_ENGINE
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FString FMixerPlatformAndroid::GetDefaultDeviceName()
|
|
{
|
|
return FString();
|
|
}
|
|
|
|
void FMixerPlatformAndroid::OpenSLBufferQueueCallback(SLAndroidSimpleBufferQueueItf InQueueInterface, void* pContext)
|
|
{
|
|
FMixerPlatformAndroid* MixerPlatformAndroid = (FMixerPlatformAndroid*)pContext;
|
|
if (MixerPlatformAndroid != nullptr)
|
|
{
|
|
MixerPlatformAndroid->ReadNextBuffer();
|
|
}
|
|
}
|
|
}
|