Last active
September 24, 2022 10:27
-
-
Save jamiebullock/10829962 to your computer and use it in GitHub Desktop.
Set up RemoteIO or HALOutput Audio Unit for recording
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR | |
#define INPUT_BYTES_PER_SAMPLE sizeof(SInt16) | |
#define AU_IO_COMPONENT kAudioUnitSubType_RemoteIO | |
#define AU_DEFAULT_COMPONENT AU_IO_COMPONENT | |
#elif TARGET_OS_MAC | |
#define INPUT_BYTES_PER_SAMPLE sizeof(Float32) | |
#define AU_IO_COMPONENT kAudioUnitSubType_HALOutput | |
#define AU_DEFAULT_COMPONENT kAudioUnitSubType_DefaultOutput | |
#endif | |
AS_ErrorCode as_InputAudioUnit_Create (AS_AudioRecorder *audioRecorder) | |
{ | |
AudioComponentDescription inputcd = {0}; | |
inputcd.componentType = kAudioUnitType_Output; | |
inputcd.componentSubType = AU_IO_COMPONENT; | |
inputcd.componentManufacturer = kAudioUnitManufacturer_Apple; | |
AudioComponent audioComponent = AudioComponentFindNext(NULL, &inputcd); | |
if (audioComponent == NULL) | |
{ | |
AS_LOG(AS_LogType_Error, "Error: can't get audio component"); | |
assert(false); | |
return AS_ErrorCode_Error; | |
} | |
assert_noErr(AudioComponentInstanceNew(audioComponent, &audioRecorder->inputUnit)); | |
UInt32 disableFlag = 0; | |
UInt32 enableFlag = 1; | |
OSStatus status = noErr; | |
status = AudioUnitSetProperty( | |
audioRecorder->inputUnit, | |
kAudioOutputUnitProperty_EnableIO, | |
kAudioUnitScope_Input, | |
INPUT_BUS_ID, | |
&enableFlag, | |
sizeof(enableFlag) | |
); | |
assert_noErr(status); | |
status = AudioUnitSetProperty( | |
audioRecorder->inputUnit, | |
kAudioOutputUnitProperty_EnableIO, | |
kAudioUnitScope_Output, | |
OUTPUT_BUS_ID, | |
&disableFlag, | |
sizeof(enableFlag) | |
); | |
assert_noErr(status); | |
UInt32 propertySize = 0; | |
#if !TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR | |
AudioDeviceID defaultDevice = kAudioObjectUnknown; | |
propertySize = sizeof(defaultDevice); | |
AudioObjectPropertyAddress defaultDeviceProperty; | |
defaultDeviceProperty.mSelector = kAudioHardwarePropertyDefaultInputDevice; | |
defaultDeviceProperty.mScope = kAudioObjectPropertyScopeGlobal; | |
defaultDeviceProperty.mElement = kAudioObjectPropertyElementMaster; | |
status = AudioObjectGetPropertyData( | |
kAudioObjectSystemObject, | |
&defaultDeviceProperty, | |
0, | |
NULL, | |
&propertySize, | |
&defaultDevice | |
); | |
assert_noErr(status); | |
status = AudioUnitSetProperty( | |
audioRecorder->inputUnit, | |
kAudioOutputUnitProperty_CurrentDevice, | |
kAudioUnitScope_Global, | |
OUTPUT_BUS_ID, | |
&defaultDevice, | |
sizeof(defaultDevice) | |
); | |
assert_noErr(status); | |
#endif | |
propertySize = sizeof(AudioStreamBasicDescription); | |
status = AudioUnitGetProperty( | |
audioRecorder->inputUnit, | |
kAudioUnitProperty_StreamFormat, | |
kAudioUnitScope_Output, | |
INPUT_BUS_ID, | |
&audioRecorder->streamFormat, | |
&propertySize | |
), | |
assert_noErr(status); | |
AudioStreamBasicDescription deviceFormat; | |
status = AudioUnitGetProperty( | |
audioRecorder->inputUnit, | |
kAudioUnitProperty_StreamFormat, | |
kAudioUnitScope_Input, | |
INPUT_BUS_ID, | |
&deviceFormat, | |
&propertySize | |
); | |
assert_noErr(status); | |
audioRecorder->streamFormat.mSampleRate = deviceFormat.mSampleRate; | |
propertySize = sizeof (AudioStreamBasicDescription); | |
status = AudioUnitSetProperty( | |
audioRecorder->inputUnit, | |
kAudioUnitProperty_StreamFormat, | |
kAudioUnitScope_Output, | |
INPUT_BUS_ID, | |
&audioRecorder->streamFormat, | |
propertySize | |
); | |
assert_noErr(status); | |
Float64 sampleRate = 0.f; | |
propertySize = sizeof(sampleRate); | |
#if TARGET_IPHONE_SIMULATOR | |
sampleRate = AS_SAMPLERATE; | |
#elif TARGET_OS_IPHONE | |
status = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, | |
&propertySize, | |
&sampleRate); | |
assert_noErr(status); | |
#elif TARGET_OS_MAC | |
AudioObjectPropertyAddress propertyAddressSampleRate = { | |
kAudioDevicePropertyNominalSampleRate, | |
kAudioObjectPropertyScopeInput, | |
0 | |
}; | |
propertySize = sizeof(sampleRate); | |
status = AudioObjectGetPropertyData(defaultDevice, | |
&propertyAddressSampleRate, | |
0, | |
NULL, | |
&propertySize, | |
&sampleRate); | |
assert_noErr(status); | |
#endif | |
if (sampleRate != AS_SAMPLERATE) | |
{ | |
AS_LOG(AS_LogType_Error, "Samplerate mismatch. Device samplerate was %f but audio unit samplerate was set to %d", | |
sampleRate, AS_SAMPLERATE); | |
sampleRate = AS_SAMPLERATE; | |
AS_LOG(AS_LogType_Warning, "Attempting to force samplerate to: %f", sampleRate); | |
#if TARGET_OS_IPHONE | |
status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate, | |
propertySize, | |
&sampleRate); | |
assert_noErr(status); | |
AudioSessionSetActive(true); | |
status = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate, | |
&propertySize, | |
&sampleRate); | |
#elif TARGET_OS_MAC | |
status = AudioObjectSetPropertyData(defaultDevice, | |
&propertyAddressSampleRate, | |
0, | |
NULL, | |
propertySize, | |
&sampleRate); | |
#endif | |
assert_noErr(status); | |
if (sampleRate != AS_SAMPLERATE) | |
{ | |
AS_LOG(AS_LogType_Error, "failed to set samplerate\n"); | |
assert(false); | |
} | |
} | |
UInt32 bufferSizeFrames = 0; | |
#if TARGET_IPHONE_SIMULATOR | |
bufferSizeFrames = 512; | |
#elif TARGET_OS_IPHONE | |
Float32 preferredBufferSize = 512.f / sampleRate; // in seconds | |
status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration, | |
sizeof(preferredBufferSize), | |
&preferredBufferSize); | |
assert_noErr(status); | |
Float32 bufferDuration = 0; | |
propertySize = sizeof(Float32); | |
status = AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration, | |
&propertySize, | |
&bufferDuration); | |
assert_noErr(status); | |
bufferSizeFrames = bufferDuration * sampleRate; | |
bufferSizeFrames = next_power_of_two(bufferSizeFrames); | |
#elif TARGET_OS_MAC | |
propertySize = sizeof(UInt32); | |
status = AudioUnitGetProperty( | |
audioRecorder->inputUnit, | |
kAudioDevicePropertyBufferFrameSize, | |
kAudioUnitScope_Global, | |
0, | |
&bufferSizeFrames, | |
&propertySize | |
); | |
assert_noErr(status); | |
#endif | |
size_t bufferListTotalBytes = 0; | |
audioRecorder->bufferSizeFrames = bufferSizeFrames; | |
audioRecorder->inputBufferList = as_AudioBufferList_New(audioRecorder->streamFormat, bufferSizeFrames, &bufferListTotalBytes); | |
AURenderCallbackStruct callbackStruct; | |
callbackStruct.inputProc = inputCallback; | |
callbackStruct.inputProcRefCon = audioRecorder; | |
status = AudioUnitSetProperty( | |
audioRecorder->inputUnit, | |
kAudioOutputUnitProperty_SetInputCallback, | |
kAudioUnitScope_Global, | |
0, | |
&callbackStruct, | |
sizeof(callbackStruct) | |
); | |
assert_noErr(status); | |
status = AudioUnitInitialize(audioRecorder->inputUnit); | |
assert_noErr(status); | |
return AS_ErrorCode_NoError; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment