2019-12-26 14:45:42 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-08-26 18:35:22 -04:00
# include "AudioCaptureAudioUnit.h"
2024-05-13 04:00:53 -04:00
# include "AudioCaptureCoreLog.h"
# include <AVFoundation/AVAudioSession.h>
2019-08-26 18:35:22 -04:00
2020-04-20 12:56:15 -04:00
const int32 kInputBus = 1 ;
const int32 kOutputBus = 0 ;
2024-05-13 04:00:53 -04:00
const int32 kRemoteIODeviceIndex = 0 ;
const int32 kVoiceProcessingIODeviceIndex = 1 ;
2019-08-26 18:35:22 -04:00
Audio : : FAudioCaptureAudioUnitStream : : FAudioCaptureAudioUnitStream ( )
: NumChannels ( 0 )
, SampleRate ( 0 )
2021-12-13 18:34:32 -05:00
, bIsStreamOpen ( false )
2021-12-13 21:34:59 -05:00
, bHasCaptureStarted ( false )
2024-05-13 04:00:53 -04:00
, bIsHardwareVoiceProcessingSupported ( false )
2019-08-26 18:35:22 -04:00
{
}
static OSStatus RecordingCallback ( void * inRefCon ,
AudioUnitRenderActionFlags * ioActionFlags ,
const AudioTimeStamp * inTimeStamp ,
UInt32 inBusNumber ,
UInt32 inNumberFrames ,
AudioBufferList * ioData )
{
Audio : : FAudioCaptureAudioUnitStream * AudioCapture = ( Audio : : FAudioCaptureAudioUnitStream * ) inRefCon ;
2020-04-20 12:56:15 -04:00
return AudioCapture - > OnCaptureCallback ( ioActionFlags , inTimeStamp , inBusNumber , inNumberFrames , ioData ) ;
}
2019-08-26 18:35:22 -04:00
2020-04-20 12:56:15 -04:00
OSStatus Audio : : FAudioCaptureAudioUnitStream : : OnCaptureCallback ( AudioUnitRenderActionFlags * ioActionFlags , const AudioTimeStamp * inTimeStamp , UInt32 inBusNumber , UInt32 inNumberFrames , AudioBufferList * ioData )
{
OSStatus status = noErr ;
const int NeededBufferSize = inNumberFrames * NumChannels * sizeof ( float ) ;
if ( CaptureBuffer . Num ( ) = = 0 | | BufferSize < NeededBufferSize )
{
BufferSize = NeededBufferSize ;
AllocateBuffer ( BufferSize ) ;
}
AudioBufferList * BufferList = ( AudioBufferList * ) CaptureBuffer . GetData ( ) ;
for ( int i = 0 ; i < BufferList - > mNumberBuffers ; + + i ) {
BufferList - > mBuffers [ i ] . mDataByteSize = ( UInt32 ) BufferSize ;
}
status = AudioUnitRender ( IOUnit ,
2019-08-26 18:35:22 -04:00
ioActionFlags ,
inTimeStamp ,
inBusNumber ,
inNumberFrames ,
2020-04-20 12:56:15 -04:00
BufferList ) ;
2019-08-26 18:35:22 -04:00
check ( status = = noErr ) ;
2020-04-20 12:56:15 -04:00
void * InBuffer = ( void * ) BufferList - > mBuffers [ 0 ] . mData ; // only first channel ?!
OnAudioCapture ( InBuffer , inNumberFrames , 0.0 , false ) ; // need calculate timestamp
2019-08-26 18:35:22 -04:00
return noErr ;
}
2020-04-20 12:56:15 -04:00
void Audio : : FAudioCaptureAudioUnitStream : : AllocateBuffer ( int SizeInBytes )
{
size_t NeedBytes = sizeof ( AudioBufferList ) + NumChannels * ( sizeof ( AudioBuffer ) + SizeInBytes ) ;
CaptureBuffer . SetNum ( NeedBytes ) ;
AudioBufferList * list = ( AudioBufferList * ) CaptureBuffer . GetData ( ) ;
uint8 * data = CaptureBuffer . GetData ( ) + sizeof ( AudioBufferList ) + sizeof ( AudioBuffer ) ;
list - > mNumberBuffers = NumChannels ;
for ( int i = 0 ; i < NumChannels ; i + + )
{
list - > mBuffers [ i ] . mNumberChannels = 1 ;
list - > mBuffers [ i ] . mDataByteSize = ( UInt32 ) SizeInBytes ;
list - > mBuffers [ i ] . mData = data ;
data + = ( SizeInBytes + sizeof ( AudioBuffer ) ) ;
}
}
2019-08-26 18:35:22 -04:00
bool Audio : : FAudioCaptureAudioUnitStream : : GetCaptureDeviceInfo ( FCaptureDeviceInfo & OutInfo , int32 DeviceIndex )
{
2024-05-13 04:00:53 -04:00
switch ( DeviceIndex )
{
case Audio : : DefaultDeviceIndex :
case kRemoteIODeviceIndex :
{
OutInfo . DeviceName = FString ( TEXT ( " Remote IO Audio Component " ) ) ;
2024-05-27 05:27:17 -04:00
OutInfo . DeviceId = OutInfo . DeviceName ;
2024-05-13 04:00:53 -04:00
OutInfo . InputChannels = 1 ;
OutInfo . PreferredSampleRate = 48000 ;
OutInfo . bSupportsHardwareAEC = false ;
return true ;
}
case kVoiceProcessingIODeviceIndex :
{
OutInfo . DeviceName = FString ( TEXT ( " VoiceProcesing IO Audio Component " ) ) ;
2024-05-27 05:27:17 -04:00
OutInfo . DeviceId = OutInfo . DeviceName ;
2024-05-13 04:00:53 -04:00
OutInfo . InputChannels = 1 ;
OutInfo . PreferredSampleRate = 48000 ;
OutInfo . bSupportsHardwareAEC = true ;
return true ;
}
default :
{
return false ;
}
}
2019-08-26 18:35:22 -04:00
}
2023-03-22 19:38:36 -04:00
bool Audio : : FAudioCaptureAudioUnitStream : : OpenAudioCaptureStream ( const FAudioCaptureDeviceParams & InParams , FOnAudioCaptureFunction InOnCapture , uint32 NumFramesDesired )
2019-08-26 18:35:22 -04:00
{
2024-05-13 04:00:53 -04:00
switch ( InParams . DeviceIndex )
{
case Audio : : DefaultDeviceIndex :
case kRemoteIODeviceIndex :
{
bIsHardwareVoiceProcessingSupported = false ;
UE_CLOG ( InParams . bUseHardwareAEC , LogAudioCaptureCore , Warning , TEXT ( " Hardware support is only available for VoiceProcessing IO Audio Component (DeviceIndex = %d) " ) , kVoiceProcessingIODeviceIndex ) ;
break ;
}
case kVoiceProcessingIODeviceIndex :
{
bIsHardwareVoiceProcessingSupported = true ;
break ;
}
default :
{
return false ;
}
}
2020-04-20 12:56:15 -04:00
NumChannels = 1 ;
SampleRate = 48000 ;
OnCapture = MoveTemp ( InOnCapture ) ;
2024-05-13 04:00:53 -04:00
OSStatus Status = noErr ;
2020-04-20 12:56:15 -04:00
// Source of info "Technical Note TN2091 - Device input using the HAL Output Audio Unit"
2019-08-26 18:35:22 -04:00
AudioComponentDescription desc ;
desc . componentType = kAudioUnitType_Output ;
2024-05-13 04:00:53 -04:00
desc . componentSubType = bIsHardwareVoiceProcessingSupported ? kAudioUnitSubType_VoiceProcessingIO : kAudioUnitSubType_RemoteIO ;
2019-08-26 18:35:22 -04:00
desc . componentManufacturer = kAudioUnitManufacturer_Apple ;
desc . componentFlags = 0 ;
desc . componentFlagsMask = 0 ;
2024-05-13 04:00:53 -04:00
// Using VoiceProcessing IO may change the AVAudioSession mode to AVAudioSessionModeVoiceChat. Cache AVAudioSession settings and restore them after initialisation
NSString * Mode = [ [ AVAudioSession sharedInstance ] mode ] ;
NSString * Category = [ [ AVAudioSession sharedInstance ] category ] ;
AVAudioSessionCategoryOptions Options = [ [ AVAudioSession sharedInstance ] categoryOptions ] ;
2019-08-26 18:35:22 -04:00
AudioComponent InputComponent = AudioComponentFindNext ( NULL , & desc ) ;
2020-04-20 12:56:15 -04:00
Status = AudioComponentInstanceNew ( InputComponent , & IOUnit ) ;
check ( Status = = noErr ) ;
2019-08-26 18:35:22 -04:00
2020-04-20 12:56:15 -04:00
// Enable recording
uint32 EnableIO = 1 ;
Status = AudioUnitSetProperty ( IOUnit ,
2019-08-26 18:35:22 -04:00
kAudioOutputUnitProperty_EnableIO ,
kAudioUnitScope_Input ,
2020-04-20 12:56:15 -04:00
kInputBus ,
& EnableIO ,
sizeof ( EnableIO ) ) ;
check ( Status = = noErr ) ;
2019-08-26 18:35:22 -04:00
2020-04-20 12:56:15 -04:00
// Disable output part
EnableIO = 0 ;
Status = AudioUnitSetProperty ( IOUnit ,
2019-08-26 18:35:22 -04:00
kAudioOutputUnitProperty_EnableIO ,
kAudioUnitScope_Output ,
2020-04-20 12:56:15 -04:00
kOutputBus ,
& EnableIO ,
sizeof ( EnableIO ) ) ;
check ( Status = = noErr ) ;
2019-08-26 18:35:22 -04:00
2020-04-20 12:56:15 -04:00
AudioStreamBasicDescription StreamDescription = { 0 } ;
const UInt32 BytesPerSample = sizeof ( Float32 ) ;
StreamDescription . mSampleRate = SampleRate ;
StreamDescription . mFormatID = kAudioFormatLinearPCM ;
StreamDescription . mFormatFlags = kAudioFormatFlagsNativeFloatPacked ;
StreamDescription . mChannelsPerFrame = NumChannels ;
StreamDescription . mBitsPerChannel = 8 * BytesPerSample ;
StreamDescription . mBytesPerFrame = BytesPerSample * StreamDescription . mChannelsPerFrame ;
StreamDescription . mFramesPerPacket = 1 ;
StreamDescription . mBytesPerPacket = StreamDescription . mFramesPerPacket * StreamDescription . mBytesPerFrame ;
// Configure output format
Status = AudioUnitSetProperty ( IOUnit ,
2019-08-26 18:35:22 -04:00
kAudioUnitProperty_StreamFormat ,
kAudioUnitScope_Output ,
2020-04-20 12:56:15 -04:00
kInputBus ,
2019-08-26 18:35:22 -04:00
& StreamDescription ,
sizeof ( StreamDescription ) ) ;
2020-04-20 12:56:15 -04:00
check ( Status = = noErr ) ;
2019-08-26 18:35:22 -04:00
2020-04-20 12:56:15 -04:00
// Setup capture callback
2019-08-26 18:35:22 -04:00
AURenderCallbackStruct CallbackInfo ;
CallbackInfo . inputProc = RecordingCallback ;
CallbackInfo . inputProcRefCon = this ;
2020-04-20 12:56:15 -04:00
Status = AudioUnitSetProperty ( IOUnit ,
2019-08-26 18:35:22 -04:00
kAudioOutputUnitProperty_SetInputCallback ,
kAudioUnitScope_Global ,
2020-04-20 12:56:15 -04:00
kInputBus ,
2019-08-26 18:35:22 -04:00
& CallbackInfo ,
sizeof ( CallbackInfo ) ) ;
2020-04-20 12:56:15 -04:00
check ( Status = = noErr ) ;
2024-05-13 04:00:53 -04:00
// Configure unit processing
if ( bIsHardwareVoiceProcessingSupported )
{
SetHardwareFeatureEnabled ( Audio : : EHardwareInputFeature : : EchoCancellation , InParams . bUseHardwareAEC ) ;
SetHardwareFeatureEnabled ( Audio : : EHardwareInputFeature : : AutomaticGainControl , InParams . bUseHardwareAEC ) ;
}
2020-04-20 12:56:15 -04:00
// Initialize audio unit
Status = AudioUnitInitialize ( IOUnit ) ;
check ( Status = = noErr ) ;
2019-08-26 18:35:22 -04:00
2021-12-13 18:34:32 -05:00
bIsStreamOpen = ( Status = = noErr ) ;
2024-05-13 04:00:53 -04:00
if ( [ [ [ AVAudioSession sharedInstance ] category ] compare : Category ] ! = NSOrderedSame | |
[ [ [ AVAudioSession sharedInstance ] mode ] compare : Mode ] ! = NSOrderedSame | |
[ [ AVAudioSession sharedInstance ] categoryOptions ] ! = Options )
{
NSError * ActiveError = nil ;
[ [ AVAudioSession sharedInstance ] setCategory : Category mode : Mode options : Options error : & ActiveError ] ;
}
2021-12-13 18:34:32 -05:00
return bIsStreamOpen ;
2019-08-26 18:35:22 -04:00
}
bool Audio : : FAudioCaptureAudioUnitStream : : CloseStream ( )
{
StopStream ( ) ;
2020-04-20 12:56:15 -04:00
AudioComponentInstanceDispose ( IOUnit ) ;
2021-12-13 18:34:32 -05:00
bIsStreamOpen = false ;
2019-08-26 18:35:22 -04:00
return true ;
}
bool Audio : : FAudioCaptureAudioUnitStream : : StartStream ( )
{
2021-12-13 18:34:32 -05:00
bHasCaptureStarted = ( AudioOutputUnitStart ( IOUnit ) = = noErr ) ;
return bHasCaptureStarted ;
2019-08-26 18:35:22 -04:00
}
bool Audio : : FAudioCaptureAudioUnitStream : : StopStream ( )
{
2021-12-13 18:34:32 -05:00
bHasCaptureStarted = false ;
2020-04-20 12:56:15 -04:00
return ( AudioOutputUnitStop ( IOUnit ) = = noErr ) ;
2019-08-26 18:35:22 -04:00
}
bool Audio : : FAudioCaptureAudioUnitStream : : AbortStream ( )
{
StopStream ( ) ;
CloseStream ( ) ;
return true ;
}
bool Audio : : FAudioCaptureAudioUnitStream : : GetStreamTime ( double & OutStreamTime )
{
OutStreamTime = 0.0f ;
return true ;
}
bool Audio : : FAudioCaptureAudioUnitStream : : IsStreamOpen ( ) const
{
2021-12-13 18:34:32 -05:00
return bIsStreamOpen ;
2019-08-26 18:35:22 -04:00
}
bool Audio : : FAudioCaptureAudioUnitStream : : IsCapturing ( ) const
{
2021-12-13 18:34:32 -05:00
return bHasCaptureStarted ;
2019-08-26 18:35:22 -04:00
}
void Audio : : FAudioCaptureAudioUnitStream : : OnAudioCapture ( void * InBuffer , uint32 InBufferFrames , double StreamTime , bool bOverflow )
{
2023-03-22 19:38:36 -04:00
OnCapture ( InBuffer , InBufferFrames , NumChannels , SampleRate , StreamTime , bOverflow ) ;
2019-08-26 18:35:22 -04:00
}
bool Audio : : FAudioCaptureAudioUnitStream : : GetInputDevicesAvailable ( TArray < FCaptureDeviceInfo > & OutDevices )
{
OutDevices . Reset ( ) ;
2024-05-13 04:00:53 -04:00
GetCaptureDeviceInfo ( OutDevices . AddDefaulted_GetRef ( ) , kRemoteIODeviceIndex ) ;
GetCaptureDeviceInfo ( OutDevices . AddDefaulted_GetRef ( ) , kVoiceProcessingIODeviceIndex ) ;
2019-08-26 18:35:22 -04:00
return true ;
}
2020-04-20 12:56:15 -04:00
void Audio : : FAudioCaptureAudioUnitStream : : SetHardwareFeatureEnabled ( EHardwareInputFeature FeatureType , bool bEnabled )
{
2024-05-13 04:00:53 -04:00
if ( IOUnit = = nil | | ! bIsHardwareVoiceProcessingSupported )
2020-04-20 12:56:15 -04:00
{
2024-05-13 04:00:53 -04:00
UE_CLOG ( ! bIsHardwareVoiceProcessingSupported , LogAudioCaptureCore , Warning , TEXT ( " Hardware support is only available for VoiceProcessing IO Audio Component (DeviceIndex = %d) " ) , kVoiceProcessingIODeviceIndex ) ;
2020-04-20 12:56:15 -04:00
return ;
}
// we ignore result those functions because sometime we can't set this parameters
OSStatus status = noErr ;
switch ( FeatureType )
{
case Audio : : EHardwareInputFeature : : EchoCancellation :
{
const UInt32 EnableParam = ( bEnabled ? 0 : 1 ) ;
status = AudioUnitSetProperty ( IOUnit ,
kAUVoiceIOProperty_BypassVoiceProcessing ,
kAudioUnitScope_Global ,
kInputBus ,
& EnableParam ,
sizeof ( EnableParam )
) ;
}
break ;
case Audio : : EHardwareInputFeature : : AutomaticGainControl :
{
const UInt32 EnableParam = ( bEnabled ? 1 : 0 ) ;
status = AudioUnitSetProperty ( IOUnit ,
kAUVoiceIOProperty_VoiceProcessingEnableAGC ,
kAudioUnitScope_Global ,
kInputBus ,
& EnableParam ,
sizeof ( EnableParam )
) ;
}
break ;
}
}