// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= IOSAudioDevice.cpp: Unreal IOSAudio audio interface object. =============================================================================*/ /*------------------------------------------------------------------------------------ Includes ------------------------------------------------------------------------------------*/ #include "IOSAudioDevice.h" #include "AudioEffect.h" #include "AudioPluginUtilities.h" #include "AudioDecompress.h" DEFINE_LOG_CATEGORY(LogIOSAudio); class FIOSAudioDeviceModule : public IAudioDeviceModule { public: /** Creates a new instance of the audio device implemented by the module. */ virtual FAudioDevice* CreateAudioDevice() override { return new FIOSAudioDevice; } }; IMPLEMENT_MODULE(FIOSAudioDeviceModule, IOSAudio); /*------------------------------------------------------------------------------------ FIOSAudioDevice ------------------------------------------------------------------------------------*/ static int32 SuspendCounter = 0; void FIOSAudioDevice::ResumeContext() { if (SuspendCounter > 0) { FPlatformAtomics::InterlockedDecrement(&SuspendCounter); if (AudioUnitGraph != NULL) { AUGraphStart(AudioUnitGraph); } if (OutputUnit != NULL) { AudioOutputUnitStart(OutputUnit); } UE_LOG(LogIOSAudio, Display, TEXT("Resuming Audio")); } } void FIOSAudioDevice::SuspendContext() { if (SuspendCounter == 0) { FPlatformAtomics::InterlockedIncrement(&SuspendCounter); if (OutputUnit != NULL) { AudioOutputUnitStop(OutputUnit); } if (AudioUnitGraph != NULL) { AUGraphStop(AudioUnitGraph); } UE_LOG(LogIOSAudio, Display, TEXT("Suspending Audio")); } } void FIOSAudioDevice::IncrementSuspendCounter() { if(SuspendCounter == 0) { FPlatformAtomics::InterlockedIncrement(&SuspendCounter); } } void FIOSAudioDevice::DecrementSuspendCounter() { if (SuspendCounter > 0) { FPlatformAtomics::InterlockedDecrement(&SuspendCounter); } } void FIOSAudioDevice::UpdateDeviceDeltaTime() { DeviceDeltaTime = GetGameDeltaTime(); } FIOSAudioDevice::FIOSAudioDevice() : FAudioDevice(), AudioUnitGraph(NULL), OutputNode(0), OutputUnit(NULL), MixerNode(0), MixerUnit(NULL), NextBusNumber(0) { bDisableAudioCaching = true; // Only use DTYPE_RealTime or DTYPE_Streaming since on the fly decompression is so cheap, it saves memory, and requires fewer code paths } bool FIOSAudioDevice::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) { if (FParse::Command(&Cmd, TEXT("DumpAUGraph")) && AudioUnitGraph) { CAShow(AudioUnitGraph); return true; } return FAudioDevice::Exec(InWorld, Cmd, Ar); } FAudioPlatformSettings FIOSAudioDevice::GetPlatformSettings() const { return FAudioPlatformSettings::GetPlatformSettings(FPlatformProperties::GetRuntimeSettingsClassName()); } bool FIOSAudioDevice::InitializeHardware() { UE_LOG(LogIOSAudio, Warning, TEXT("Initializing legacy audio backend for iOS")); SIZE_T SampleSize = sizeof(AudioSampleType); double GraphSampleRate = 44100.0; if (!SetHardwareSampleRate(GraphSampleRate) || !SetAudioSessionActive(true)) { HandleError(TEXT("Failed to establish the audio session!")); return false; } // Retrieve the actual hardware sample rate GetHardwareSampleRate(GraphSampleRate); // Linear PCM stream format MixerFormat.mFormatID = kAudioFormatLinearPCM; MixerFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked; MixerFormat.mBytesPerPacket = SampleSize; MixerFormat.mFramesPerPacket = 1; MixerFormat.mBytesPerFrame = SampleSize; MixerFormat.mChannelsPerFrame = 1; MixerFormat.mBitsPerChannel = 8 * SampleSize; MixerFormat.mSampleRate = GraphSampleRate; OSStatus Status = NewAUGraph(&AudioUnitGraph); if (Status != noErr) { HandleError(TEXT("Failed to create audio unit graph!")); return false; } AudioComponentDescription UnitDescription; // Setup audio output unit UnitDescription.componentType = kAudioUnitType_Output; UnitDescription.componentSubType = kAudioUnitSubType_RemoteIO; UnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple; UnitDescription.componentFlags = 0; UnitDescription.componentFlagsMask = 0; Status = AUGraphAddNode(AudioUnitGraph, &UnitDescription, &OutputNode); if (Status != noErr) { HandleError(TEXT("Failed to initialize audio output node!"), true); return false; } // Setup audo mixer unit UnitDescription.componentType = kAudioUnitType_Mixer; UnitDescription.componentSubType = kAudioUnitSubType_SpatialMixer; UnitDescription.componentManufacturer = kAudioUnitManufacturer_Apple; UnitDescription.componentFlags = 0; UnitDescription.componentFlagsMask = 0; Status = AUGraphAddNode(AudioUnitGraph, &UnitDescription, &MixerNode); if (Status != noErr) { HandleError(TEXT("Failed to initialize audio mixer node!"), true); return false; } Status = AUGraphOpen(AudioUnitGraph); if (Status != noErr) { HandleError(TEXT("Failed to open audio unit graph"), true); return false; } Status = AUGraphNodeInfo(AudioUnitGraph, OutputNode, NULL, &OutputUnit); if (Status != noErr) { HandleError(TEXT("Failed to retrieve output unit reference!"), true); OutputUnit = NULL; return false; } Status = AUGraphNodeInfo(AudioUnitGraph, MixerNode, NULL, &MixerUnit); if (Status != noErr) { HandleError(TEXT("Failed to retrieve mixer unit reference!"), true); MixerUnit = NULL; return false; } uint32 BusCount = GetMaxSources() * CHANNELS_PER_BUS; Status = AudioUnitSetProperty(MixerUnit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Input, 0, &BusCount, sizeof(BusCount)); if (Status != noErr) { HandleError(TEXT("Failed to set kAudioUnitProperty_ElementCount for audio mixer unit!"), true); return false; } // Initialize sound source early on, allowing for render callback hookups InitSoundSources(); // Setup the mixer unit sample rate Status = AudioUnitSetProperty(MixerUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &GraphSampleRate, sizeof(GraphSampleRate)); if (Status != noErr) { HandleError(TEXT("Failed to set kAudioUnitProperty_SampleRate for audio mixer unit!"), true); return false; } // Connect mixer node output to output node input Status = AUGraphConnectNodeInput(AudioUnitGraph, MixerNode, 0, OutputNode, 0); if (Status != noErr) { HandleError(TEXT("Failed to connect mixer node to output node!"), true); return false; } // Initialize and start the audio unit graph Status = AUGraphInitialize(AudioUnitGraph); if (Status == noErr && SuspendCounter == 0) { Status = AUGraphStart(AudioUnitGraph); } if (Status != noErr) { HandleError(TEXT("Failed to start audio graph!"), true); return false; } return true; } void FIOSAudioDevice::TeardownHardware() { if (AudioUnitGraph) { AUGraphStop(AudioUnitGraph); DisposeAUGraph(AudioUnitGraph); AudioUnitGraph = NULL; OutputNode = -1; OutputUnit = NULL; MixerNode = -1; MixerUnit = NULL; } } void FIOSAudioDevice::UpdateHardware() { const FListener& Listener = GetListeners()[0]; PlayerLocation = Listener.Transform.GetLocation(); PlayerFacing = Listener.GetFront(); PlayerUp = Listener.GetUp(); PlayerRight = Listener.GetRight(); } void FIOSAudioDevice::HandleError(const TCHAR* InLogOutput, bool bTeardown) { UE_LOG(LogIOSAudio, Log, TEXT("%s"), InLogOutput); if (bTeardown) { Teardown(); } } FAudioEffectsManager* FIOSAudioDevice::CreateEffectsManager() { // Create the basic no-op effects manager return FAudioDevice::CreateEffectsManager(); } FSoundSource* FIOSAudioDevice::CreateSoundSource() { return new FIOSAudioSoundSource(this, NextBusNumber++); }