Files
UnrealEngineUWP/Engine/Source/Runtime/Android/AudioMixerAndroid/Private/AudioMixerPlatformAndroid.cpp
dan thompson ab80c69fd3 Bink Audio :: 4/4 :: Integration :: #pf 60aea2116905a6000136901e #pf 60c9308ef7cffb0001674132
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]
2021-06-15 20:30:02 -04:00

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();
}
}
}