From 6111cb6b09a9f15e1b2876e1875d6ba1b95b8884 Mon Sep 17 00:00:00 2001 From: Hans Petter Selasky Date: Sun, 14 Jul 2019 10:21:29 +0200 Subject: [PATCH 1/6] Add basic support for iOS to portaudio. Signed-off-by: Hans Petter Selasky --- include/pa_ios_core.h | 59 + src/common/pa_hostapi.h | 7 + src/hostapi/coreaudio_ios/pa_ios_core.c | 1429 +++++++++++++++++ .../coreaudio_ios/pa_ios_core_blocking.c | 637 ++++++++ .../coreaudio_ios/pa_ios_core_blocking.h | 134 ++ .../coreaudio_ios/pa_ios_core_internal.h | 175 ++ .../coreaudio_ios/pa_ios_core_utilities.c | 225 +++ .../coreaudio_ios/pa_ios_core_utilities.h | 123 ++ src/os/unix/pa_unix_hostapis.c | 5 + 9 files changed, 2794 insertions(+) create mode 100644 include/pa_ios_core.h create mode 100644 src/hostapi/coreaudio_ios/pa_ios_core.c create mode 100644 src/hostapi/coreaudio_ios/pa_ios_core_blocking.c create mode 100644 src/hostapi/coreaudio_ios/pa_ios_core_blocking.h create mode 100644 src/hostapi/coreaudio_ios/pa_ios_core_internal.h create mode 100644 src/hostapi/coreaudio_ios/pa_ios_core_utilities.c create mode 100644 src/hostapi/coreaudio_ios/pa_ios_core_utilities.h diff --git a/include/pa_ios_core.h b/include/pa_ios_core.h new file mode 100644 index 000000000..e4e0386aa --- /dev/null +++ b/include/pa_ios_core.h @@ -0,0 +1,59 @@ +#ifndef PA_IOS_CORE_H +#define PA_IOS_CORE_H +/* + * PortAudio Portable Real-Time Audio Library + * iOS Core Audio specific extensions + * portaudio.h should be included before this file. + * + * Copyright (c) 2019 Hans Petter Selasky + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + * @ingroup public_header + * @brief iOS-specific PortAudio API extension header file. + */ + +#include "portaudio.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif /** __cplusplus */ + +#endif /* PA_IOS_CORE_H */ diff --git a/src/common/pa_hostapi.h b/src/common/pa_hostapi.h index c716fa7e5..e06db8664 100644 --- a/src/common/pa_hostapi.h +++ b/src/common/pa_hostapi.h @@ -153,6 +153,13 @@ are defaulted to 1. #define PA_USE_COREAUDIO 1 #endif +#ifndef PA_USE_COREAUDIO_IOS +#define PA_USE_COREAUDIO_IOS 0 +#elif (PA_USE_COREAUDIO_IOS != 0) && (PA_USE_COREAUDIO_IOS != 1) +#undef PA_USE_COREAUDIO_IOS +#define PA_USE_COREAUDIO_IOS 1 +#endif + #ifndef PA_USE_ASIHPI #define PA_USE_ASIHPI 0 #elif (PA_USE_ASIHPI != 0) && (PA_USE_ASIHPI != 1) diff --git a/src/hostapi/coreaudio_ios/pa_ios_core.c b/src/hostapi/coreaudio_ios/pa_ios_core.c new file mode 100644 index 000000000..37d605edd --- /dev/null +++ b/src/hostapi/coreaudio_ios/pa_ios_core.c @@ -0,0 +1,1429 @@ +/* + * Implementation of the PortAudio API for Apple AUHAL + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file pa_ios_core + @ingroup hostapi_src + @author Bjorn Roche + @brief AUHAL implementation of PortAudio +*/ + +#include "pa_ios_core_internal.h" + +#include + +#include + +#include + +#include "pa_ios_core.h" +#include "pa_ios_core_utilities.h" +#include "pa_ios_core_blocking.h" + +/* prototypes for functions declared in this file */ +PaError PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex index); + +static void Terminate(struct PaUtilHostApiRepresentation *hostApi); +static PaError IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate); +static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, + PaStream ** s, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback * streamCallback, + void *userData); +static PaError CloseStream(PaStream * stream); +static PaError StartStream(PaStream * stream); +static PaError StopStream(PaStream * stream); +static PaError AbortStream(PaStream * stream); +static PaError IsStreamStopped(PaStream * s); +static PaError IsStreamActive(PaStream * stream); +static PaTime GetStreamTime(PaStream * stream); +static OSStatus AudioIOProc(void *inRefCon, + AudioUnitRenderActionFlags * ioActionFlags, + const AudioTimeStamp * inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList * ioData); +static double GetStreamCpuLoad(PaStream * stream); + +/* + * Callback called when starting or stopping a stream. + */ +static void +startStopCallback( + void *inRefCon, + AudioUnit ci, + AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) inRefCon; + UInt32 isRunning; + UInt32 size = sizeof(isRunning); + OSStatus err; + + err = AudioUnitGetProperty(ci, kAudioOutputUnitProperty_IsRunning, inScope, inElement, &isRunning, &size); + assert(!err); + if (err) + isRunning = false; + if (isRunning) + return; + if (stream->inputUnit && stream->outputUnit && stream->inputUnit != stream->outputUnit && ci == stream->inputUnit) + return; + PaStreamFinishedCallback *sfc = stream->streamRepresentation.streamFinishedCallback; + + if (stream->state == STOPPING) + stream->state = STOPPED; + if (sfc) + sfc(stream->streamRepresentation.userData); +} + +static void +FillDeviceInfo(PaIosAUHAL * auhalHostApi, + PaDeviceInfo * deviceInfo, + PaHostApiIndex hostApiIndex) +{ + memset(deviceInfo, 0, sizeof(PaDeviceInfo)); + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = "Default"; + deviceInfo->defaultSampleRate = 48000; + deviceInfo->maxInputChannels = 1; + deviceInfo->maxOutputChannels = 2; + + deviceInfo->defaultLowInputLatency = 0.008; + deviceInfo->defaultHighInputLatency = 0.080; + deviceInfo->defaultLowOutputLatency = 0.008; + deviceInfo->defaultHighOutputLatency = 0.080; +} + +PaError +PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex hostApiIndex) +{ + PaError result = paNoError; + PaIosAUHAL *auhalHostApi = NULL; + PaDeviceInfo *deviceInfoArray; + + auhalHostApi = (PaIosAUHAL *) PaUtil_AllocateMemory(sizeof(PaIosAUHAL)); + if (auhalHostApi == NULL) { + result = paInsufficientMemory; + goto error; + } + auhalHostApi->allocations = PaUtil_CreateAllocationGroup(); + if (auhalHostApi->allocations == NULL) { + result = paInsufficientMemory; + goto error; + } + *hostApi = &auhalHostApi->inheritedHostApiRep; + + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "iOS Audio"; + (*hostApi)->info.defaultInputDevice = 0; + (*hostApi)->info.defaultOutputDevice = 0; + (*hostApi)->info.deviceCount = 1; + + (*hostApi)->deviceInfos = (PaDeviceInfo **) PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo *) * 1); + + if ((*hostApi)->deviceInfos == NULL) { + result = paInsufficientMemory; + goto error; + } + deviceInfoArray = (PaDeviceInfo *) PaUtil_GroupAllocateMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo) * 1); + if (deviceInfoArray == NULL) { + result = paInsufficientMemory; + goto error; + } + FillDeviceInfo(auhalHostApi, &deviceInfoArray[0], hostApiIndex); + + (*hostApi)->deviceInfos[0] = &deviceInfoArray[0]; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( + &auhalHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable); + + PaUtil_InitializeStreamInterface( + &auhalHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable); + + return (result); + +error: + if (auhalHostApi != NULL) { + if (auhalHostApi->allocations != NULL) { + PaUtil_FreeAllAllocations(auhalHostApi->allocations); + PaUtil_DestroyAllocationGroup(auhalHostApi->allocations); + } + PaUtil_FreeMemory(auhalHostApi); + } + return (result); +} + +static void +Terminate(struct PaUtilHostApiRepresentation *hostApi) +{ + PaIosAUHAL *auhalHostApi = (PaIosAUHAL *) hostApi; + + if (auhalHostApi->allocations) { + PaUtil_FreeAllAllocations(auhalHostApi->allocations); + PaUtil_DestroyAllocationGroup(auhalHostApi->allocations); + } + PaUtil_FreeMemory(auhalHostApi); +} + +static PaError +IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate) +{ + PaSampleFormat inputSampleFormat; + PaSampleFormat outputSampleFormat; + int inputChannelCount; + int outputChannelCount; + PaError err; + PaStream *s; + + if (inputParameters) { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + if (inputSampleFormat & paCustomFormat) + return (paSampleFormatNotSupported); + if (inputParameters->device == paUseHostApiSpecificDeviceSpecification) + return (paInvalidDevice); + if (inputChannelCount > hostApi->deviceInfos[inputParameters->device]->maxInputChannels) + return (paInvalidChannelCount); + } else { + inputChannelCount = 0; + } + + if (outputParameters) { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + if (outputSampleFormat & paCustomFormat) + return (paSampleFormatNotSupported); + if (outputParameters->device == paUseHostApiSpecificDeviceSpecification) + return (paInvalidDevice); + if (outputChannelCount > hostApi->deviceInfos[outputParameters->device]->maxOutputChannels) + return (paInvalidChannelCount); + } else { + outputChannelCount = 0; + } + + err = OpenStream(hostApi, &s, inputParameters, outputParameters, + sampleRate, 1024, 0, (PaStreamCallback *) 1, NULL); + if (err) + return (err); + + (void)CloseStream(s); + + return paFormatIsSupported; +} + +/* ================================================================================= */ +static void +InitializeDeviceProperties(PaIosCoreDeviceProperties * deviceProperties) +{ + memset(deviceProperties, 0, sizeof(PaIosCoreDeviceProperties)); + deviceProperties->sampleRate = 1.0; /* Better than random. + * Overwritten by actual + * values later on. */ + deviceProperties->samplePeriod = 1.0 / deviceProperties->sampleRate; +} + +static Float64 +CalculateSoftwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) +{ + UInt32 latencyFrames = deviceProperties->bufferFrameSize + deviceProperties->deviceLatency + deviceProperties->safetyOffset; + + return latencyFrames * deviceProperties->samplePeriod; /* same as dividing by + * sampleRate but faster */ +} + +static Float64 +CalculateHardwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) +{ + return deviceProperties->deviceLatency * deviceProperties->samplePeriod; /* same as dividing by + * sampleRate but faster */ +} + +/* Calculate values used to convert Apple timestamps into PA timestamps + * from the device properties. The final results of this calculation + * will be used in the audio callback function. + */ +static void +UpdateTimeStampOffsets(PaIosCoreStream * stream) +{ + Float64 inputSoftwareLatency = 0.0; + Float64 inputHardwareLatency = 0.0; + Float64 outputSoftwareLatency = 0.0; + Float64 outputHardwareLatency = 0.0; + + if (stream->inputUnit != NULL) { + inputSoftwareLatency = CalculateSoftwareLatencyFromProperties(stream, &stream->inputProperties); + inputHardwareLatency = CalculateHardwareLatencyFromProperties(stream, &stream->inputProperties); + } + if (stream->outputUnit != NULL) { + outputSoftwareLatency = CalculateSoftwareLatencyFromProperties(stream, &stream->outputProperties); + outputHardwareLatency = CalculateHardwareLatencyFromProperties(stream, &stream->outputProperties); + } + /* We only need a mutex around setting these variables as a group. */ + pthread_mutex_lock(&stream->timingInformationMutex); + stream->timestampOffsetCombined = inputSoftwareLatency + outputSoftwareLatency; + stream->timestampOffsetInputDevice = inputHardwareLatency; + stream->timestampOffsetOutputDevice = outputHardwareLatency; + pthread_mutex_unlock(&stream->timingInformationMutex); +} + +static PaError +OpenAndSetupOneAudioUnit( + const PaIosCoreStream * stream, + const PaStreamParameters * inStreamParams, + const PaStreamParameters * outStreamParams, + const UInt32 requestedFramesPerBuffer, + UInt32 * actualInputFramesPerBuffer, + UInt32 * actualOutputFramesPerBuffer, + const PaIosAUHAL * auhalHostApi, + AudioUnit * audioUnit, + const double sampleRate, + void *refCon) +{ + AudioComponentDescription desc; + AudioComponent comp; + AudioStreamBasicDescription desiredFormat; + OSStatus result = noErr; + PaError paResult = paNoError; + int line = 0; + UInt32 callbackKey; + AURenderCallbackStruct rcbs; + + if (!inStreamParams && !outStreamParams) { + *audioUnit = NULL; + return paNoError; + } + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = AudioComponentFindNext(NULL, &desc); + if (comp == NULL) { + *audioUnit = NULL; + return (paUnanticipatedHostError); + } + result = AudioComponentInstanceNew(comp, audioUnit); + if (result) { + *audioUnit = NULL; + return (ERR(result)); + } +#define ERR_WRAP(ios_err) do { \ + result = ios_err; \ + line = __LINE__ ; \ + if (result != noErr) \ + goto error; \ +} while(0) + + if (inStreamParams) { + UInt32 enableIO = 1; + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &enableIO, + sizeof(enableIO))); + } + if (!outStreamParams) { + UInt32 enableIO = 0; + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + OUTPUT_ELEMENT, + &enableIO, + sizeof(enableIO))); + } + if (inStreamParams && outStreamParams) { + assert(outStreamParams->device == inStreamParams->device); + } + ERR_WRAP(AudioUnitAddPropertyListener(*audioUnit, + kAudioOutputUnitProperty_IsRunning, + startStopCallback, + (void *)stream)); + + memset(&desiredFormat, 0, sizeof(desiredFormat)); + desiredFormat.mFormatID = kAudioFormatLinearPCM; + desiredFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; + desiredFormat.mFramesPerPacket = 1; + desiredFormat.mBitsPerChannel = sizeof(float) * 8; + + result = 0; + + if (outStreamParams) { + UInt32 value = kAudioConverterQuality_High; + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_RenderQuality, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + &value, + sizeof(value))); + } + /* now set the format on the Audio Units. */ + if (outStreamParams) { + desiredFormat.mSampleRate = sampleRate; + desiredFormat.mBytesPerPacket = sizeof(float) * outStreamParams->channelCount; + desiredFormat.mBytesPerFrame = sizeof(float) * outStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = outStreamParams->channelCount; + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription))); + } + if (inStreamParams) { + AudioStreamBasicDescription sourceFormat; + UInt32 size = sizeof(AudioStreamBasicDescription); + + /* keep the sample rate of the device, or we confuse AUHAL */ + ERR_WRAP(AudioUnitGetProperty(*audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &sourceFormat, + &size)); + desiredFormat.mSampleRate = sampleRate; + desiredFormat.mBytesPerPacket = sizeof(float) * inStreamParams->channelCount; + desiredFormat.mBytesPerFrame = sizeof(float) * inStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = inStreamParams->channelCount; + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription))); + } + if (outStreamParams) { + UInt32 size = sizeof(*actualOutputFramesPerBuffer); + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + &requestedFramesPerBuffer, + sizeof(requestedFramesPerBuffer))); + ERR_WRAP(AudioUnitGetProperty(*audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + actualOutputFramesPerBuffer, + &size)); + } + if (inStreamParams) { + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &requestedFramesPerBuffer, + sizeof(requestedFramesPerBuffer))); + + *actualInputFramesPerBuffer = requestedFramesPerBuffer; + } + callbackKey = outStreamParams ? kAudioUnitProperty_SetRenderCallback : + kAudioOutputUnitProperty_SetInputCallback; + rcbs.inputProc = AudioIOProc; + rcbs.inputProcRefCon = refCon; + ERR_WRAP(AudioUnitSetProperty( + *audioUnit, + callbackKey, + kAudioUnitScope_Output, + outStreamParams ? OUTPUT_ELEMENT : INPUT_ELEMENT, + &rcbs, + sizeof(rcbs))); + + /* initialize the audio unit */ + ERR_WRAP(AudioUnitInitialize(*audioUnit)); + + return (paNoError); +#undef ERR_WRAP + +error: + AudioComponentInstanceDispose(*audioUnit); + *audioUnit = NULL; + if (result) + return PaIosCore_SetError(result, line, 1); + return (paResult); +} + +static long +computeRingBufferSize(const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + long inputFramesPerBuffer, + long outputFramesPerBuffer, + double sampleRate) +{ + long ringSize; + int index; + int i; + double latency; + long framesPerBuffer; + + assert(inputParameters || outputParameters); + + if (outputParameters && inputParameters) { + latency = MAX(inputParameters->suggestedLatency, outputParameters->suggestedLatency); + framesPerBuffer = MAX(inputFramesPerBuffer, outputFramesPerBuffer); + } else if (outputParameters) { + latency = outputParameters->suggestedLatency; + framesPerBuffer = outputFramesPerBuffer; + } else { + latency = inputParameters->suggestedLatency; + framesPerBuffer = inputFramesPerBuffer; + } + + ringSize = (long)(latency * sampleRate * 2 + .5); + + if (ringSize < framesPerBuffer * 3) + ringSize = framesPerBuffer * 3; + + /* make sure it's at least 4 */ + ringSize = MAX(ringSize, 4); + + /* round up to the next power of 2 */ + index = -1; + + for (i = 0; i != (sizeof(long) * 8); ++i) + if ((ringSize >> i) & 0x01) + index = i; + assert(index > 0); + + if (ringSize <= (0x01 << index)) + ringSize = 0x01 << index; + else + ringSize = 0x01 << (index + 1); + + return ringSize; +} + +static PaError +OpenStream(struct PaUtilHostApiRepresentation *hostApi, + PaStream ** s, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate, + unsigned long requestedFramesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback * streamCallback, + void *userData) +{ + PaError result = paNoError; + PaIosAUHAL *auhalHostApi = (PaIosAUHAL *) hostApi; + PaIosCoreStream *stream = 0; + int inputChannelCount; + int outputChannelCount; + PaSampleFormat inputSampleFormat; + PaSampleFormat outputSampleFormat; + PaSampleFormat hostInputSampleFormat; + PaSampleFormat hostOutputSampleFormat; + + UInt32 inputLatencyFrames = 0; + UInt32 outputLatencyFrames = 0; + + if (requestedFramesPerBuffer == paFramesPerBufferUnspecified) + requestedFramesPerBuffer = sampleRate * 0.016; + + if (inputParameters) { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* @todo Blocking read/write on Ios is not yet supported. */ + if (!streamCallback && inputSampleFormat & paNonInterleaved) { + return paSampleFormatNotSupported; + } + /* + * unless alternate device specification is supported, + * reject the use of paUseHostApiSpecificDeviceSpecification + */ + + if (inputParameters->device == paUseHostApiSpecificDeviceSpecification) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if (inputChannelCount > hostApi->deviceInfos[inputParameters->device]->maxInputChannels) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostInputSampleFormat = paFloat32; + } else { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paFloat32; + } + + if (outputParameters) { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* @todo Blocking read/write on Ios is not yet supported. */ + if (!streamCallback && outputSampleFormat & paNonInterleaved) { + return paSampleFormatNotSupported; + } + /* + * unless alternate device specification is supported, + * reject the use of paUseHostApiSpecificDeviceSpecification + */ + + if (outputParameters->device == paUseHostApiSpecificDeviceSpecification) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if (outputChannelCount > hostApi->deviceInfos[outputParameters->device]->maxOutputChannels) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostOutputSampleFormat = paFloat32; + } else { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paFloat32; + } + + /* validate platform specific flags */ + if ((streamFlags & paPlatformSpecificFlags) != 0) + return paInvalidFlag; /* unexpected platform specific flag */ + + stream = (PaIosCoreStream *) PaUtil_AllocateMemory(sizeof(PaIosCoreStream)); + if (!stream) { + result = paInsufficientMemory; + goto error; + } + /* + * If we fail after this point, we my be left in a bad state, with + * some data structures setup and others not. So, first thing we do + * is initialize everything so that if we fail, we know what hasn't + * been touched. + */ + memset(stream, 0, sizeof(PaIosCoreStream)); + + if (streamCallback) { + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &auhalHostApi->callbackStreamInterface, + streamCallback, userData); + } else { + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &auhalHostApi->blockingStreamInterface, + BlioCallback, &stream->blio); + } + + PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); + + if (inputParameters && outputParameters && outputParameters->device == inputParameters->device) { + /* full duplex. One device. */ + UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; + UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; + + result = OpenAndSetupOneAudioUnit(stream, + inputParameters, + outputParameters, + requestedFramesPerBuffer, + &inputFramesPerBuffer, + &outputFramesPerBuffer, + auhalHostApi, + &stream->inputUnit, + sampleRate, + stream); + stream->inputFramesPerBuffer = inputFramesPerBuffer; + stream->outputFramesPerBuffer = outputFramesPerBuffer; + stream->outputUnit = stream->inputUnit; + if (result != paNoError) + goto error; + } else { + /* full duplex, different devices OR simplex */ + UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; + UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; + + result = OpenAndSetupOneAudioUnit(stream, + NULL, + outputParameters, + requestedFramesPerBuffer, + NULL, + &outputFramesPerBuffer, + auhalHostApi, + &stream->outputUnit, + sampleRate, + stream); + if (result != paNoError) + goto error; + result = OpenAndSetupOneAudioUnit(stream, + inputParameters, + NULL, + requestedFramesPerBuffer, + &inputFramesPerBuffer, + NULL, + auhalHostApi, + &stream->inputUnit, + sampleRate, + stream); + if (result != paNoError) + goto error; + stream->inputFramesPerBuffer = inputFramesPerBuffer; + stream->outputFramesPerBuffer = outputFramesPerBuffer; + } + + inputLatencyFrames += stream->inputFramesPerBuffer; + outputLatencyFrames += stream->outputFramesPerBuffer; + + if (stream->inputUnit) { + const size_t szfl = sizeof(float); + + /* setup the AudioBufferList used for input */ + memset(&stream->inputAudioBufferList, 0, sizeof(AudioBufferList)); + stream->inputAudioBufferList.mNumberBuffers = 1; + stream->inputAudioBufferList.mBuffers[0].mNumberChannels + = inputChannelCount; + stream->inputAudioBufferList.mBuffers[0].mDataByteSize + = stream->inputFramesPerBuffer * inputChannelCount * szfl; + stream->inputAudioBufferList.mBuffers[0].mData + = (float *)calloc( + stream->inputFramesPerBuffer * inputChannelCount, + szfl); + if (!stream->inputAudioBufferList.mBuffers[0].mData) { + result = paInsufficientMemory; + goto error; + } + + /* + * If input and output devs are different we also need + * a ring buffer to store input data while waiting for + * output data: + */ + if (stream->outputUnit && (stream->inputUnit != stream->outputUnit)) { + /* + * May want the ringSize or initial position in ring + * buffer to depend somewhat on sample rate change + */ + void *data; + long ringSize; + + ringSize = computeRingBufferSize(inputParameters, + outputParameters, + stream->inputFramesPerBuffer, + stream->outputFramesPerBuffer, + sampleRate); + + /* + * now, we need to allocate memory for the ring + * buffer + */ + data = calloc(ringSize, szfl * inputParameters->channelCount); + if (!data) { + result = paInsufficientMemory; + goto error; + } + /* now we can initialize the ring buffer */ + result = PaUtil_InitializeRingBuffer(&stream->inputRingBuffer, + szfl * inputParameters->channelCount, ringSize, data); + if (result != 0) { + /* + * The only reason this should fail is if + * ringSize is not a power of 2, which we do + * not anticipate happening. + */ + result = paUnanticipatedHostError; + free(data); + goto error; + } + /* + * advance the read point a little, so we are + * reading from the middle of the buffer + */ + if (stream->outputUnit) { + PaUtil_AdvanceRingBufferWriteIndex(&stream->inputRingBuffer, + ringSize / RING_BUFFER_ADVANCE_DENOMINATOR); + } + + /* + * Just adds to input latency between input device + * and PA full duplex callback. + */ + inputLatencyFrames += ringSize; + } + } + + /* -- initialize Blio Buffer Processors -- */ + if (!streamCallback) { + long ringSize; + + ringSize = computeRingBufferSize(inputParameters, + outputParameters, + stream->inputFramesPerBuffer, + stream->outputFramesPerBuffer, + sampleRate); + result = initializeBlioRingBuffers(&stream->blio, + inputParameters ? inputParameters->sampleFormat : 0, + outputParameters ? outputParameters->sampleFormat : 0, + ringSize, + inputParameters ? inputChannelCount : 0, + outputParameters ? outputChannelCount : 0); + if (result != paNoError) + goto error; + + inputLatencyFrames += ringSize; + outputLatencyFrames += ringSize; + + } + /* -- initialize Buffer Processor -- */ + { + size_t maxHostFrames = stream->inputFramesPerBuffer; + + if (stream->outputFramesPerBuffer > maxHostFrames) + maxHostFrames = stream->outputFramesPerBuffer; + + result = PaUtil_InitializeBufferProcessor(&stream->bufferProcessor, + inputChannelCount, inputSampleFormat, + hostInputSampleFormat, + outputChannelCount, outputSampleFormat, + hostOutputSampleFormat, + sampleRate, + streamFlags, + requestedFramesPerBuffer, + maxHostFrames, + paUtilBoundedHostBufferSize, + streamCallback ? streamCallback : BlioCallback, + streamCallback ? userData : &stream->blio); + if (result != paNoError) + goto error; + } + stream->bufferProcessorIsInitialized = TRUE; + + /* Calculate actual latency from the sum of individual latencies. */ + if (inputParameters) { + inputLatencyFrames += PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.inputLatency = inputLatencyFrames / sampleRate; + } else { + stream->streamRepresentation.streamInfo.inputLatency = 0.0; + } + + if (outputParameters) { + outputLatencyFrames += PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.outputLatency = outputLatencyFrames / sampleRate; + } else { + stream->streamRepresentation.streamInfo.outputLatency = 0.0; + } + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->sampleRate = sampleRate; + + stream->userInChan = inputChannelCount; + stream->userOutChan = outputChannelCount; + + /* Setup property listeners for timestamp and latency calculations. */ + pthread_mutex_init(&stream->timingInformationMutex, NULL); + stream->timingInformationMutexIsInitialized = 1; + InitializeDeviceProperties(&stream->inputProperties); + InitializeDeviceProperties(&stream->outputProperties); + + UpdateTimeStampOffsets(stream); + /* Setup timestamp copies to be used by audio callback */ + stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined; + stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice; + stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice; + + stream->state = STOPPED; + stream->xrunFlags = 0; + + *s = (PaStream *) stream; + + return result; + +error: + CloseStream(stream); + return result; +} + +/* Convert to nanoseconds and then to seconds */ +#define HOST_TIME_TO_PA_TIME(x) ({ \ + mach_timebase_info_data_t info; \ + mach_timebase_info(&info); \ + ((x) * (double)info.numer / (double)info.denom) * 1.0E-09; \ + }) + +PaTime +GetStreamTime(PaStream * s) +{ + return (HOST_TIME_TO_PA_TIME(mach_absolute_time())); +} + +#define RING_BUFFER_EMPTY 1000 + +/* + * Called by the AudioUnit API to process audio from the sound card. + * This is where the magic happens. + */ +static OSStatus +AudioIOProc(void *inRefCon, + AudioUnitRenderActionFlags * ioActionFlags, + const AudioTimeStamp * inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList * ioData) +{ + size_t framesProcessed = 0; + PaStreamCallbackTimeInfo timeInfo = {}; + PaIosCoreStream *stream = (PaIosCoreStream *) inRefCon; + const bool isRender = (inBusNumber == OUTPUT_ELEMENT); + int callbackResult = paContinue; + double hostTimeStampInPaTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime); + + PaUtil_BeginCpuLoadMeasurement(&stream->cpuLoadMeasurer); + + /* compute PaStreamCallbackTimeInfo */ + if (pthread_mutex_trylock(&stream->timingInformationMutex) == 0) { + /* snapshot the ioproc copy of timing information */ + stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined; + stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice; + stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice; + pthread_mutex_unlock(&stream->timingInformationMutex); + } + + /* + * For timeInfo.currentTime we could calculate current time + * backwards from the HAL audio output time to give a more accurate + * impression of the current timeslice but it doesn't seem worth it + * at the moment since other PA host APIs don't do any better. + */ + timeInfo.currentTime = HOST_TIME_TO_PA_TIME(mach_absolute_time()); + + /* + * For an input HAL AU, inTimeStamp is the time the samples + * are received from the hardware, for an output HAL AU + * inTimeStamp is the time the samples are sent to the + * hardware. PA expresses timestamps in terms of when the + * samples enter the ADC or leave the DAC so we add or + * subtract kAudioDevicePropertyLatency below. + */ + + /* + * FIXME: not sure what to do below if the host timestamps aren't + * valid (kAudioTimeStampHostTimeValid isn't set) Could ask on CA + * mailing list if it is possible for it not to be set. If so, could + * probably grab a now timestamp at the top and compute from there + * (modulo scheduling jitter) or ask on mailing list for other + * options. + */ + + if (isRender) { + if (stream->inputUnit) { + /* full duplex */ + timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - + (stream->timestampOffsetCombined_ioProcCopy + stream->timestampOffsetInputDevice_ioProcCopy); + timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy; + } else { + /* output only */ + timeInfo.inputBufferAdcTime = 0; + timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy; + } + } else { + /* input only */ + timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - stream->timestampOffsetInputDevice_ioProcCopy; + timeInfo.outputBufferDacTime = 0; + } + + if (isRender && stream->inputUnit == stream->outputUnit) { + /* + * Full Duplex, One Device + * + * This is the lowest latency case, and also the simplest. + * Input data and output data are available at the same + * time. we do not use the input SR converter or the input + * ring buffer. + */ + OSStatus err; + size_t bytesPerFrame = sizeof(float) * ioData->mBuffers[0].mNumberChannels; + size_t frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame; + size_t total = 0; + + assert(ioData->mNumberBuffers == 1); + assert(ioData->mBuffers[0].mNumberChannels == stream->userOutChan); + + while (1) { + size_t delta = frames - total; + if (delta > stream->inputFramesPerBuffer) + delta = stream->inputFramesPerBuffer; + if (delta > stream->outputFramesPerBuffer) + delta = stream->outputFramesPerBuffer; + if (delta == 0) + break; + + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, stream->xrunFlags); + stream->xrunFlags = 0; + + stream->inputAudioBufferList.mBuffers[0].mDataByteSize = + delta * bytesPerFrame; + + err = AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + delta, + &stream->inputAudioBufferList); + if (err) + goto stop_stream; + + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + stream->inputAudioBufferList.mBuffers[0].mData, + stream->inputAudioBufferList.mBuffers[0].mNumberChannels); + + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, + (char *)ioData->mBuffers[0].mData + (bytesPerFrame * total), + ioData->mBuffers[0].mNumberChannels); + + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + total += delta; + } + } else if (isRender) { + /* + * Output Side of Full Duplex or Simplex Output + * + * This case handles output data as in the full duplex + * case and if there is input data, reads it off the + * ring buffer and into the PA buffer processor. + */ + size_t bytesPerFrame = sizeof(float) * ioData->mBuffers[0].mNumberChannels; + size_t frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame; + size_t total = 0; + int xrunFlags = stream->xrunFlags; + + if (stream->state == STOPPING || stream->state == CALLBACK_STOPPED) + xrunFlags = 0; + + assert(ioData->mNumberBuffers == 1); + assert(ioData->mBuffers[0].mNumberChannels == stream->userOutChan); + + while (1) { + size_t delta = frames - total; + + if (stream->inputUnit && delta > stream->inputFramesPerBuffer) + delta = stream->inputFramesPerBuffer; + if (delta > stream->outputFramesPerBuffer) + delta = stream->outputFramesPerBuffer; + if (delta == 0) + break; + + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, xrunFlags); + stream->xrunFlags = xrunFlags = 0; + + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, + (char *)ioData->mBuffers[0].mData + (total * bytesPerFrame), + ioData->mBuffers[0].mNumberChannels); + + if (stream->inputUnit) { + /* read data out of the ring buffer */ + int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; + size_t inBytesPerFrame = sizeof(float) * inChan; + void *data1; + void *data2; + ring_buffer_size_t size1; + ring_buffer_size_t size2; + size_t framesReadable = PaUtil_GetRingBufferReadRegions(&stream->inputRingBuffer, + delta, &data1, &size1, &data2, &size2); + + if (size1 == delta) { + /* simplest case: all in first buffer */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + data1, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, size1); + } else if (framesReadable < delta) { + long sizeBytes1 = size1 * inBytesPerFrame; + long sizeBytes2 = size2 * inBytesPerFrame; + + /* + * Underflow: Take what data we can, + * zero the rest. + */ + unsigned char data[delta * inBytesPerFrame]; + + if (size1 > 0) + memcpy(data, data1, sizeBytes1); + if (size2 > 0) + memcpy(data + sizeBytes1, data2, sizeBytes2); + memset(data + sizeBytes1 + sizeBytes2, 0, + (delta * inBytesPerFrame) - sizeBytes1 - sizeBytes2); + + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + data, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, + framesReadable); + /* flag underflow */ + stream->xrunFlags |= paInputUnderflow; + } else { + /* + * We got all the data, but + * split between buffers + */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, size1); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + data1, inChan); + PaUtil_Set2ndInputFrameCount(&stream->bufferProcessor, size2); + PaUtil_Set2ndInterleavedInputChannels(&stream->bufferProcessor, 0, + data2, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, framesReadable); + } + } else { + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + } + total += delta; + } + } else { + /* + * Input + * + * First, we read off the audio data and put it in the ring + * buffer. if this is an input-only stream, we need to + * process it more, otherwise, we let the output case deal + * with it. + */ + OSStatus err; + int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; + size_t bytesPerFrame = sizeof(float) * inChan; + size_t frames = inNumberFrames; + size_t total = 0; + + while (1) { + size_t delta = frames - total; + + if (delta > stream->inputFramesPerBuffer) + delta = stream->inputFramesPerBuffer; + if (delta == 0) + break; + + stream->inputAudioBufferList.mBuffers[0].mDataByteSize = + frames * bytesPerFrame; + + err = AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + delta, + &stream->inputAudioBufferList); + if (err) + goto stop_stream; + + if (stream->outputUnit) { + /* + * If this is duplex put the data into the + * ring buffer: + */ + size_t framesWritten = PaUtil_WriteRingBuffer(&stream->inputRingBuffer, + stream->inputAudioBufferList.mBuffers[0].mData, delta); + + if (framesWritten != delta) + stream->xrunFlags |= paInputOverflow; + } else { + /* Push data into the buffer processor */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, stream->xrunFlags); + stream->xrunFlags = 0; + + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + stream->inputAudioBufferList.mBuffers[0].mData, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + } + total += delta; + } + } + + /* + * Should we return successfully or fall through to stopping + * the stream? + */ + if (callbackResult == paContinue) { + PaUtil_EndCpuLoadMeasurement(&stream->cpuLoadMeasurer, framesProcessed); + return noErr; + } + +stop_stream: + /* XXX stopping stream from here causes a deadlock */ + PaUtil_EndCpuLoadMeasurement(&stream->cpuLoadMeasurer, framesProcessed); + return noErr; +} + +static PaError +CloseStream(PaStream * s) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaError result = paNoError; + + if (stream == NULL) + return (result); + + if (stream->outputUnit != NULL && stream->outputUnit != stream->inputUnit) + AudioComponentInstanceDispose(stream->outputUnit); + stream->outputUnit = NULL; + + if (stream->inputUnit != NULL) + AudioComponentInstanceDispose(stream->inputUnit); + stream->inputUnit = NULL; + + free((void *)stream->inputRingBuffer.buffer); + stream->inputRingBuffer.buffer = NULL; + + free(stream->inputAudioBufferList.mBuffers[0].mData); + stream->inputAudioBufferList.mBuffers[0].mData = NULL; + + result = destroyBlioRingBuffers(&stream->blio); + if (result) + return (result); + + if (stream->bufferProcessorIsInitialized) + PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); + if (stream->timingInformationMutexIsInitialized) + pthread_mutex_destroy(&stream->timingInformationMutex); + + PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); + PaUtil_FreeMemory(stream); + + return (result); +} + +static PaError +StartStream(PaStream * s) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) s; + OSStatus result = noErr; + +#define ERR_WRAP(ios_err) do { \ + result = ios_err; \ + if (result != noErr) \ + return ERR(result); \ +} while(0) + + PaUtil_ResetBufferProcessor(&stream->bufferProcessor); + + stream->state = ACTIVE; + if (stream->inputUnit) + ERR_WRAP(AudioOutputUnitStart(stream->inputUnit)); + + if (stream->outputUnit && stream->outputUnit != stream->inputUnit) + ERR_WRAP(AudioOutputUnitStart(stream->outputUnit)); + + return paNoError; +#undef ERR_WRAP +} + +static OSStatus +BlockWhileAudioUnitIsRunning(AudioUnit audioUnit, AudioUnitElement element) +{ + Boolean isRunning; + + while (1) { + UInt32 s = sizeof(isRunning); + OSStatus err = AudioUnitGetProperty(audioUnit, + kAudioOutputUnitProperty_IsRunning, + kAudioUnitScope_Global, element, &isRunning, &s); + + if (err || isRunning == false) + return (err); + Pa_Sleep(100); + } + return (noErr); +} + +static PaError +FinishStoppingStream(PaIosCoreStream * stream) +{ + OSStatus result = noErr; + PaError paErr; + +#define ERR_WRAP(ios_err) do { \ + result = ios_err; \ + if (result != noErr) \ + return ERR(result); \ +} while(0) + + if (stream->inputUnit == stream->outputUnit && stream->inputUnit) { + ERR_WRAP(AudioOutputUnitStop(stream->inputUnit)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 0)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 1)); + ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1)); + ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 0)); + } else { + if (stream->inputUnit) { + ERR_WRAP(AudioOutputUnitStop(stream->inputUnit)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 1)); + ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1)); + } + if (stream->outputUnit) { + ERR_WRAP(AudioOutputUnitStop(stream->outputUnit)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->outputUnit, 0)); + ERR_WRAP(AudioUnitReset(stream->outputUnit, kAudioUnitScope_Global, 0)); + } + } + if (stream->inputRingBuffer.buffer) { + PaUtil_FlushRingBuffer(&stream->inputRingBuffer); + memset((void *)stream->inputRingBuffer.buffer, 0, stream->inputRingBuffer.bufferSize); + if (stream->outputUnit) { + PaUtil_AdvanceRingBufferWriteIndex( + &stream->inputRingBuffer, stream->inputRingBuffer.bufferSize / + RING_BUFFER_ADVANCE_DENOMINATOR); + } + } + stream->xrunFlags = 0; + stream->state = STOPPED; + + paErr = resetBlioRingBuffers(&stream->blio); + if (paErr) + return (paErr); + + return (paNoError); +#undef ERR_WRAP +} + +static PaError +StopStream(PaStream * s) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaError paErr; + + stream->state = STOPPING; + + if (stream->userOutChan > 0) { + size_t maxHostFrames = MAX(stream->inputFramesPerBuffer, stream->outputFramesPerBuffer); + + paErr = waitUntilBlioWriteBufferIsEmpty(&stream->blio, stream->sampleRate, maxHostFrames); + } + return (FinishStoppingStream(stream)); +} + +static PaError +AbortStream(PaStream * s) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) s; + + stream->state = STOPPING; + return (FinishStoppingStream(stream)); +} + +static PaError +IsStreamStopped(PaStream * s) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) s; + + return (stream->state == STOPPED); +} + +static PaError +IsStreamActive(PaStream * s) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) s; + + return (stream->state == ACTIVE || stream->state == STOPPING); +} + +static double +GetStreamCpuLoad(PaStream * s) +{ + PaIosCoreStream *stream = (PaIosCoreStream *) s; + + return (PaUtil_GetCpuLoad(&stream->cpuLoadMeasurer)); +} diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_blocking.c b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.c new file mode 100644 index 000000000..236dccc56 --- /dev/null +++ b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.c @@ -0,0 +1,637 @@ +/* + * Implementation of the PortAudio API for Apple AUHAL + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostapi_src + + This file contains the implementation + required for blocking I/O. It is separated from pa_ios_core.c simply to ease + development. +*/ + +#include "pa_ios_core_blocking.h" +#include "pa_ios_core_internal.h" +#include +#ifdef MOSX_USE_NON_ATOMIC_FLAG_BITS +# define OSAtomicOr32( a, b ) ( (*(b)) |= (a) ) +# define OSAtomicAnd32( a, b ) ( (*(b)) &= (a) ) +#else +# include +#endif + +/* + * This function determines the size of a particular sample format. + * if the format is not recognized, this returns zero. + */ +static size_t computeSampleSizeFromFormat( PaSampleFormat format ) +{ + switch( format & (~paNonInterleaved) ) { + case paFloat32: return 4; + case paInt32: return 4; + case paInt24: return 3; + case paInt16: return 2; + case paInt8: case paUInt8: return 1; + default: return 0; + } +} +/* + * Same as computeSampleSizeFromFormat, except that if + * the size is not a power of two, it returns the next power of two up + */ +static size_t computeSampleSizeFromFormatPow2( PaSampleFormat format ) +{ + switch( format & (~paNonInterleaved) ) { + case paFloat32: return 4; + case paInt32: return 4; + case paInt24: return 4; + case paInt16: return 2; + case paInt8: case paUInt8: return 1; + default: return 0; + } +} + + + +/* + * Functions for initializing, resetting, and destroying BLIO structures. + * + */ + +/** + * This should be called with the relevant info when initializing a stream for callback. + * + * @param ringBufferSizeInFrames must be a power of 2 + */ +PaError initializeBlioRingBuffers( + PaIosBlio *blio, + PaSampleFormat inputSampleFormat, + PaSampleFormat outputSampleFormat, + long ringBufferSizeInFrames, + int inChan, + int outChan ) +{ + void *data; + int result; + OSStatus err; + + /* zeroify things */ + bzero( blio, sizeof( PaIosBlio ) ); + /* this is redundant, but the buffers are used to check + if the buffers have been initialized, so we do it explicitly. */ + blio->inputRingBuffer.buffer = NULL; + blio->outputRingBuffer.buffer = NULL; + + /* initialize simple data */ + blio->ringBufferFrames = ringBufferSizeInFrames; + blio->inputSampleFormat = inputSampleFormat; + blio->inputSampleSizeActual = computeSampleSizeFromFormat(inputSampleFormat); + blio->inputSampleSizePow2 = computeSampleSizeFromFormatPow2(inputSampleFormat); // FIXME: WHY? + blio->outputSampleFormat = outputSampleFormat; + blio->outputSampleSizeActual = computeSampleSizeFromFormat(outputSampleFormat); + blio->outputSampleSizePow2 = computeSampleSizeFromFormatPow2(outputSampleFormat); + + blio->inChan = inChan; + blio->outChan = outChan; + blio->statusFlags = 0; + blio->errors = paNoError; +#ifdef PA_IOS_BLIO_MUTEX + blio->isInputEmpty = false; + blio->isOutputFull = false; +#endif + + /* setup ring buffers */ +#ifdef PA_IOS_BLIO_MUTEX + result = PaIosCore_SetUnixError( pthread_mutex_init(&(blio->inputMutex),NULL), 0 ); + if( result ) + goto error; + result = UNIX_ERR( pthread_cond_init( &(blio->inputCond), NULL ) ); + if( result ) + goto error; + result = UNIX_ERR( pthread_mutex_init(&(blio->outputMutex),NULL) ); + if( result ) + goto error; + result = UNIX_ERR( pthread_cond_init( &(blio->outputCond), NULL ) ); +#endif + if( inChan ) { + data = calloc( ringBufferSizeInFrames, blio->inputSampleSizePow2 * inChan ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + err = PaUtil_InitializeRingBuffer( + &blio->inputRingBuffer, + blio->inputSampleSizePow2 * inChan, + ringBufferSizeInFrames, + data ); + assert( !err ); + } + if( outChan ) { + data = calloc( ringBufferSizeInFrames, blio->outputSampleSizePow2 * outChan ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + err = PaUtil_InitializeRingBuffer( + &blio->outputRingBuffer, + blio->outputSampleSizePow2 * outChan, + ringBufferSizeInFrames, + data ); + assert( !err ); + } + + result = resetBlioRingBuffers( blio ); + if( result ) + goto error; + + return 0; + + error: + destroyBlioRingBuffers( blio ); + return result; +} + +#ifdef PA_IOS_BLIO_MUTEX +PaError blioSetIsInputEmpty( PaIosBlio *blio, bool isEmpty ) +{ + PaError result = paNoError; + if( isEmpty == blio->isInputEmpty ) + goto done; + + /* we need to update the value. Here's what we do: + * - Lock the mutex, so noone else can write. + * - update the value. + * - unlock. + * - broadcast to all listeners. + */ + result = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); + if( result ) + goto done; + blio->isInputEmpty = isEmpty; + result = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); + if( result ) + goto done; + result = UNIX_ERR( pthread_cond_broadcast( &blio->inputCond ) ); + if( result ) + goto done; + + done: + return result; +} +PaError blioSetIsOutputFull( PaIosBlio *blio, bool isFull ) +{ + PaError result = paNoError; + if( isFull == blio->isOutputFull ) + goto done; + + /* we need to update the value. Here's what we do: + * - Lock the mutex, so noone else can write. + * - update the value. + * - unlock. + * - broadcast to all listeners. + */ + result = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); + if( result ) + goto done; + blio->isOutputFull = isFull; + result = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); + if( result ) + goto done; + result = UNIX_ERR( pthread_cond_broadcast( &blio->outputCond ) ); + if( result ) + goto done; + + done: + return result; +} +#endif + +/* This should be called after stopping or aborting the stream, so that on next + start, the buffers will be ready. */ +PaError resetBlioRingBuffers( PaIosBlio *blio ) +{ +#ifdef PA_IOS__BLIO_MUTEX + int result; +#endif + blio->statusFlags = 0; + if( blio->outputRingBuffer.buffer ) { + PaUtil_FlushRingBuffer( &blio->outputRingBuffer ); + /* Fill the buffer with zeros. */ + bzero( blio->outputRingBuffer.buffer, + blio->outputRingBuffer.bufferSize * blio->outputRingBuffer.elementSizeBytes ); + PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->ringBufferFrames ); + + /* Update isOutputFull. */ +#ifdef PA_IOS__BLIO_MUTEX + result = blioSetIsOutputFull( blio, toAdvance == blio->outputRingBuffer.bufferSize ); + if( result ) + goto error; +#endif +/* + printf( "------%d\n" , blio->outChan ); + printf( "------%d\n" , blio->outputSampleSize ); +*/ + } + if( blio->inputRingBuffer.buffer ) { + PaUtil_FlushRingBuffer( &blio->inputRingBuffer ); + bzero( blio->inputRingBuffer.buffer, + blio->inputRingBuffer.bufferSize * blio->inputRingBuffer.elementSizeBytes ); + /* Update isInputEmpty. */ +#ifdef PA_IOS__BLIO_MUTEX + result = blioSetIsInputEmpty( blio, true ); + if( result ) + goto error; +#endif + } + return paNoError; +#ifdef PA_IOS__BLIO_MUTEX + error: + return result; +#endif +} + +/*This should be called when you are done with the blio. It can safely be called + multiple times if there are no exceptions. */ +PaError destroyBlioRingBuffers( PaIosBlio *blio ) +{ + PaError result = paNoError; + if( blio->inputRingBuffer.buffer ) { + free( blio->inputRingBuffer.buffer ); +#ifdef PA_IOS__BLIO_MUTEX + result = UNIX_ERR( pthread_mutex_destroy( & blio->inputMutex ) ); + if( result ) return result; + result = UNIX_ERR( pthread_cond_destroy( & blio->inputCond ) ); + if( result ) return result; +#endif + } + blio->inputRingBuffer.buffer = NULL; + if( blio->outputRingBuffer.buffer ) { + free( blio->outputRingBuffer.buffer ); +#ifdef PA_IOS__BLIO_MUTEX + result = UNIX_ERR( pthread_mutex_destroy( & blio->outputMutex ) ); + if( result ) return result; + result = UNIX_ERR( pthread_cond_destroy( & blio->outputCond ) ); + if( result ) return result; +#endif + } + blio->outputRingBuffer.buffer = NULL; + + return result; +} + +/* + * this is the BlioCallback function. It expects to recieve a PaIosBlio Object + * pointer as userData. + * + */ +int BlioCallback( const void *input, void *output, unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + PaIosBlio *blio = (PaIosBlio*)userData; + ring_buffer_size_t framesAvailable; + ring_buffer_size_t framesToTransfer; + ring_buffer_size_t framesTransferred; + + /* set flags returned by OS: */ + OSAtomicOr32( statusFlags, &blio->statusFlags ) ; + + /* --- Handle Input Buffer --- */ + if( blio->inChan ) { + framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->inputRingBuffer ); + + /* check for underflow */ + if( framesAvailable < frameCount ) + { + OSAtomicOr32( paInputOverflow, &blio->statusFlags ); + framesToTransfer = framesAvailable; + } + else + { + framesToTransfer = (ring_buffer_size_t)frameCount; + } + + /* Copy the data from the audio input to the application ring buffer. */ + /*printf( "reading %d\n", toRead );*/ + framesTransferred = PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, framesToTransfer ); + assert( framesToTransfer == framesTransferred ); +#ifdef PA_IOS__BLIO_MUTEX + /* Priority inversion. See notes below. */ + blioSetIsInputEmpty( blio, false ); +#endif + } + + + /* --- Handle Output Buffer --- */ + if( blio->outChan ) { + framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer ); + + /* check for underflow */ + if( framesAvailable < frameCount ) + { + /* zero out the end of the output buffer that we do not have data for */ + framesToTransfer = framesAvailable; + + size_t bytesPerFrame = blio->outputSampleSizeActual * blio->outChan; + size_t offsetInBytes = framesToTransfer * bytesPerFrame; + size_t countInBytes = (frameCount - framesToTransfer) * bytesPerFrame; + bzero( ((char *)output) + offsetInBytes, countInBytes ); + + OSAtomicOr32( paOutputUnderflow, &blio->statusFlags ); + framesToTransfer = framesAvailable; + } + else + { + framesToTransfer = (ring_buffer_size_t)frameCount; + } + + /* copy the data */ + /*printf( "writing %d\n", toWrite );*/ + framesTransferred = PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, framesToTransfer ); + assert( framesToTransfer == framesTransferred ); +#ifdef PA_IOS__BLIO_MUTEX + /* We have a priority inversion here. However, we will only have to + wait if this was true and is now false, which means we've got + some room in the buffer. + Hopefully problems will be minimized. */ + blioSetIsOutputFull( blio, false ); +#endif + } + + return paContinue; +} + +PaError ReadStream( PaStream* stream, + void *buffer, + unsigned long framesRequested ) +{ + PaIosBlio *blio = & ((PaIosCoreStream*)stream) -> blio; + char *cbuf = (char *) buffer; + PaError ret = paNoError; + VVDBUG(("ReadStream()\n")); + + while( framesRequested > 0 ) { + ring_buffer_size_t framesAvailable; + ring_buffer_size_t framesToTransfer; + ring_buffer_size_t framesTransferred; + do { + framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ); +/* + printf( "Read Buffer is %%%g full: %ld of %ld.\n", + 100 * (float)avail / (float) blio->inputRingBuffer.bufferSize, + framesAvailable, blio->inputRingBuffer.bufferSize ); +*/ + if( framesAvailable == 0 ) { +#ifdef PA_IOS_BLIO_MUTEX + /**block when empty*/ + ret = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); + if( ret ) + return ret; + while( blio->isInputEmpty ) { + ret = UNIX_ERR( pthread_cond_wait( &blio->inputCond, &blio->inputMutex ) ); + if( ret ) + return ret; + } + ret = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); + if( ret ) + return ret; +#else + Pa_Sleep( PA_IOS_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); +#endif + } + } while( framesAvailable == 0 ); + framesToTransfer = (ring_buffer_size_t) MIN( framesAvailable, framesRequested ); + framesTransferred = PaUtil_ReadRingBuffer( &blio->inputRingBuffer, (void *)cbuf, framesToTransfer ); + cbuf += framesTransferred * blio->inputSampleSizeActual * blio->inChan; + framesRequested -= framesTransferred; + + if( framesToTransfer == framesAvailable ) { +#ifdef PA_IOS_BLIO_MUTEX + /* we just emptied the buffer, so we need to mark it as empty. */ + ret = blioSetIsInputEmpty( blio, true ); + if( ret ) + return ret; + /* of course, in the meantime, the callback may have put some sats + in, so + so check for that, too, to avoid a race condition. */ + /* FIXME - this does not seem to fix any race condition. */ + if( PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ) ) { + blioSetIsInputEmpty( blio, false ); + /* FIXME - why check? ret has not been set? */ + if( ret ) + return ret; + } +#endif + } + } + + /* Report either paNoError or paInputOverflowed. */ + /* may also want to report other errors, but this is non-standard. */ + /* FIXME should not clobber ret, use if(blio->statusFlags & paInputOverflow) */ + ret = blio->statusFlags & paInputOverflow; + + /* report underflow only once: */ + if( ret ) { + OSAtomicAnd32( (uint32_t)(~paInputOverflow), &blio->statusFlags ); + ret = paInputOverflowed; + } + + return ret; +} + + +PaError WriteStream( PaStream* stream, + const void *buffer, + unsigned long framesRequested ) +{ + PaIosCoreStream *iosStream = (PaIosCoreStream*)stream; + PaIosBlio *blio = &iosStream->blio; + char *cbuf = (char *) buffer; + PaError ret = paNoError; + VVDBUG(("WriteStream()\n")); + + while( framesRequested > 0 && iosStream->state != STOPPING ) { + ring_buffer_size_t framesAvailable; + ring_buffer_size_t framesToTransfer; + ring_buffer_size_t framesTransferred; + + do { + framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ); +/* + printf( "Write Buffer is %%%g full: %ld of %ld.\n", + 100 - 100 * (float)avail / (float) blio->outputRingBuffer.bufferSize, + framesAvailable, blio->outputRingBuffer.bufferSize ); +*/ + if( framesAvailable == 0 ) { +#ifdef PA_IOS_BLIO_MUTEX + /*block while full*/ + ret = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); + if( ret ) + return ret; + while( blio->isOutputFull ) { + ret = UNIX_ERR( pthread_cond_wait( &blio->outputCond, &blio->outputMutex ) ); + if( ret ) + return ret; + } + ret = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); + if( ret ) + return ret; +#else + Pa_Sleep( PA_IOS_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); +#endif + } + } while( framesAvailable == 0 && iosStream->state != STOPPING ); + + if( iosStream->state == STOPPING ) + { + break; + } + + framesToTransfer = MIN( framesAvailable, framesRequested ); + framesTransferred = PaUtil_WriteRingBuffer( &blio->outputRingBuffer, (void *)cbuf, framesToTransfer ); + cbuf += framesTransferred * blio->outputSampleSizeActual * blio->outChan; + framesRequested -= framesTransferred; + +#ifdef PA_IOS_BLIO_MUTEX + if( framesToTransfer == framesAvailable ) { + /* we just filled up the buffer, so we need to mark it as filled. */ + ret = blioSetIsOutputFull( blio, true ); + if( ret ) + return ret; + /* of course, in the meantime, we may have emptied the buffer, so + so check for that, too, to avoid a race condition. */ + if( PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ) ) { + blioSetIsOutputFull( blio, false ); + /* FIXME remove or review this code, does not fix race, ret not set! */ + if( ret ) + return ret; + } + } +#endif + } + + if ( iosStream->state == STOPPING ) + { + ret = paInternalError; + } + else if (ret == paNoError ) + { + /* Test for underflow. */ + ret = blio->statusFlags & paOutputUnderflow; + + /* report underflow only once: */ + if( ret ) + { + OSAtomicAnd32( (uint32_t)(~paOutputUnderflow), &blio->statusFlags ); + ret = paOutputUnderflowed; + } + } + + return ret; +} + +/* + * Wait until the data in the buffer has finished playing. + */ +PaError waitUntilBlioWriteBufferIsEmpty( PaIosBlio *blio, double sampleRate, + size_t framesPerBuffer ) +{ + PaError result = paNoError; + if( blio->outputRingBuffer.buffer ) { + ring_buffer_size_t framesLeft = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer ); + + /* Calculate when we should give up waiting. To be safe wait for two extra periods. */ + PaTime now = PaUtil_GetTime(); + PaTime startTime = now; + PaTime timeoutTime = startTime + (framesLeft + (2 * framesPerBuffer)) / sampleRate; + + long msecPerBuffer = 1 + (long)( 1000.0 * framesPerBuffer / sampleRate); + while( framesLeft > 0 && now < timeoutTime ) { + VDBUG(( "waitUntilBlioWriteBufferIsFlushed: framesLeft = %d, framesPerBuffer = %ld\n", + framesLeft, framesPerBuffer )); + Pa_Sleep( msecPerBuffer ); + framesLeft = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer ); + now = PaUtil_GetTime(); + } + + if( framesLeft > 0 ) + { + VDBUG(( "waitUntilBlioWriteBufferIsFlushed: TIMED OUT - framesLeft = %d\n", framesLeft )); + result = paTimedOut; + } + } + return result; +} + +signed long GetStreamReadAvailable( PaStream* stream ) +{ + PaIosBlio *blio = & ((PaIosCoreStream*)stream) -> blio; + VVDBUG(("GetStreamReadAvailable()\n")); + + return PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ); +} + + +signed long GetStreamWriteAvailable( PaStream* stream ) +{ + PaIosBlio *blio = & ((PaIosCoreStream*)stream) -> blio; + VVDBUG(("GetStreamWriteAvailable()\n")); + + return PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ); +} + diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_blocking.h b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.h new file mode 100644 index 000000000..6afb59041 --- /dev/null +++ b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.h @@ -0,0 +1,134 @@ +/* + * Internal blocking interfaces for PortAudio Apple AUHAL implementation + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostapi_src +*/ + +#ifndef PA_IOS_CORE_BLOCKING_H_ +#define PA_IOS_CORE_BLOCKING_H_ + +#include "pa_ringbuffer.h" +#include "portaudio.h" +#include "pa_ios_core_utilities.h" + +/* + * Number of milliseconds to busy wait while waiting for data in blocking calls. + */ +#define PA_IOS_BLIO_BUSY_WAIT_SLEEP_INTERVAL (5) +/* + * Define exactly one of these blocking methods + * PA_IOS_BLIO_MUTEX is not actively maintained. + */ +#define PA_IOS_BLIO_BUSY_WAIT +/* +#define PA_IOS_BLIO_MUTEX +*/ + +typedef struct { + PaUtilRingBuffer inputRingBuffer; + PaUtilRingBuffer outputRingBuffer; + ring_buffer_size_t ringBufferFrames; + PaSampleFormat inputSampleFormat; + size_t inputSampleSizeActual; + size_t inputSampleSizePow2; + PaSampleFormat outputSampleFormat; + size_t outputSampleSizeActual; + size_t outputSampleSizePow2; + + int inChan; + int outChan; + + //PaStreamCallbackFlags statusFlags; + uint32_t statusFlags; + PaError errors; + + /* Here we handle blocking, using condition variables. */ +#ifdef PA_IOS_BLIO_MUTEX + volatile bool isInputEmpty; + pthread_mutex_t inputMutex; + pthread_cond_t inputCond; + + volatile bool isOutputFull; + pthread_mutex_t outputMutex; + pthread_cond_t outputCond; +#endif +} +PaIosBlio; + +/* + * These functions operate on condition and related variables. + */ + +PaError initializeBlioRingBuffers( + PaIosBlio *blio, + PaSampleFormat inputSampleFormat, + PaSampleFormat outputSampleFormat, + long ringBufferSizeInFrames, + int inChan, + int outChan ); +PaError destroyBlioRingBuffers( PaIosBlio *blio ); +PaError resetBlioRingBuffers( PaIosBlio *blio ); + +int BlioCallback( + const void *input, void *output, + unsigned long frameCount, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ); + +PaError waitUntilBlioWriteBufferIsEmpty( PaIosBlio *blio, double sampleRate, + size_t framesPerBuffer ); + +#endif /*PA_IOS_CORE_BLOCKING_H_*/ diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_internal.h b/src/hostapi/coreaudio_ios/pa_ios_core_internal.h new file mode 100644 index 000000000..58f46609e --- /dev/null +++ b/src/hostapi/coreaudio_ios/pa_ios_core_internal.h @@ -0,0 +1,175 @@ +/* + * Internal interfaces for PortAudio Apple AUHAL implementation + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file pa_ios_core + @ingroup hostapi_src + @author Bjorn Roche + @brief AUHAL implementation of PortAudio +*/ + +#ifndef PA_IOS_CORE_INTERNAL_H__ +#define PA_IOS_CORE_INTERNAL_H__ + +#include +#include +#include + +#include "portaudio.h" +#include "pa_util.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_allocation.h" +#include "pa_cpuload.h" +#include "pa_process.h" +#include "pa_ringbuffer.h" + +#include "pa_ios_core_blocking.h" + +/* function prototypes */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaIosCore_Initialize(PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define RING_BUFFER_ADVANCE_DENOMINATOR 4 + +PaError ReadStream(PaStream* stream, void *buffer, unsigned long frames); +PaError WriteStream(PaStream* stream, const void *buffer, unsigned long frames); +signed long GetStreamReadAvailable(PaStream* stream); +signed long GetStreamWriteAvailable(PaStream* stream); + +/* PaIosAUHAL - host API datastructure specific to this implementation */ +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; +} +PaIosAUHAL; + +typedef struct PaIosCoreDeviceProperties +{ + UInt32 safetyOffset; + UInt32 bufferFrameSize; + UInt32 deviceLatency; + Float64 sampleRate; + Float64 samplePeriod; +} +PaIosCoreDeviceProperties; + +/* stream data structure specifically for this implementation */ +typedef struct PaIosCoreStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + + /* implementation specific data goes here */ + bool bufferProcessorIsInitialized; + AudioUnit inputUnit; + AudioUnit outputUnit; + size_t userInChan; + size_t userOutChan; + size_t inputFramesPerBuffer; + size_t outputFramesPerBuffer; + PaIosBlio blio; + /* We use this ring buffer when input and out devs are different. */ + PaUtilRingBuffer inputRingBuffer; + /* We need to preallocate an inputBuffer for reading data. */ + AudioBufferList inputAudioBufferList; + AudioTimeStamp startTime; + volatile uint32_t xrunFlags; /* PaStreamCallbackFlags*/ + volatile enum { + STOPPED = 0, /* playback is completely stopped, + and the user has called StopStream(). */ + CALLBACK_STOPPED = 1, /* callback has requested stop, + but user has not yet called StopStream(). */ + STOPPING = 2, /* The stream is in the process of closing + because the user has called StopStream. + This state is just used internally; + externally it is indistinguishable from + ACTIVE.*/ + ACTIVE = 3 /* The stream is active and running. */ + } state; + double sampleRate; + PaIosCoreDeviceProperties inputProperties; + PaIosCoreDeviceProperties outputProperties; + + /* data updated by main thread and notifications, protected by timingInformationMutex */ + int timingInformationMutexIsInitialized; + pthread_mutex_t timingInformationMutex; + + /* These are written by the PA thread or from CoreAudio callbacks. Protected by the mutex. */ + Float64 timestampOffsetCombined; + Float64 timestampOffsetInputDevice; + Float64 timestampOffsetOutputDevice; + + /* Offsets in seconds to be applied to Apple timestamps to convert them to PA timestamps. + * While the io proc is active, the following values are only accessed and manipulated by the ioproc */ + Float64 timestampOffsetCombined_ioProcCopy; + Float64 timestampOffsetInputDevice_ioProcCopy; + Float64 timestampOffsetOutputDevice_ioProcCopy; +} +PaIosCoreStream; + +#endif /* PA_IOS_CORE_INTERNAL_H__ */ diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c new file mode 100644 index 000000000..5d936ee30 --- /dev/null +++ b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c @@ -0,0 +1,225 @@ +/* + * Helper and utility functions for pa_ios_core.c (Apple AUHAL implementation) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostapi_src +*/ + +#include "pa_ios_core_utilities.h" +#include "pa_ios_core_internal.h" + +#include + +#include +#include + +#include + +PaError +PaIosCore_SetUnixError(int err, int line) +{ + PaError ret; + const char *errorText; + + if (err == 0) + return paNoError; + + ret = paNoError; + + errorText = strerror(err); + + if (err == ENOMEM) + ret = paInsufficientMemory; + else + ret = paInternalError; + + PaUtil_SetLastHostErrorInfo(paCoreAudio, err, errorText); + + return (ret); +} + +PaError +PaIosCore_SetError(OSStatus error, int line, int isError) +{ + PaError result; + const char *errorType; + const char *errorText; + + switch (error) { + case kAudioServicesNoError: + return paNoError; + case kAudioFormatUnspecifiedError: + errorText = "Unspecified Audio Format Error"; + result = paInternalError; + break; + case kAudioFormatUnknownFormatError: + errorText = "Audio Format: Unknown Format Error"; + result = paInternalError; + break; + case kAudioFormatBadPropertySizeError: + errorText = "Audio Format: Bad Property Size"; + result = paInternalError; + break; + case kAudioFormatUnsupportedPropertyError: + errorText = "Audio Format: Unsupported Property Error"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidProperty: + errorText = "Audio Unit: Invalid Property"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidParameter: + errorText = "Audio Unit: Invalid Parameter"; + result = paInternalError; + break; + case kAudioUnitErr_NoConnection: + errorText = "Audio Unit: No Connection"; + result = paInternalError; + break; + case kAudioUnitErr_FailedInitialization: + errorText = "Audio Unit: Initialization Failed"; + result = paInternalError; + break; + case kAudioUnitErr_TooManyFramesToProcess: + errorText = "Audio Unit: Too Many Frames"; + result = paInternalError; + break; + case kAudioUnitErr_IllegalInstrument: + errorText = "Audio Unit: Illegal Instrument"; + result = paInternalError; + break; + case kAudioUnitErr_InstrumentTypeNotFound: + errorText = "Audio Unit: Instrument Type Not Found"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidFile: + errorText = "Audio Unit: Invalid File"; + result = paInternalError; + break; + case kAudioUnitErr_UnknownFileType: + errorText = "Audio Unit: Unknown File Type"; + result = paInternalError; + break; + case kAudioUnitErr_FileNotSpecified: + errorText = "Audio Unit: File Not Specified"; + result = paInternalError; + break; + case kAudioUnitErr_FormatNotSupported: + errorText = "Audio Unit: Format Not Supported"; + result = paInternalError; + break; + case kAudioUnitErr_Uninitialized: + errorText = "Audio Unit: Unitialized"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidScope: + errorText = "Audio Unit: Invalid Scope"; + result = paInternalError; + break; + case kAudioUnitErr_PropertyNotWritable: + errorText = "Audio Unit: PropertyNotWritable"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidPropertyValue: + errorText = "Audio Unit: Invalid Property Value"; + result = paInternalError; + break; + case kAudioUnitErr_PropertyNotInUse: + errorText = "Audio Unit: Property Not In Use"; + result = paInternalError; + break; + case kAudioUnitErr_Initialized: + errorText = "Audio Unit: Initialized"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidOfflineRender: + errorText = "Audio Unit: Invalid Offline Render"; + result = paInternalError; + break; + case kAudioUnitErr_Unauthorized: + errorText = "Audio Unit: Unauthorized"; + result = paInternalError; + break; + case kAudioUnitErr_CannotDoInCurrentContext: + errorText = "Audio Unit: cannot do in current context"; + result = paInternalError; + break; + default: + errorText = "Unknown Error"; + result = paInternalError; + break; + } + + if (isError) + errorType = "Error"; + else + errorType = "Warning"; + + char str[20]; + + /* see if it appears to be a 4-char-code */ + *(UInt32 *) (str + 1) = CFSwapInt32HostToBig(error); + + if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { + str[0] = str[5] = '\''; + str[6] = '\0'; + } else { + /* no, format it as an integer */ + snprintf(str, sizeof(str), "%d", (int)error); + } + + PaUtil_SetLastHostErrorInfo(paCoreAudio, error, errorText); + + return (result); +} diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.h b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.h new file mode 100644 index 000000000..0dd624fa2 --- /dev/null +++ b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.h @@ -0,0 +1,123 @@ +/* + * Helper and utility functions for pa_ios_core.c (Apple AUHAL implementation) + * + * PortAudio Portable Real-Time Audio Library + * Latest Version at: http://www.portaudio.com + * + * Written by Bjorn Roche of XO Audio LLC, from PA skeleton code. + * Portions copied from code by Dominic Mazzoni (who wrote a HAL implementation) + * + * Dominic's code was based on code by Phil Burk, Darren Gibbs, + * Gord Peters, Stephane Letz, and Greg Pfiel. + * + * The following people also deserve acknowledgements: + * + * Olivier Tristan for feedback and testing + * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O + * interface. + * + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup hostapi_src +*/ + +#ifndef PA_IOS_CORE_UTILITIES_H__ +#define PA_IOS_CORE_UTILITIES_H__ + +#include + +#include "portaudio.h" + +#include "pa_util.h" + +#include +#include + +#ifndef MIN +#define MIN(a, b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a, b) (((a)<(b))?(b):(a)) +#endif + +#define ERR(ios_error) PaIosCore_SetError(ios_error, __LINE__, 1 ) +#define WARNING(ios_error) PaIosCore_SetError(ios_error, __LINE__, 0 ) + + +/* Help keep track of AUHAL element numbers */ +#define INPUT_ELEMENT (1) +#define OUTPUT_ELEMENT (0) + +/* Normal level of debugging: fine for most apps that don't mind the occational warning being printf'ed */ +/* + */ +#define IOS_CORE_DEBUG +#ifdef IOS_CORE_DEBUG +# define DBUG(MSG) do { printf("||PaIosCore (AUHAL)|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define DBUG(MSG) +#endif + +/* Verbose Debugging: useful for developement */ +/* +#define IOS_CORE_VERBOSE_DEBUG +*/ +#ifdef IOS_CORE_VERBOSE_DEBUG +# define VDBUG(MSG) do { printf("||PaIosCore (v )|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define VDBUG(MSG) +#endif + +/* Very Verbose Debugging: Traces every call. */ +/* +#define IOS_CORE_VERY_VERBOSE_DEBUG + */ +#ifdef IOS_CORE_VERY_VERBOSE_DEBUG +# define VVDBUG(MSG) do { printf("||PaIosCore (vv)|| "); printf MSG ; fflush(stdout); } while(0) +#else +# define VVDBUG(MSG) +#endif + +#define UNIX_ERR(err) \ + PaIosCore_SetUnixError(err, __LINE__) + +PaError PaIosCore_SetUnixError(int err, int line); +PaError PaIosCore_SetError(OSStatus error, int line, int isError); + +#endif /* PA_IOS_CORE_UTILITIES_H__*/ diff --git a/src/os/unix/pa_unix_hostapis.c b/src/os/unix/pa_unix_hostapis.c index 95cd89b9d..de0cdf40f 100644 --- a/src/os/unix/pa_unix_hostapis.c +++ b/src/os/unix/pa_unix_hostapis.c @@ -52,6 +52,7 @@ PaError PaSGI_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex /* Linux AudioScience HPI */ PaError PaAsiHpi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaIosCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); PaError PaSkeleton_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); /** Note that on Linux, ALSA is placed before OSS so that the former is preferred over the latter. @@ -101,6 +102,10 @@ PaUtilHostApiInitializer *paHostApiInitializers[] = PaMacCore_Initialize, #endif +#if PA_USE_COREAUDIO_IOS + PaIosCore_Initialize, +#endif + #if PA_USE_PULSEAUDIO PaPulseAudio_Initialize, #endif From dbe735dcc520a31335562982d004754d7d151b0f Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 29 Jan 2024 01:48:56 +0100 Subject: [PATCH 2/6] Update CMakeLists with iOS implementation --- CMakeLists.txt | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fbbe2722..baf81c160 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -296,14 +296,25 @@ elseif(UNIX) if(APPLE) set(CMAKE_MACOSX_RPATH 1) - target_sources(PortAudio PRIVATE - src/hostapi/coreaudio/pa_mac_core.c - src/hostapi/coreaudio/pa_mac_core_blocking.c - src/hostapi/coreaudio/pa_mac_core_blocking.h - src/hostapi/coreaudio/pa_mac_core_internal.h - src/hostapi/coreaudio/pa_mac_core_utilities.c - src/hostapi/coreaudio/pa_mac_core_utilities.h - ) + if(IOS) + target_sources(PortAudio PRIVATE + src/hostapi/coreaudio_ios/pa_ios_core.c + src/hostapi/coreaudio_ios/pa_ios_core_blocking.c + src/hostapi/coreaudio_ios/pa_ios_core_blocking.h + src/hostapi/coreaudio_ios/pa_ios_core_internal.h + src/hostapi/coreaudio_ios/pa_ios_core_utilities.c + src/hostapi/coreaudio_ios/pa_ios_core_utilities.h + ) + else() + target_sources(PortAudio PRIVATE + src/hostapi/coreaudio/pa_mac_core.c + src/hostapi/coreaudio/pa_mac_core_blocking.c + src/hostapi/coreaudio/pa_mac_core_blocking.h + src/hostapi/coreaudio/pa_mac_core_internal.h + src/hostapi/coreaudio/pa_mac_core_utilities.c + src/hostapi/coreaudio/pa_mac_core_utilities.h + ) + endif() target_include_directories(PortAudio PRIVATE src/hostapi/coreaudio) set(PORTAUDIO_PUBLIC_HEADERS "${PORTAUDIO_PUBLIC_HEADERS}" include/pa_mac_core.h) @@ -315,8 +326,12 @@ elseif(UNIX) -Wl,-framework,CoreFoundation -Wl,-framework,CoreServices ) - target_compile_definitions(PortAudio PUBLIC PA_USE_COREAUDIO=1) - set(PKGCONFIG_CFLAGS "${PKGCONFIG_CFLAGS} -DPA_USE_COREAUDIO=1") + if(IOS) + target_compile_definitions(PortAudio PUBLIC PA_USE_COREAUDIO_IOS=1) + else() + target_compile_definitions(PortAudio PUBLIC PA_USE_COREAUDIO=1) + set(PKGCONFIG_CFLAGS "${PKGCONFIG_CFLAGS} -DPA_USE_COREAUDIO=1") + endif() # Use C11 so that we can make use of atomic library and avoid deprecation errors. set_property(TARGET PortAudio PROPERTY C_STANDARD 11) From b49d4e013312f8bcec385cbc00d5189fd0a2924a Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 5 Feb 2024 01:16:06 +0100 Subject: [PATCH 3/6] Fix renamed memory allocation functions See https://github.com/PortAudio/portaudio/pull/723 --- src/hostapi/coreaudio_ios/pa_ios_core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hostapi/coreaudio_ios/pa_ios_core.c b/src/hostapi/coreaudio_ios/pa_ios_core.c index 37d605edd..c246ed8f6 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core.c +++ b/src/hostapi/coreaudio_ios/pa_ios_core.c @@ -161,7 +161,7 @@ PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex host PaIosAUHAL *auhalHostApi = NULL; PaDeviceInfo *deviceInfoArray; - auhalHostApi = (PaIosAUHAL *) PaUtil_AllocateMemory(sizeof(PaIosAUHAL)); + auhalHostApi = (PaIosAUHAL *) PaUtil_AllocateZeroInitializedMemory(sizeof(PaIosAUHAL)); if (auhalHostApi == NULL) { result = paInsufficientMemory; goto error; @@ -180,14 +180,14 @@ PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex host (*hostApi)->info.defaultOutputDevice = 0; (*hostApi)->info.deviceCount = 1; - (*hostApi)->deviceInfos = (PaDeviceInfo **) PaUtil_GroupAllocateMemory( + (*hostApi)->deviceInfos = (PaDeviceInfo **) PaUtil_GroupAllocateZeroInitializedMemory( auhalHostApi->allocations, sizeof(PaDeviceInfo *) * 1); if ((*hostApi)->deviceInfos == NULL) { result = paInsufficientMemory; goto error; } - deviceInfoArray = (PaDeviceInfo *) PaUtil_GroupAllocateMemory( + deviceInfoArray = (PaDeviceInfo *) PaUtil_GroupAllocateZeroInitializedMemory( auhalHostApi->allocations, sizeof(PaDeviceInfo) * 1); if (deviceInfoArray == NULL) { result = paInsufficientMemory; @@ -668,7 +668,7 @@ OpenStream(struct PaUtilHostApiRepresentation *hostApi, if ((streamFlags & paPlatformSpecificFlags) != 0) return paInvalidFlag; /* unexpected platform specific flag */ - stream = (PaIosCoreStream *) PaUtil_AllocateMemory(sizeof(PaIosCoreStream)); + stream = (PaIosCoreStream *) PaUtil_AllocateZeroInitializedMemory(sizeof(PaIosCoreStream)); if (!stream) { result = paInsufficientMemory; goto error; From ac824cd99db303c4272f06b3cdda193698d64759 Mon Sep 17 00:00:00 2001 From: fwcd Date: Fri, 9 Feb 2024 23:25:40 +0100 Subject: [PATCH 4/6] Fix formatting issues in iOS implementation --- include/pa_ios_core.h | 8 +- src/hostapi/coreaudio_ios/pa_ios_core.c | 2290 ++++++++--------- .../coreaudio_ios/pa_ios_core_blocking.c | 710 +++-- .../coreaudio_ios/pa_ios_core_blocking.h | 8 +- .../coreaudio_ios/pa_ios_core_internal.h | 34 +- .../coreaudio_ios/pa_ios_core_utilities.c | 284 +- .../coreaudio_ios/pa_ios_core_utilities.h | 14 +- 7 files changed, 1673 insertions(+), 1675 deletions(-) diff --git a/include/pa_ios_core.h b/include/pa_ios_core.h index e4e0386aa..596d02549 100644 --- a/include/pa_ios_core.h +++ b/include/pa_ios_core.h @@ -28,13 +28,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ @@ -56,4 +56,4 @@ extern "C" { } #endif /** __cplusplus */ -#endif /* PA_IOS_CORE_H */ +#endif /* PA_IOS_CORE_H */ diff --git a/src/hostapi/coreaudio_ios/pa_ios_core.c b/src/hostapi/coreaudio_ios/pa_ios_core.c index c246ed8f6..1f19573da 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core.c +++ b/src/hostapi/coreaudio_ios/pa_ios_core.c @@ -71,22 +71,22 @@ #include "pa_ios_core_blocking.h" /* prototypes for functions declared in this file */ -PaError PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex index); +PaError PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex index); static void Terminate(struct PaUtilHostApiRepresentation *hostApi); -static PaError IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, - const PaStreamParameters * inputParameters, - const PaStreamParameters * outputParameters, - double sampleRate); -static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, - PaStream ** s, - const PaStreamParameters * inputParameters, - const PaStreamParameters * outputParameters, - double sampleRate, - unsigned long framesPerBuffer, - PaStreamFlags streamFlags, - PaStreamCallback * streamCallback, - void *userData); +static PaError IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate); +static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, + PaStream ** s, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback * streamCallback, + void *userData); static PaError CloseStream(PaStream * stream); static PaError StartStream(PaStream * stream); static PaError StopStream(PaStream * stream); @@ -113,25 +113,25 @@ startStopCallback( AudioUnitScope inScope, AudioUnitElement inElement) { - PaIosCoreStream *stream = (PaIosCoreStream *) inRefCon; - UInt32 isRunning; - UInt32 size = sizeof(isRunning); - OSStatus err; - - err = AudioUnitGetProperty(ci, kAudioOutputUnitProperty_IsRunning, inScope, inElement, &isRunning, &size); - assert(!err); - if (err) - isRunning = false; - if (isRunning) - return; - if (stream->inputUnit && stream->outputUnit && stream->inputUnit != stream->outputUnit && ci == stream->inputUnit) - return; - PaStreamFinishedCallback *sfc = stream->streamRepresentation.streamFinishedCallback; - - if (stream->state == STOPPING) - stream->state = STOPPED; - if (sfc) - sfc(stream->streamRepresentation.userData); + PaIosCoreStream *stream = (PaIosCoreStream *) inRefCon; + UInt32 isRunning; + UInt32 size = sizeof(isRunning); + OSStatus err; + + err = AudioUnitGetProperty(ci, kAudioOutputUnitProperty_IsRunning, inScope, inElement, &isRunning, &size); + assert(!err); + if (err) + isRunning = false; + if (isRunning) + return; + if (stream->inputUnit && stream->outputUnit && stream->inputUnit != stream->outputUnit && ci == stream->inputUnit) + return; + PaStreamFinishedCallback *sfc = stream->streamRepresentation.streamFinishedCallback; + + if (stream->state == STOPPING) + stream->state = STOPPED; + if (sfc) + sfc(stream->streamRepresentation.userData); } static void @@ -139,188 +139,188 @@ FillDeviceInfo(PaIosAUHAL * auhalHostApi, PaDeviceInfo * deviceInfo, PaHostApiIndex hostApiIndex) { - memset(deviceInfo, 0, sizeof(PaDeviceInfo)); - - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; - deviceInfo->name = "Default"; - deviceInfo->defaultSampleRate = 48000; - deviceInfo->maxInputChannels = 1; - deviceInfo->maxOutputChannels = 2; - - deviceInfo->defaultLowInputLatency = 0.008; - deviceInfo->defaultHighInputLatency = 0.080; - deviceInfo->defaultLowOutputLatency = 0.008; - deviceInfo->defaultHighOutputLatency = 0.080; + memset(deviceInfo, 0, sizeof(PaDeviceInfo)); + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = "Default"; + deviceInfo->defaultSampleRate = 48000; + deviceInfo->maxInputChannels = 1; + deviceInfo->maxOutputChannels = 2; + + deviceInfo->defaultLowInputLatency = 0.008; + deviceInfo->defaultHighInputLatency = 0.080; + deviceInfo->defaultLowOutputLatency = 0.008; + deviceInfo->defaultHighOutputLatency = 0.080; } PaError PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex hostApiIndex) { - PaError result = paNoError; - PaIosAUHAL *auhalHostApi = NULL; - PaDeviceInfo *deviceInfoArray; - - auhalHostApi = (PaIosAUHAL *) PaUtil_AllocateZeroInitializedMemory(sizeof(PaIosAUHAL)); - if (auhalHostApi == NULL) { - result = paInsufficientMemory; - goto error; - } - auhalHostApi->allocations = PaUtil_CreateAllocationGroup(); - if (auhalHostApi->allocations == NULL) { - result = paInsufficientMemory; - goto error; - } - *hostApi = &auhalHostApi->inheritedHostApiRep; - - (*hostApi)->info.structVersion = 1; - (*hostApi)->info.type = paCoreAudio; - (*hostApi)->info.name = "iOS Audio"; - (*hostApi)->info.defaultInputDevice = 0; - (*hostApi)->info.defaultOutputDevice = 0; - (*hostApi)->info.deviceCount = 1; - - (*hostApi)->deviceInfos = (PaDeviceInfo **) PaUtil_GroupAllocateZeroInitializedMemory( - auhalHostApi->allocations, sizeof(PaDeviceInfo *) * 1); - - if ((*hostApi)->deviceInfos == NULL) { - result = paInsufficientMemory; - goto error; - } - deviceInfoArray = (PaDeviceInfo *) PaUtil_GroupAllocateZeroInitializedMemory( - auhalHostApi->allocations, sizeof(PaDeviceInfo) * 1); - if (deviceInfoArray == NULL) { - result = paInsufficientMemory; - goto error; - } - FillDeviceInfo(auhalHostApi, &deviceInfoArray[0], hostApiIndex); - - (*hostApi)->deviceInfos[0] = &deviceInfoArray[0]; - (*hostApi)->Terminate = Terminate; - (*hostApi)->OpenStream = OpenStream; - (*hostApi)->IsFormatSupported = IsFormatSupported; - - PaUtil_InitializeStreamInterface( - &auhalHostApi->callbackStreamInterface, - CloseStream, StartStream, - StopStream, AbortStream, IsStreamStopped, - IsStreamActive, - GetStreamTime, GetStreamCpuLoad, - PaUtil_DummyRead, PaUtil_DummyWrite, - PaUtil_DummyGetReadAvailable, - PaUtil_DummyGetWriteAvailable); - - PaUtil_InitializeStreamInterface( - &auhalHostApi->blockingStreamInterface, - CloseStream, StartStream, - StopStream, AbortStream, IsStreamStopped, - IsStreamActive, - GetStreamTime, PaUtil_DummyGetCpuLoad, - ReadStream, WriteStream, - GetStreamReadAvailable, - GetStreamWriteAvailable); - - return (result); + PaError result = paNoError; + PaIosAUHAL *auhalHostApi = NULL; + PaDeviceInfo *deviceInfoArray; + + auhalHostApi = (PaIosAUHAL *) PaUtil_AllocateZeroInitializedMemory(sizeof(PaIosAUHAL)); + if (auhalHostApi == NULL) { + result = paInsufficientMemory; + goto error; + } + auhalHostApi->allocations = PaUtil_CreateAllocationGroup(); + if (auhalHostApi->allocations == NULL) { + result = paInsufficientMemory; + goto error; + } + *hostApi = &auhalHostApi->inheritedHostApiRep; + + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paCoreAudio; + (*hostApi)->info.name = "iOS Audio"; + (*hostApi)->info.defaultInputDevice = 0; + (*hostApi)->info.defaultOutputDevice = 0; + (*hostApi)->info.deviceCount = 1; + + (*hostApi)->deviceInfos = (PaDeviceInfo **) PaUtil_GroupAllocateZeroInitializedMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo *) * 1); + + if ((*hostApi)->deviceInfos == NULL) { + result = paInsufficientMemory; + goto error; + } + deviceInfoArray = (PaDeviceInfo *) PaUtil_GroupAllocateZeroInitializedMemory( + auhalHostApi->allocations, sizeof(PaDeviceInfo) * 1); + if (deviceInfoArray == NULL) { + result = paInsufficientMemory; + goto error; + } + FillDeviceInfo(auhalHostApi, &deviceInfoArray[0], hostApiIndex); + + (*hostApi)->deviceInfos[0] = &deviceInfoArray[0]; + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( + &auhalHostApi->callbackStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable); + + PaUtil_InitializeStreamInterface( + &auhalHostApi->blockingStreamInterface, + CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, + IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, + GetStreamReadAvailable, + GetStreamWriteAvailable); + + return (result); error: - if (auhalHostApi != NULL) { - if (auhalHostApi->allocations != NULL) { - PaUtil_FreeAllAllocations(auhalHostApi->allocations); - PaUtil_DestroyAllocationGroup(auhalHostApi->allocations); - } - PaUtil_FreeMemory(auhalHostApi); - } - return (result); + if (auhalHostApi != NULL) { + if (auhalHostApi->allocations != NULL) { + PaUtil_FreeAllAllocations(auhalHostApi->allocations); + PaUtil_DestroyAllocationGroup(auhalHostApi->allocations); + } + PaUtil_FreeMemory(auhalHostApi); + } + return (result); } static void Terminate(struct PaUtilHostApiRepresentation *hostApi) { - PaIosAUHAL *auhalHostApi = (PaIosAUHAL *) hostApi; + PaIosAUHAL *auhalHostApi = (PaIosAUHAL *) hostApi; - if (auhalHostApi->allocations) { - PaUtil_FreeAllAllocations(auhalHostApi->allocations); - PaUtil_DestroyAllocationGroup(auhalHostApi->allocations); - } - PaUtil_FreeMemory(auhalHostApi); + if (auhalHostApi->allocations) { + PaUtil_FreeAllAllocations(auhalHostApi->allocations); + PaUtil_DestroyAllocationGroup(auhalHostApi->allocations); + } + PaUtil_FreeMemory(auhalHostApi); } -static PaError +static PaError IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters * inputParameters, const PaStreamParameters * outputParameters, double sampleRate) { - PaSampleFormat inputSampleFormat; - PaSampleFormat outputSampleFormat; - int inputChannelCount; - int outputChannelCount; - PaError err; - PaStream *s; - - if (inputParameters) { - inputChannelCount = inputParameters->channelCount; - inputSampleFormat = inputParameters->sampleFormat; - - if (inputSampleFormat & paCustomFormat) - return (paSampleFormatNotSupported); - if (inputParameters->device == paUseHostApiSpecificDeviceSpecification) - return (paInvalidDevice); - if (inputChannelCount > hostApi->deviceInfos[inputParameters->device]->maxInputChannels) - return (paInvalidChannelCount); - } else { - inputChannelCount = 0; - } - - if (outputParameters) { - outputChannelCount = outputParameters->channelCount; - outputSampleFormat = outputParameters->sampleFormat; - - if (outputSampleFormat & paCustomFormat) - return (paSampleFormatNotSupported); - if (outputParameters->device == paUseHostApiSpecificDeviceSpecification) - return (paInvalidDevice); - if (outputChannelCount > hostApi->deviceInfos[outputParameters->device]->maxOutputChannels) - return (paInvalidChannelCount); - } else { - outputChannelCount = 0; - } - - err = OpenStream(hostApi, &s, inputParameters, outputParameters, - sampleRate, 1024, 0, (PaStreamCallback *) 1, NULL); - if (err) - return (err); - - (void)CloseStream(s); - - return paFormatIsSupported; + PaSampleFormat inputSampleFormat; + PaSampleFormat outputSampleFormat; + int inputChannelCount; + int outputChannelCount; + PaError err; + PaStream *s; + + if (inputParameters) { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + if (inputSampleFormat & paCustomFormat) + return (paSampleFormatNotSupported); + if (inputParameters->device == paUseHostApiSpecificDeviceSpecification) + return (paInvalidDevice); + if (inputChannelCount > hostApi->deviceInfos[inputParameters->device]->maxInputChannels) + return (paInvalidChannelCount); + } else { + inputChannelCount = 0; + } + + if (outputParameters) { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + if (outputSampleFormat & paCustomFormat) + return (paSampleFormatNotSupported); + if (outputParameters->device == paUseHostApiSpecificDeviceSpecification) + return (paInvalidDevice); + if (outputChannelCount > hostApi->deviceInfos[outputParameters->device]->maxOutputChannels) + return (paInvalidChannelCount); + } else { + outputChannelCount = 0; + } + + err = OpenStream(hostApi, &s, inputParameters, outputParameters, + sampleRate, 1024, 0, (PaStreamCallback *) 1, NULL); + if (err) + return (err); + + (void)CloseStream(s); + + return paFormatIsSupported; } /* ================================================================================= */ static void InitializeDeviceProperties(PaIosCoreDeviceProperties * deviceProperties) { - memset(deviceProperties, 0, sizeof(PaIosCoreDeviceProperties)); - deviceProperties->sampleRate = 1.0; /* Better than random. - * Overwritten by actual - * values later on. */ - deviceProperties->samplePeriod = 1.0 / deviceProperties->sampleRate; + memset(deviceProperties, 0, sizeof(PaIosCoreDeviceProperties)); + deviceProperties->sampleRate = 1.0; /* Better than random. + * Overwritten by actual + * values later on. */ + deviceProperties->samplePeriod = 1.0 / deviceProperties->sampleRate; } -static Float64 +static Float64 CalculateSoftwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) { - UInt32 latencyFrames = deviceProperties->bufferFrameSize + deviceProperties->deviceLatency + deviceProperties->safetyOffset; + UInt32 latencyFrames = deviceProperties->bufferFrameSize + deviceProperties->deviceLatency + deviceProperties->safetyOffset; - return latencyFrames * deviceProperties->samplePeriod; /* same as dividing by - * sampleRate but faster */ + return latencyFrames * deviceProperties->samplePeriod; /* same as dividing by + * sampleRate but faster */ } -static Float64 +static Float64 CalculateHardwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) { - return deviceProperties->deviceLatency * deviceProperties->samplePeriod; /* same as dividing by - * sampleRate but faster */ + return deviceProperties->deviceLatency * deviceProperties->samplePeriod; /* same as dividing by + * sampleRate but faster */ } /* Calculate values used to convert Apple timestamps into PA timestamps @@ -330,28 +330,28 @@ CalculateHardwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDevice static void UpdateTimeStampOffsets(PaIosCoreStream * stream) { - Float64 inputSoftwareLatency = 0.0; - Float64 inputHardwareLatency = 0.0; - Float64 outputSoftwareLatency = 0.0; - Float64 outputHardwareLatency = 0.0; - - if (stream->inputUnit != NULL) { - inputSoftwareLatency = CalculateSoftwareLatencyFromProperties(stream, &stream->inputProperties); - inputHardwareLatency = CalculateHardwareLatencyFromProperties(stream, &stream->inputProperties); - } - if (stream->outputUnit != NULL) { - outputSoftwareLatency = CalculateSoftwareLatencyFromProperties(stream, &stream->outputProperties); - outputHardwareLatency = CalculateHardwareLatencyFromProperties(stream, &stream->outputProperties); - } - /* We only need a mutex around setting these variables as a group. */ - pthread_mutex_lock(&stream->timingInformationMutex); - stream->timestampOffsetCombined = inputSoftwareLatency + outputSoftwareLatency; - stream->timestampOffsetInputDevice = inputHardwareLatency; - stream->timestampOffsetOutputDevice = outputHardwareLatency; - pthread_mutex_unlock(&stream->timingInformationMutex); + Float64 inputSoftwareLatency = 0.0; + Float64 inputHardwareLatency = 0.0; + Float64 outputSoftwareLatency = 0.0; + Float64 outputHardwareLatency = 0.0; + + if (stream->inputUnit != NULL) { + inputSoftwareLatency = CalculateSoftwareLatencyFromProperties(stream, &stream->inputProperties); + inputHardwareLatency = CalculateHardwareLatencyFromProperties(stream, &stream->inputProperties); + } + if (stream->outputUnit != NULL) { + outputSoftwareLatency = CalculateSoftwareLatencyFromProperties(stream, &stream->outputProperties); + outputHardwareLatency = CalculateHardwareLatencyFromProperties(stream, &stream->outputProperties); + } + /* We only need a mutex around setting these variables as a group. */ + pthread_mutex_lock(&stream->timingInformationMutex); + stream->timestampOffsetCombined = inputSoftwareLatency + outputSoftwareLatency; + stream->timestampOffsetInputDevice = inputHardwareLatency; + stream->timestampOffsetOutputDevice = outputHardwareLatency; + pthread_mutex_unlock(&stream->timingInformationMutex); } -static PaError +static PaError OpenAndSetupOneAudioUnit( const PaIosCoreStream * stream, const PaStreamParameters * inStreamParams, @@ -364,173 +364,173 @@ OpenAndSetupOneAudioUnit( const double sampleRate, void *refCon) { - AudioComponentDescription desc; - AudioComponent comp; - AudioStreamBasicDescription desiredFormat; - OSStatus result = noErr; - PaError paResult = paNoError; - int line = 0; - UInt32 callbackKey; - AURenderCallbackStruct rcbs; - - if (!inStreamParams && !outStreamParams) { - *audioUnit = NULL; - return paNoError; - } - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_RemoteIO; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - comp = AudioComponentFindNext(NULL, &desc); - if (comp == NULL) { - *audioUnit = NULL; - return (paUnanticipatedHostError); - } - result = AudioComponentInstanceNew(comp, audioUnit); - if (result) { - *audioUnit = NULL; - return (ERR(result)); - } -#define ERR_WRAP(ios_err) do { \ - result = ios_err; \ - line = __LINE__ ; \ - if (result != noErr) \ - goto error; \ + AudioComponentDescription desc; + AudioComponent comp; + AudioStreamBasicDescription desiredFormat; + OSStatus result = noErr; + PaError paResult = paNoError; + int line = 0; + UInt32 callbackKey; + AURenderCallbackStruct rcbs; + + if (!inStreamParams && !outStreamParams) { + *audioUnit = NULL; + return paNoError; + } + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_RemoteIO; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = AudioComponentFindNext(NULL, &desc); + if (comp == NULL) { + *audioUnit = NULL; + return (paUnanticipatedHostError); + } + result = AudioComponentInstanceNew(comp, audioUnit); + if (result) { + *audioUnit = NULL; + return (ERR(result)); + } +#define ERR_WRAP(ios_err) do { \ + result = ios_err; \ + line = __LINE__ ; \ + if (result != noErr) \ + goto error; \ } while(0) - if (inStreamParams) { - UInt32 enableIO = 1; - - ERR_WRAP(AudioUnitSetProperty(*audioUnit, - kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Input, - INPUT_ELEMENT, - &enableIO, - sizeof(enableIO))); - } - if (!outStreamParams) { - UInt32 enableIO = 0; - - ERR_WRAP(AudioUnitSetProperty(*audioUnit, - kAudioOutputUnitProperty_EnableIO, - kAudioUnitScope_Output, - OUTPUT_ELEMENT, - &enableIO, - sizeof(enableIO))); - } - if (inStreamParams && outStreamParams) { - assert(outStreamParams->device == inStreamParams->device); - } - ERR_WRAP(AudioUnitAddPropertyListener(*audioUnit, - kAudioOutputUnitProperty_IsRunning, - startStopCallback, - (void *)stream)); - - memset(&desiredFormat, 0, sizeof(desiredFormat)); - desiredFormat.mFormatID = kAudioFormatLinearPCM; - desiredFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; - desiredFormat.mFramesPerPacket = 1; - desiredFormat.mBitsPerChannel = sizeof(float) * 8; - - result = 0; - - if (outStreamParams) { - UInt32 value = kAudioConverterQuality_High; - - ERR_WRAP(AudioUnitSetProperty(*audioUnit, - kAudioUnitProperty_RenderQuality, - kAudioUnitScope_Global, - OUTPUT_ELEMENT, - &value, - sizeof(value))); - } - /* now set the format on the Audio Units. */ - if (outStreamParams) { - desiredFormat.mSampleRate = sampleRate; - desiredFormat.mBytesPerPacket = sizeof(float) * outStreamParams->channelCount; - desiredFormat.mBytesPerFrame = sizeof(float) * outStreamParams->channelCount; - desiredFormat.mChannelsPerFrame = outStreamParams->channelCount; - ERR_WRAP(AudioUnitSetProperty(*audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - OUTPUT_ELEMENT, - &desiredFormat, - sizeof(AudioStreamBasicDescription))); - } - if (inStreamParams) { - AudioStreamBasicDescription sourceFormat; - UInt32 size = sizeof(AudioStreamBasicDescription); - - /* keep the sample rate of the device, or we confuse AUHAL */ - ERR_WRAP(AudioUnitGetProperty(*audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - INPUT_ELEMENT, - &sourceFormat, - &size)); - desiredFormat.mSampleRate = sampleRate; - desiredFormat.mBytesPerPacket = sizeof(float) * inStreamParams->channelCount; - desiredFormat.mBytesPerFrame = sizeof(float) * inStreamParams->channelCount; - desiredFormat.mChannelsPerFrame = inStreamParams->channelCount; - ERR_WRAP(AudioUnitSetProperty(*audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, - INPUT_ELEMENT, - &desiredFormat, - sizeof(AudioStreamBasicDescription))); - } - if (outStreamParams) { - UInt32 size = sizeof(*actualOutputFramesPerBuffer); - - ERR_WRAP(AudioUnitSetProperty(*audioUnit, - kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Input, - OUTPUT_ELEMENT, - &requestedFramesPerBuffer, - sizeof(requestedFramesPerBuffer))); - ERR_WRAP(AudioUnitGetProperty(*audioUnit, - kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Global, - OUTPUT_ELEMENT, - actualOutputFramesPerBuffer, - &size)); - } - if (inStreamParams) { - ERR_WRAP(AudioUnitSetProperty(*audioUnit, - kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Output, - INPUT_ELEMENT, - &requestedFramesPerBuffer, - sizeof(requestedFramesPerBuffer))); - - *actualInputFramesPerBuffer = requestedFramesPerBuffer; - } - callbackKey = outStreamParams ? kAudioUnitProperty_SetRenderCallback : - kAudioOutputUnitProperty_SetInputCallback; - rcbs.inputProc = AudioIOProc; - rcbs.inputProcRefCon = refCon; - ERR_WRAP(AudioUnitSetProperty( - *audioUnit, - callbackKey, - kAudioUnitScope_Output, - outStreamParams ? OUTPUT_ELEMENT : INPUT_ELEMENT, - &rcbs, - sizeof(rcbs))); - - /* initialize the audio unit */ - ERR_WRAP(AudioUnitInitialize(*audioUnit)); - - return (paNoError); + if (inStreamParams) { + UInt32 enableIO = 1; + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &enableIO, + sizeof(enableIO))); + } + if (!outStreamParams) { + UInt32 enableIO = 0; + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + OUTPUT_ELEMENT, + &enableIO, + sizeof(enableIO))); + } + if (inStreamParams && outStreamParams) { + assert(outStreamParams->device == inStreamParams->device); + } + ERR_WRAP(AudioUnitAddPropertyListener(*audioUnit, + kAudioOutputUnitProperty_IsRunning, + startStopCallback, + (void *)stream)); + + memset(&desiredFormat, 0, sizeof(desiredFormat)); + desiredFormat.mFormatID = kAudioFormatLinearPCM; + desiredFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; + desiredFormat.mFramesPerPacket = 1; + desiredFormat.mBitsPerChannel = sizeof(float) * 8; + + result = 0; + + if (outStreamParams) { + UInt32 value = kAudioConverterQuality_High; + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_RenderQuality, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + &value, + sizeof(value))); + } + /* now set the format on the Audio Units. */ + if (outStreamParams) { + desiredFormat.mSampleRate = sampleRate; + desiredFormat.mBytesPerPacket = sizeof(float) * outStreamParams->channelCount; + desiredFormat.mBytesPerFrame = sizeof(float) * outStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = outStreamParams->channelCount; + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription))); + } + if (inStreamParams) { + AudioStreamBasicDescription sourceFormat; + UInt32 size = sizeof(AudioStreamBasicDescription); + + /* keep the sample rate of the device, or we confuse AUHAL */ + ERR_WRAP(AudioUnitGetProperty(*audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + INPUT_ELEMENT, + &sourceFormat, + &size)); + desiredFormat.mSampleRate = sampleRate; + desiredFormat.mBytesPerPacket = sizeof(float) * inStreamParams->channelCount; + desiredFormat.mBytesPerFrame = sizeof(float) * inStreamParams->channelCount; + desiredFormat.mChannelsPerFrame = inStreamParams->channelCount; + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &desiredFormat, + sizeof(AudioStreamBasicDescription))); + } + if (outStreamParams) { + UInt32 size = sizeof(*actualOutputFramesPerBuffer); + + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Input, + OUTPUT_ELEMENT, + &requestedFramesPerBuffer, + sizeof(requestedFramesPerBuffer))); + ERR_WRAP(AudioUnitGetProperty(*audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + OUTPUT_ELEMENT, + actualOutputFramesPerBuffer, + &size)); + } + if (inStreamParams) { + ERR_WRAP(AudioUnitSetProperty(*audioUnit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Output, + INPUT_ELEMENT, + &requestedFramesPerBuffer, + sizeof(requestedFramesPerBuffer))); + + *actualInputFramesPerBuffer = requestedFramesPerBuffer; + } + callbackKey = outStreamParams ? kAudioUnitProperty_SetRenderCallback : + kAudioOutputUnitProperty_SetInputCallback; + rcbs.inputProc = AudioIOProc; + rcbs.inputProcRefCon = refCon; + ERR_WRAP(AudioUnitSetProperty( + *audioUnit, + callbackKey, + kAudioUnitScope_Output, + outStreamParams ? OUTPUT_ELEMENT : INPUT_ELEMENT, + &rcbs, + sizeof(rcbs))); + + /* initialize the audio unit */ + ERR_WRAP(AudioUnitInitialize(*audioUnit)); + + return (paNoError); #undef ERR_WRAP error: - AudioComponentInstanceDispose(*audioUnit); - *audioUnit = NULL; - if (result) - return PaIosCore_SetError(result, line, 1); - return (paResult); + AudioComponentInstanceDispose(*audioUnit); + *audioUnit = NULL; + if (result) + return PaIosCore_SetError(result, line, 1); + return (paResult); } static long @@ -540,50 +540,50 @@ computeRingBufferSize(const PaStreamParameters * inputParameters, long outputFramesPerBuffer, double sampleRate) { - long ringSize; - int index; - int i; - double latency; - long framesPerBuffer; - - assert(inputParameters || outputParameters); - - if (outputParameters && inputParameters) { - latency = MAX(inputParameters->suggestedLatency, outputParameters->suggestedLatency); - framesPerBuffer = MAX(inputFramesPerBuffer, outputFramesPerBuffer); - } else if (outputParameters) { - latency = outputParameters->suggestedLatency; - framesPerBuffer = outputFramesPerBuffer; - } else { - latency = inputParameters->suggestedLatency; - framesPerBuffer = inputFramesPerBuffer; - } - - ringSize = (long)(latency * sampleRate * 2 + .5); - - if (ringSize < framesPerBuffer * 3) - ringSize = framesPerBuffer * 3; - - /* make sure it's at least 4 */ - ringSize = MAX(ringSize, 4); - - /* round up to the next power of 2 */ - index = -1; - - for (i = 0; i != (sizeof(long) * 8); ++i) - if ((ringSize >> i) & 0x01) - index = i; - assert(index > 0); - - if (ringSize <= (0x01 << index)) - ringSize = 0x01 << index; - else - ringSize = 0x01 << (index + 1); - - return ringSize; + long ringSize; + int index; + int i; + double latency; + long framesPerBuffer; + + assert(inputParameters || outputParameters); + + if (outputParameters && inputParameters) { + latency = MAX(inputParameters->suggestedLatency, outputParameters->suggestedLatency); + framesPerBuffer = MAX(inputFramesPerBuffer, outputFramesPerBuffer); + } else if (outputParameters) { + latency = outputParameters->suggestedLatency; + framesPerBuffer = outputFramesPerBuffer; + } else { + latency = inputParameters->suggestedLatency; + framesPerBuffer = inputFramesPerBuffer; + } + + ringSize = (long)(latency * sampleRate * 2 + .5); + + if (ringSize < framesPerBuffer * 3) + ringSize = framesPerBuffer * 3; + + /* make sure it's at least 4 */ + ringSize = MAX(ringSize, 4); + + /* round up to the next power of 2 */ + index = -1; + + for (i = 0; i != (sizeof(long) * 8); ++i) + if ((ringSize >> i) & 0x01) + index = i; + assert(index > 0); + + if (ringSize <= (0x01 << index)) + ringSize = 0x01 << index; + else + ringSize = 0x01 << (index + 1); + + return ringSize; } -static PaError +static PaError OpenStream(struct PaUtilHostApiRepresentation *hostApi, PaStream ** s, const PaStreamParameters * inputParameters, @@ -594,344 +594,344 @@ OpenStream(struct PaUtilHostApiRepresentation *hostApi, PaStreamCallback * streamCallback, void *userData) { - PaError result = paNoError; - PaIosAUHAL *auhalHostApi = (PaIosAUHAL *) hostApi; - PaIosCoreStream *stream = 0; - int inputChannelCount; - int outputChannelCount; - PaSampleFormat inputSampleFormat; - PaSampleFormat outputSampleFormat; - PaSampleFormat hostInputSampleFormat; - PaSampleFormat hostOutputSampleFormat; - - UInt32 inputLatencyFrames = 0; - UInt32 outputLatencyFrames = 0; - - if (requestedFramesPerBuffer == paFramesPerBufferUnspecified) - requestedFramesPerBuffer = sampleRate * 0.016; - - if (inputParameters) { - inputChannelCount = inputParameters->channelCount; - inputSampleFormat = inputParameters->sampleFormat; - - /* @todo Blocking read/write on Ios is not yet supported. */ - if (!streamCallback && inputSampleFormat & paNonInterleaved) { - return paSampleFormatNotSupported; - } - /* - * unless alternate device specification is supported, - * reject the use of paUseHostApiSpecificDeviceSpecification - */ - - if (inputParameters->device == paUseHostApiSpecificDeviceSpecification) - return paInvalidDevice; - - /* check that input device can support inputChannelCount */ - if (inputChannelCount > hostApi->deviceInfos[inputParameters->device]->maxInputChannels) - return paInvalidChannelCount; - - /* Host supports interleaved float32 */ - hostInputSampleFormat = paFloat32; - } else { - inputChannelCount = 0; - inputSampleFormat = hostInputSampleFormat = paFloat32; - } - - if (outputParameters) { - outputChannelCount = outputParameters->channelCount; - outputSampleFormat = outputParameters->sampleFormat; - - /* @todo Blocking read/write on Ios is not yet supported. */ - if (!streamCallback && outputSampleFormat & paNonInterleaved) { - return paSampleFormatNotSupported; - } - /* - * unless alternate device specification is supported, - * reject the use of paUseHostApiSpecificDeviceSpecification - */ - - if (outputParameters->device == paUseHostApiSpecificDeviceSpecification) - return paInvalidDevice; - - /* check that output device can support inputChannelCount */ - if (outputChannelCount > hostApi->deviceInfos[outputParameters->device]->maxOutputChannels) - return paInvalidChannelCount; - - /* Host supports interleaved float32 */ - hostOutputSampleFormat = paFloat32; - } else { - outputChannelCount = 0; - outputSampleFormat = hostOutputSampleFormat = paFloat32; - } - - /* validate platform specific flags */ - if ((streamFlags & paPlatformSpecificFlags) != 0) - return paInvalidFlag; /* unexpected platform specific flag */ - - stream = (PaIosCoreStream *) PaUtil_AllocateZeroInitializedMemory(sizeof(PaIosCoreStream)); - if (!stream) { - result = paInsufficientMemory; - goto error; - } - /* - * If we fail after this point, we my be left in a bad state, with - * some data structures setup and others not. So, first thing we do - * is initialize everything so that if we fail, we know what hasn't - * been touched. - */ - memset(stream, 0, sizeof(PaIosCoreStream)); - - if (streamCallback) { - PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, - &auhalHostApi->callbackStreamInterface, - streamCallback, userData); - } else { - PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, - &auhalHostApi->blockingStreamInterface, - BlioCallback, &stream->blio); - } - - PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); - - if (inputParameters && outputParameters && outputParameters->device == inputParameters->device) { - /* full duplex. One device. */ - UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; - UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; - - result = OpenAndSetupOneAudioUnit(stream, - inputParameters, - outputParameters, - requestedFramesPerBuffer, - &inputFramesPerBuffer, - &outputFramesPerBuffer, - auhalHostApi, - &stream->inputUnit, - sampleRate, - stream); - stream->inputFramesPerBuffer = inputFramesPerBuffer; - stream->outputFramesPerBuffer = outputFramesPerBuffer; - stream->outputUnit = stream->inputUnit; - if (result != paNoError) - goto error; - } else { - /* full duplex, different devices OR simplex */ - UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; - UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; - - result = OpenAndSetupOneAudioUnit(stream, - NULL, - outputParameters, - requestedFramesPerBuffer, - NULL, - &outputFramesPerBuffer, - auhalHostApi, - &stream->outputUnit, - sampleRate, - stream); - if (result != paNoError) - goto error; - result = OpenAndSetupOneAudioUnit(stream, - inputParameters, - NULL, - requestedFramesPerBuffer, - &inputFramesPerBuffer, - NULL, - auhalHostApi, - &stream->inputUnit, - sampleRate, - stream); - if (result != paNoError) - goto error; - stream->inputFramesPerBuffer = inputFramesPerBuffer; - stream->outputFramesPerBuffer = outputFramesPerBuffer; - } - - inputLatencyFrames += stream->inputFramesPerBuffer; - outputLatencyFrames += stream->outputFramesPerBuffer; - - if (stream->inputUnit) { - const size_t szfl = sizeof(float); - - /* setup the AudioBufferList used for input */ - memset(&stream->inputAudioBufferList, 0, sizeof(AudioBufferList)); - stream->inputAudioBufferList.mNumberBuffers = 1; - stream->inputAudioBufferList.mBuffers[0].mNumberChannels - = inputChannelCount; - stream->inputAudioBufferList.mBuffers[0].mDataByteSize - = stream->inputFramesPerBuffer * inputChannelCount * szfl; - stream->inputAudioBufferList.mBuffers[0].mData - = (float *)calloc( - stream->inputFramesPerBuffer * inputChannelCount, - szfl); - if (!stream->inputAudioBufferList.mBuffers[0].mData) { - result = paInsufficientMemory; - goto error; - } - - /* - * If input and output devs are different we also need - * a ring buffer to store input data while waiting for - * output data: - */ - if (stream->outputUnit && (stream->inputUnit != stream->outputUnit)) { - /* - * May want the ringSize or initial position in ring - * buffer to depend somewhat on sample rate change - */ - void *data; - long ringSize; - - ringSize = computeRingBufferSize(inputParameters, - outputParameters, - stream->inputFramesPerBuffer, - stream->outputFramesPerBuffer, - sampleRate); - - /* - * now, we need to allocate memory for the ring - * buffer - */ - data = calloc(ringSize, szfl * inputParameters->channelCount); - if (!data) { - result = paInsufficientMemory; - goto error; - } - /* now we can initialize the ring buffer */ - result = PaUtil_InitializeRingBuffer(&stream->inputRingBuffer, - szfl * inputParameters->channelCount, ringSize, data); - if (result != 0) { - /* - * The only reason this should fail is if - * ringSize is not a power of 2, which we do - * not anticipate happening. - */ - result = paUnanticipatedHostError; - free(data); - goto error; - } - /* - * advance the read point a little, so we are - * reading from the middle of the buffer - */ - if (stream->outputUnit) { - PaUtil_AdvanceRingBufferWriteIndex(&stream->inputRingBuffer, - ringSize / RING_BUFFER_ADVANCE_DENOMINATOR); - } - - /* - * Just adds to input latency between input device - * and PA full duplex callback. - */ - inputLatencyFrames += ringSize; - } - } - - /* -- initialize Blio Buffer Processors -- */ - if (!streamCallback) { - long ringSize; - - ringSize = computeRingBufferSize(inputParameters, - outputParameters, - stream->inputFramesPerBuffer, - stream->outputFramesPerBuffer, - sampleRate); - result = initializeBlioRingBuffers(&stream->blio, - inputParameters ? inputParameters->sampleFormat : 0, - outputParameters ? outputParameters->sampleFormat : 0, - ringSize, - inputParameters ? inputChannelCount : 0, - outputParameters ? outputChannelCount : 0); - if (result != paNoError) - goto error; - - inputLatencyFrames += ringSize; - outputLatencyFrames += ringSize; - - } - /* -- initialize Buffer Processor -- */ - { - size_t maxHostFrames = stream->inputFramesPerBuffer; - - if (stream->outputFramesPerBuffer > maxHostFrames) - maxHostFrames = stream->outputFramesPerBuffer; - - result = PaUtil_InitializeBufferProcessor(&stream->bufferProcessor, - inputChannelCount, inputSampleFormat, - hostInputSampleFormat, - outputChannelCount, outputSampleFormat, - hostOutputSampleFormat, - sampleRate, - streamFlags, - requestedFramesPerBuffer, - maxHostFrames, - paUtilBoundedHostBufferSize, - streamCallback ? streamCallback : BlioCallback, - streamCallback ? userData : &stream->blio); - if (result != paNoError) - goto error; - } - stream->bufferProcessorIsInitialized = TRUE; - - /* Calculate actual latency from the sum of individual latencies. */ - if (inputParameters) { - inputLatencyFrames += PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor); - stream->streamRepresentation.streamInfo.inputLatency = inputLatencyFrames / sampleRate; - } else { - stream->streamRepresentation.streamInfo.inputLatency = 0.0; - } - - if (outputParameters) { - outputLatencyFrames += PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor); - stream->streamRepresentation.streamInfo.outputLatency = outputLatencyFrames / sampleRate; - } else { - stream->streamRepresentation.streamInfo.outputLatency = 0.0; - } - - stream->streamRepresentation.streamInfo.sampleRate = sampleRate; - - stream->sampleRate = sampleRate; - - stream->userInChan = inputChannelCount; - stream->userOutChan = outputChannelCount; - - /* Setup property listeners for timestamp and latency calculations. */ - pthread_mutex_init(&stream->timingInformationMutex, NULL); - stream->timingInformationMutexIsInitialized = 1; - InitializeDeviceProperties(&stream->inputProperties); - InitializeDeviceProperties(&stream->outputProperties); - - UpdateTimeStampOffsets(stream); - /* Setup timestamp copies to be used by audio callback */ - stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined; - stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice; - stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice; - - stream->state = STOPPED; - stream->xrunFlags = 0; - - *s = (PaStream *) stream; - - return result; + PaError result = paNoError; + PaIosAUHAL *auhalHostApi = (PaIosAUHAL *) hostApi; + PaIosCoreStream *stream = 0; + int inputChannelCount; + int outputChannelCount; + PaSampleFormat inputSampleFormat; + PaSampleFormat outputSampleFormat; + PaSampleFormat hostInputSampleFormat; + PaSampleFormat hostOutputSampleFormat; + + UInt32 inputLatencyFrames = 0; + UInt32 outputLatencyFrames = 0; + + if (requestedFramesPerBuffer == paFramesPerBufferUnspecified) + requestedFramesPerBuffer = sampleRate * 0.016; + + if (inputParameters) { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* @todo Blocking read/write on Ios is not yet supported. */ + if (!streamCallback && inputSampleFormat & paNonInterleaved) { + return paSampleFormatNotSupported; + } + /* + * unless alternate device specification is supported, + * reject the use of paUseHostApiSpecificDeviceSpecification + */ + + if (inputParameters->device == paUseHostApiSpecificDeviceSpecification) + return paInvalidDevice; + + /* check that input device can support inputChannelCount */ + if (inputChannelCount > hostApi->deviceInfos[inputParameters->device]->maxInputChannels) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostInputSampleFormat = paFloat32; + } else { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paFloat32; + } + + if (outputParameters) { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* @todo Blocking read/write on Ios is not yet supported. */ + if (!streamCallback && outputSampleFormat & paNonInterleaved) { + return paSampleFormatNotSupported; + } + /* + * unless alternate device specification is supported, + * reject the use of paUseHostApiSpecificDeviceSpecification + */ + + if (outputParameters->device == paUseHostApiSpecificDeviceSpecification) + return paInvalidDevice; + + /* check that output device can support inputChannelCount */ + if (outputChannelCount > hostApi->deviceInfos[outputParameters->device]->maxOutputChannels) + return paInvalidChannelCount; + + /* Host supports interleaved float32 */ + hostOutputSampleFormat = paFloat32; + } else { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paFloat32; + } + + /* validate platform specific flags */ + if ((streamFlags & paPlatformSpecificFlags) != 0) + return paInvalidFlag; /* unexpected platform specific flag */ + + stream = (PaIosCoreStream *) PaUtil_AllocateZeroInitializedMemory(sizeof(PaIosCoreStream)); + if (!stream) { + result = paInsufficientMemory; + goto error; + } + /* + * If we fail after this point, we my be left in a bad state, with + * some data structures setup and others not. So, first thing we do + * is initialize everything so that if we fail, we know what hasn't + * been touched. + */ + memset(stream, 0, sizeof(PaIosCoreStream)); + + if (streamCallback) { + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &auhalHostApi->callbackStreamInterface, + streamCallback, userData); + } else { + PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, + &auhalHostApi->blockingStreamInterface, + BlioCallback, &stream->blio); + } + + PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); + + if (inputParameters && outputParameters && outputParameters->device == inputParameters->device) { + /* full duplex. One device. */ + UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; + UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; + + result = OpenAndSetupOneAudioUnit(stream, + inputParameters, + outputParameters, + requestedFramesPerBuffer, + &inputFramesPerBuffer, + &outputFramesPerBuffer, + auhalHostApi, + &stream->inputUnit, + sampleRate, + stream); + stream->inputFramesPerBuffer = inputFramesPerBuffer; + stream->outputFramesPerBuffer = outputFramesPerBuffer; + stream->outputUnit = stream->inputUnit; + if (result != paNoError) + goto error; + } else { + /* full duplex, different devices OR simplex */ + UInt32 outputFramesPerBuffer = (UInt32) stream->outputFramesPerBuffer; + UInt32 inputFramesPerBuffer = (UInt32) stream->inputFramesPerBuffer; + + result = OpenAndSetupOneAudioUnit(stream, + NULL, + outputParameters, + requestedFramesPerBuffer, + NULL, + &outputFramesPerBuffer, + auhalHostApi, + &stream->outputUnit, + sampleRate, + stream); + if (result != paNoError) + goto error; + result = OpenAndSetupOneAudioUnit(stream, + inputParameters, + NULL, + requestedFramesPerBuffer, + &inputFramesPerBuffer, + NULL, + auhalHostApi, + &stream->inputUnit, + sampleRate, + stream); + if (result != paNoError) + goto error; + stream->inputFramesPerBuffer = inputFramesPerBuffer; + stream->outputFramesPerBuffer = outputFramesPerBuffer; + } + + inputLatencyFrames += stream->inputFramesPerBuffer; + outputLatencyFrames += stream->outputFramesPerBuffer; + + if (stream->inputUnit) { + const size_t szfl = sizeof(float); + + /* setup the AudioBufferList used for input */ + memset(&stream->inputAudioBufferList, 0, sizeof(AudioBufferList)); + stream->inputAudioBufferList.mNumberBuffers = 1; + stream->inputAudioBufferList.mBuffers[0].mNumberChannels + = inputChannelCount; + stream->inputAudioBufferList.mBuffers[0].mDataByteSize + = stream->inputFramesPerBuffer * inputChannelCount * szfl; + stream->inputAudioBufferList.mBuffers[0].mData + = (float *)calloc( + stream->inputFramesPerBuffer * inputChannelCount, + szfl); + if (!stream->inputAudioBufferList.mBuffers[0].mData) { + result = paInsufficientMemory; + goto error; + } + + /* + * If input and output devs are different we also need + * a ring buffer to store input data while waiting for + * output data: + */ + if (stream->outputUnit && (stream->inputUnit != stream->outputUnit)) { + /* + * May want the ringSize or initial position in ring + * buffer to depend somewhat on sample rate change + */ + void *data; + long ringSize; + + ringSize = computeRingBufferSize(inputParameters, + outputParameters, + stream->inputFramesPerBuffer, + stream->outputFramesPerBuffer, + sampleRate); + + /* + * now, we need to allocate memory for the ring + * buffer + */ + data = calloc(ringSize, szfl * inputParameters->channelCount); + if (!data) { + result = paInsufficientMemory; + goto error; + } + /* now we can initialize the ring buffer */ + result = PaUtil_InitializeRingBuffer(&stream->inputRingBuffer, + szfl * inputParameters->channelCount, ringSize, data); + if (result != 0) { + /* + * The only reason this should fail is if + * ringSize is not a power of 2, which we do + * not anticipate happening. + */ + result = paUnanticipatedHostError; + free(data); + goto error; + } + /* + * advance the read point a little, so we are + * reading from the middle of the buffer + */ + if (stream->outputUnit) { + PaUtil_AdvanceRingBufferWriteIndex(&stream->inputRingBuffer, + ringSize / RING_BUFFER_ADVANCE_DENOMINATOR); + } + + /* + * Just adds to input latency between input device + * and PA full duplex callback. + */ + inputLatencyFrames += ringSize; + } + } + + /* -- initialize Blio Buffer Processors -- */ + if (!streamCallback) { + long ringSize; + + ringSize = computeRingBufferSize(inputParameters, + outputParameters, + stream->inputFramesPerBuffer, + stream->outputFramesPerBuffer, + sampleRate); + result = initializeBlioRingBuffers(&stream->blio, + inputParameters ? inputParameters->sampleFormat : 0, + outputParameters ? outputParameters->sampleFormat : 0, + ringSize, + inputParameters ? inputChannelCount : 0, + outputParameters ? outputChannelCount : 0); + if (result != paNoError) + goto error; + + inputLatencyFrames += ringSize; + outputLatencyFrames += ringSize; + + } + /* -- initialize Buffer Processor -- */ + { + size_t maxHostFrames = stream->inputFramesPerBuffer; + + if (stream->outputFramesPerBuffer > maxHostFrames) + maxHostFrames = stream->outputFramesPerBuffer; + + result = PaUtil_InitializeBufferProcessor(&stream->bufferProcessor, + inputChannelCount, inputSampleFormat, + hostInputSampleFormat, + outputChannelCount, outputSampleFormat, + hostOutputSampleFormat, + sampleRate, + streamFlags, + requestedFramesPerBuffer, + maxHostFrames, + paUtilBoundedHostBufferSize, + streamCallback ? streamCallback : BlioCallback, + streamCallback ? userData : &stream->blio); + if (result != paNoError) + goto error; + } + stream->bufferProcessorIsInitialized = TRUE; + + /* Calculate actual latency from the sum of individual latencies. */ + if (inputParameters) { + inputLatencyFrames += PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.inputLatency = inputLatencyFrames / sampleRate; + } else { + stream->streamRepresentation.streamInfo.inputLatency = 0.0; + } + + if (outputParameters) { + outputLatencyFrames += PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.outputLatency = outputLatencyFrames / sampleRate; + } else { + stream->streamRepresentation.streamInfo.outputLatency = 0.0; + } + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->sampleRate = sampleRate; + + stream->userInChan = inputChannelCount; + stream->userOutChan = outputChannelCount; + + /* Setup property listeners for timestamp and latency calculations. */ + pthread_mutex_init(&stream->timingInformationMutex, NULL); + stream->timingInformationMutexIsInitialized = 1; + InitializeDeviceProperties(&stream->inputProperties); + InitializeDeviceProperties(&stream->outputProperties); + + UpdateTimeStampOffsets(stream); + /* Setup timestamp copies to be used by audio callback */ + stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined; + stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice; + stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice; + + stream->state = STOPPED; + stream->xrunFlags = 0; + + *s = (PaStream *) stream; + + return result; error: - CloseStream(stream); - return result; + CloseStream(stream); + return result; } /* Convert to nanoseconds and then to seconds */ -#define HOST_TIME_TO_PA_TIME(x) ({ \ - mach_timebase_info_data_t info; \ - mach_timebase_info(&info); \ - ((x) * (double)info.numer / (double)info.denom) * 1.0E-09; \ - }) +#define HOST_TIME_TO_PA_TIME(x) ({ \ + mach_timebase_info_data_t info; \ + mach_timebase_info(&info); \ + ((x) * (double)info.numer / (double)info.denom) * 1.0E-09; \ + }) PaTime GetStreamTime(PaStream * s) { - return (HOST_TIME_TO_PA_TIME(mach_absolute_time())); + return (HOST_TIME_TO_PA_TIME(mach_absolute_time())); } -#define RING_BUFFER_EMPTY 1000 +#define RING_BUFFER_EMPTY 1000 /* * Called by the AudioUnit API to process audio from the sound card. @@ -945,485 +945,485 @@ AudioIOProc(void *inRefCon, UInt32 inNumberFrames, AudioBufferList * ioData) { - size_t framesProcessed = 0; - PaStreamCallbackTimeInfo timeInfo = {}; - PaIosCoreStream *stream = (PaIosCoreStream *) inRefCon; - const bool isRender = (inBusNumber == OUTPUT_ELEMENT); - int callbackResult = paContinue; - double hostTimeStampInPaTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime); - - PaUtil_BeginCpuLoadMeasurement(&stream->cpuLoadMeasurer); - - /* compute PaStreamCallbackTimeInfo */ - if (pthread_mutex_trylock(&stream->timingInformationMutex) == 0) { - /* snapshot the ioproc copy of timing information */ - stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined; - stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice; - stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice; - pthread_mutex_unlock(&stream->timingInformationMutex); - } - - /* - * For timeInfo.currentTime we could calculate current time - * backwards from the HAL audio output time to give a more accurate - * impression of the current timeslice but it doesn't seem worth it - * at the moment since other PA host APIs don't do any better. - */ - timeInfo.currentTime = HOST_TIME_TO_PA_TIME(mach_absolute_time()); - - /* - * For an input HAL AU, inTimeStamp is the time the samples - * are received from the hardware, for an output HAL AU - * inTimeStamp is the time the samples are sent to the - * hardware. PA expresses timestamps in terms of when the - * samples enter the ADC or leave the DAC so we add or - * subtract kAudioDevicePropertyLatency below. - */ - - /* - * FIXME: not sure what to do below if the host timestamps aren't - * valid (kAudioTimeStampHostTimeValid isn't set) Could ask on CA - * mailing list if it is possible for it not to be set. If so, could - * probably grab a now timestamp at the top and compute from there - * (modulo scheduling jitter) or ask on mailing list for other - * options. - */ - - if (isRender) { - if (stream->inputUnit) { - /* full duplex */ - timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - - (stream->timestampOffsetCombined_ioProcCopy + stream->timestampOffsetInputDevice_ioProcCopy); - timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy; - } else { - /* output only */ - timeInfo.inputBufferAdcTime = 0; - timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy; - } - } else { - /* input only */ - timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - stream->timestampOffsetInputDevice_ioProcCopy; - timeInfo.outputBufferDacTime = 0; - } - - if (isRender && stream->inputUnit == stream->outputUnit) { - /* - * Full Duplex, One Device - * - * This is the lowest latency case, and also the simplest. - * Input data and output data are available at the same - * time. we do not use the input SR converter or the input - * ring buffer. - */ - OSStatus err; - size_t bytesPerFrame = sizeof(float) * ioData->mBuffers[0].mNumberChannels; - size_t frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame; - size_t total = 0; - - assert(ioData->mNumberBuffers == 1); - assert(ioData->mBuffers[0].mNumberChannels == stream->userOutChan); - - while (1) { - size_t delta = frames - total; - if (delta > stream->inputFramesPerBuffer) - delta = stream->inputFramesPerBuffer; - if (delta > stream->outputFramesPerBuffer) - delta = stream->outputFramesPerBuffer; - if (delta == 0) - break; - - PaUtil_BeginBufferProcessing(&stream->bufferProcessor, - &timeInfo, stream->xrunFlags); - stream->xrunFlags = 0; - - stream->inputAudioBufferList.mBuffers[0].mDataByteSize = - delta * bytesPerFrame; - - err = AudioUnitRender(stream->inputUnit, - ioActionFlags, - inTimeStamp, - INPUT_ELEMENT, - delta, - &stream->inputAudioBufferList); - if (err) - goto stop_stream; - - PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, - stream->inputAudioBufferList.mBuffers[0].mData, - stream->inputAudioBufferList.mBuffers[0].mNumberChannels); - - PaUtil_SetOutputFrameCount(&stream->bufferProcessor, delta); - PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, - (char *)ioData->mBuffers[0].mData + (bytesPerFrame * total), - ioData->mBuffers[0].mNumberChannels); - - framesProcessed += - PaUtil_EndBufferProcessing(&stream->bufferProcessor, - &callbackResult); - total += delta; - } - } else if (isRender) { - /* - * Output Side of Full Duplex or Simplex Output - * - * This case handles output data as in the full duplex - * case and if there is input data, reads it off the - * ring buffer and into the PA buffer processor. - */ - size_t bytesPerFrame = sizeof(float) * ioData->mBuffers[0].mNumberChannels; - size_t frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame; - size_t total = 0; - int xrunFlags = stream->xrunFlags; - - if (stream->state == STOPPING || stream->state == CALLBACK_STOPPED) - xrunFlags = 0; - - assert(ioData->mNumberBuffers == 1); - assert(ioData->mBuffers[0].mNumberChannels == stream->userOutChan); - - while (1) { - size_t delta = frames - total; - - if (stream->inputUnit && delta > stream->inputFramesPerBuffer) - delta = stream->inputFramesPerBuffer; - if (delta > stream->outputFramesPerBuffer) - delta = stream->outputFramesPerBuffer; - if (delta == 0) - break; - - PaUtil_BeginBufferProcessing(&stream->bufferProcessor, - &timeInfo, xrunFlags); - stream->xrunFlags = xrunFlags = 0; - - PaUtil_SetOutputFrameCount(&stream->bufferProcessor, delta); - PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, - (char *)ioData->mBuffers[0].mData + (total * bytesPerFrame), - ioData->mBuffers[0].mNumberChannels); - - if (stream->inputUnit) { - /* read data out of the ring buffer */ - int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; - size_t inBytesPerFrame = sizeof(float) * inChan; - void *data1; - void *data2; - ring_buffer_size_t size1; - ring_buffer_size_t size2; - size_t framesReadable = PaUtil_GetRingBufferReadRegions(&stream->inputRingBuffer, - delta, &data1, &size1, &data2, &size2); - - if (size1 == delta) { - /* simplest case: all in first buffer */ - PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, - data1, inChan); - framesProcessed += - PaUtil_EndBufferProcessing(&stream->bufferProcessor, - &callbackResult); - PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, size1); - } else if (framesReadable < delta) { - long sizeBytes1 = size1 * inBytesPerFrame; - long sizeBytes2 = size2 * inBytesPerFrame; - - /* - * Underflow: Take what data we can, - * zero the rest. - */ - unsigned char data[delta * inBytesPerFrame]; - - if (size1 > 0) - memcpy(data, data1, sizeBytes1); - if (size2 > 0) - memcpy(data + sizeBytes1, data2, sizeBytes2); - memset(data + sizeBytes1 + sizeBytes2, 0, - (delta * inBytesPerFrame) - sizeBytes1 - sizeBytes2); - - PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, - data, inChan); - framesProcessed += - PaUtil_EndBufferProcessing(&stream->bufferProcessor, - &callbackResult); - PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, - framesReadable); - /* flag underflow */ - stream->xrunFlags |= paInputUnderflow; - } else { - /* - * We got all the data, but - * split between buffers - */ - PaUtil_SetInputFrameCount(&stream->bufferProcessor, size1); - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, - data1, inChan); - PaUtil_Set2ndInputFrameCount(&stream->bufferProcessor, size2); - PaUtil_Set2ndInterleavedInputChannels(&stream->bufferProcessor, 0, - data2, inChan); - framesProcessed += - PaUtil_EndBufferProcessing(&stream->bufferProcessor, - &callbackResult); - PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, framesReadable); - } - } else { - framesProcessed += - PaUtil_EndBufferProcessing(&stream->bufferProcessor, - &callbackResult); - } - total += delta; - } - } else { - /* - * Input - * - * First, we read off the audio data and put it in the ring - * buffer. if this is an input-only stream, we need to - * process it more, otherwise, we let the output case deal - * with it. - */ - OSStatus err; - int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; - size_t bytesPerFrame = sizeof(float) * inChan; - size_t frames = inNumberFrames; - size_t total = 0; - - while (1) { - size_t delta = frames - total; - - if (delta > stream->inputFramesPerBuffer) - delta = stream->inputFramesPerBuffer; - if (delta == 0) - break; - - stream->inputAudioBufferList.mBuffers[0].mDataByteSize = - frames * bytesPerFrame; - - err = AudioUnitRender(stream->inputUnit, - ioActionFlags, - inTimeStamp, - INPUT_ELEMENT, - delta, - &stream->inputAudioBufferList); - if (err) - goto stop_stream; - - if (stream->outputUnit) { - /* - * If this is duplex put the data into the - * ring buffer: - */ - size_t framesWritten = PaUtil_WriteRingBuffer(&stream->inputRingBuffer, - stream->inputAudioBufferList.mBuffers[0].mData, delta); - - if (framesWritten != delta) - stream->xrunFlags |= paInputOverflow; - } else { - /* Push data into the buffer processor */ - PaUtil_BeginBufferProcessing(&stream->bufferProcessor, - &timeInfo, stream->xrunFlags); - stream->xrunFlags = 0; - - PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, - stream->inputAudioBufferList.mBuffers[0].mData, inChan); - framesProcessed += - PaUtil_EndBufferProcessing(&stream->bufferProcessor, - &callbackResult); - } - total += delta; - } - } - - /* - * Should we return successfully or fall through to stopping - * the stream? - */ - if (callbackResult == paContinue) { - PaUtil_EndCpuLoadMeasurement(&stream->cpuLoadMeasurer, framesProcessed); - return noErr; - } + size_t framesProcessed = 0; + PaStreamCallbackTimeInfo timeInfo = {}; + PaIosCoreStream *stream = (PaIosCoreStream *) inRefCon; + const bool isRender = (inBusNumber == OUTPUT_ELEMENT); + int callbackResult = paContinue; + double hostTimeStampInPaTime = HOST_TIME_TO_PA_TIME(inTimeStamp->mHostTime); + + PaUtil_BeginCpuLoadMeasurement(&stream->cpuLoadMeasurer); + + /* compute PaStreamCallbackTimeInfo */ + if (pthread_mutex_trylock(&stream->timingInformationMutex) == 0) { + /* snapshot the ioproc copy of timing information */ + stream->timestampOffsetCombined_ioProcCopy = stream->timestampOffsetCombined; + stream->timestampOffsetInputDevice_ioProcCopy = stream->timestampOffsetInputDevice; + stream->timestampOffsetOutputDevice_ioProcCopy = stream->timestampOffsetOutputDevice; + pthread_mutex_unlock(&stream->timingInformationMutex); + } + + /* + * For timeInfo.currentTime we could calculate current time + * backwards from the HAL audio output time to give a more accurate + * impression of the current timeslice but it doesn't seem worth it + * at the moment since other PA host APIs don't do any better. + */ + timeInfo.currentTime = HOST_TIME_TO_PA_TIME(mach_absolute_time()); + + /* + * For an input HAL AU, inTimeStamp is the time the samples + * are received from the hardware, for an output HAL AU + * inTimeStamp is the time the samples are sent to the + * hardware. PA expresses timestamps in terms of when the + * samples enter the ADC or leave the DAC so we add or + * subtract kAudioDevicePropertyLatency below. + */ + + /* + * FIXME: not sure what to do below if the host timestamps aren't + * valid (kAudioTimeStampHostTimeValid isn't set) Could ask on CA + * mailing list if it is possible for it not to be set. If so, could + * probably grab a now timestamp at the top and compute from there + * (modulo scheduling jitter) or ask on mailing list for other + * options. + */ + + if (isRender) { + if (stream->inputUnit) { + /* full duplex */ + timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - + (stream->timestampOffsetCombined_ioProcCopy + stream->timestampOffsetInputDevice_ioProcCopy); + timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy; + } else { + /* output only */ + timeInfo.inputBufferAdcTime = 0; + timeInfo.outputBufferDacTime = hostTimeStampInPaTime + stream->timestampOffsetOutputDevice_ioProcCopy; + } + } else { + /* input only */ + timeInfo.inputBufferAdcTime = hostTimeStampInPaTime - stream->timestampOffsetInputDevice_ioProcCopy; + timeInfo.outputBufferDacTime = 0; + } + + if (isRender && stream->inputUnit == stream->outputUnit) { + /* + * Full Duplex, One Device + * + * This is the lowest latency case, and also the simplest. + * Input data and output data are available at the same + * time. we do not use the input SR converter or the input + * ring buffer. + */ + OSStatus err; + size_t bytesPerFrame = sizeof(float) * ioData->mBuffers[0].mNumberChannels; + size_t frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame; + size_t total = 0; + + assert(ioData->mNumberBuffers == 1); + assert(ioData->mBuffers[0].mNumberChannels == stream->userOutChan); + + while (1) { + size_t delta = frames - total; + if (delta > stream->inputFramesPerBuffer) + delta = stream->inputFramesPerBuffer; + if (delta > stream->outputFramesPerBuffer) + delta = stream->outputFramesPerBuffer; + if (delta == 0) + break; + + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, stream->xrunFlags); + stream->xrunFlags = 0; + + stream->inputAudioBufferList.mBuffers[0].mDataByteSize = + delta * bytesPerFrame; + + err = AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + delta, + &stream->inputAudioBufferList); + if (err) + goto stop_stream; + + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + stream->inputAudioBufferList.mBuffers[0].mData, + stream->inputAudioBufferList.mBuffers[0].mNumberChannels); + + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, + (char *)ioData->mBuffers[0].mData + (bytesPerFrame * total), + ioData->mBuffers[0].mNumberChannels); + + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + total += delta; + } + } else if (isRender) { + /* + * Output Side of Full Duplex or Simplex Output + * + * This case handles output data as in the full duplex + * case and if there is input data, reads it off the + * ring buffer and into the PA buffer processor. + */ + size_t bytesPerFrame = sizeof(float) * ioData->mBuffers[0].mNumberChannels; + size_t frames = ioData->mBuffers[0].mDataByteSize / bytesPerFrame; + size_t total = 0; + int xrunFlags = stream->xrunFlags; + + if (stream->state == STOPPING || stream->state == CALLBACK_STOPPED) + xrunFlags = 0; + + assert(ioData->mNumberBuffers == 1); + assert(ioData->mBuffers[0].mNumberChannels == stream->userOutChan); + + while (1) { + size_t delta = frames - total; + + if (stream->inputUnit && delta > stream->inputFramesPerBuffer) + delta = stream->inputFramesPerBuffer; + if (delta > stream->outputFramesPerBuffer) + delta = stream->outputFramesPerBuffer; + if (delta == 0) + break; + + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, xrunFlags); + stream->xrunFlags = xrunFlags = 0; + + PaUtil_SetOutputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, + (char *)ioData->mBuffers[0].mData + (total * bytesPerFrame), + ioData->mBuffers[0].mNumberChannels); + + if (stream->inputUnit) { + /* read data out of the ring buffer */ + int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; + size_t inBytesPerFrame = sizeof(float) * inChan; + void *data1; + void *data2; + ring_buffer_size_t size1; + ring_buffer_size_t size2; + size_t framesReadable = PaUtil_GetRingBufferReadRegions(&stream->inputRingBuffer, + delta, &data1, &size1, &data2, &size2); + + if (size1 == delta) { + /* simplest case: all in first buffer */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + data1, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, size1); + } else if (framesReadable < delta) { + long sizeBytes1 = size1 * inBytesPerFrame; + long sizeBytes2 = size2 * inBytesPerFrame; + + /* + * Underflow: Take what data we can, + * zero the rest. + */ + unsigned char data[delta * inBytesPerFrame]; + + if (size1 > 0) + memcpy(data, data1, sizeBytes1); + if (size2 > 0) + memcpy(data + sizeBytes1, data2, sizeBytes2); + memset(data + sizeBytes1 + sizeBytes2, 0, + (delta * inBytesPerFrame) - sizeBytes1 - sizeBytes2); + + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + data, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, + framesReadable); + /* flag underflow */ + stream->xrunFlags |= paInputUnderflow; + } else { + /* + * We got all the data, but + * split between buffers + */ + PaUtil_SetInputFrameCount(&stream->bufferProcessor, size1); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + data1, inChan); + PaUtil_Set2ndInputFrameCount(&stream->bufferProcessor, size2); + PaUtil_Set2ndInterleavedInputChannels(&stream->bufferProcessor, 0, + data2, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + PaUtil_AdvanceRingBufferReadIndex(&stream->inputRingBuffer, framesReadable); + } + } else { + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + } + total += delta; + } + } else { + /* + * Input + * + * First, we read off the audio data and put it in the ring + * buffer. if this is an input-only stream, we need to + * process it more, otherwise, we let the output case deal + * with it. + */ + OSStatus err; + int inChan = stream->inputAudioBufferList.mBuffers[0].mNumberChannels; + size_t bytesPerFrame = sizeof(float) * inChan; + size_t frames = inNumberFrames; + size_t total = 0; + + while (1) { + size_t delta = frames - total; + + if (delta > stream->inputFramesPerBuffer) + delta = stream->inputFramesPerBuffer; + if (delta == 0) + break; + + stream->inputAudioBufferList.mBuffers[0].mDataByteSize = + frames * bytesPerFrame; + + err = AudioUnitRender(stream->inputUnit, + ioActionFlags, + inTimeStamp, + INPUT_ELEMENT, + delta, + &stream->inputAudioBufferList); + if (err) + goto stop_stream; + + if (stream->outputUnit) { + /* + * If this is duplex put the data into the + * ring buffer: + */ + size_t framesWritten = PaUtil_WriteRingBuffer(&stream->inputRingBuffer, + stream->inputAudioBufferList.mBuffers[0].mData, delta); + + if (framesWritten != delta) + stream->xrunFlags |= paInputOverflow; + } else { + /* Push data into the buffer processor */ + PaUtil_BeginBufferProcessing(&stream->bufferProcessor, + &timeInfo, stream->xrunFlags); + stream->xrunFlags = 0; + + PaUtil_SetInputFrameCount(&stream->bufferProcessor, delta); + PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, + stream->inputAudioBufferList.mBuffers[0].mData, inChan); + framesProcessed += + PaUtil_EndBufferProcessing(&stream->bufferProcessor, + &callbackResult); + } + total += delta; + } + } + + /* + * Should we return successfully or fall through to stopping + * the stream? + */ + if (callbackResult == paContinue) { + PaUtil_EndCpuLoadMeasurement(&stream->cpuLoadMeasurer, framesProcessed); + return noErr; + } stop_stream: - /* XXX stopping stream from here causes a deadlock */ - PaUtil_EndCpuLoadMeasurement(&stream->cpuLoadMeasurer, framesProcessed); - return noErr; + /* XXX stopping stream from here causes a deadlock */ + PaUtil_EndCpuLoadMeasurement(&stream->cpuLoadMeasurer, framesProcessed); + return noErr; } -static PaError +static PaError CloseStream(PaStream * s) { - PaIosCoreStream *stream = (PaIosCoreStream *) s; - PaError result = paNoError; + PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaError result = paNoError; - if (stream == NULL) - return (result); + if (stream == NULL) + return (result); - if (stream->outputUnit != NULL && stream->outputUnit != stream->inputUnit) - AudioComponentInstanceDispose(stream->outputUnit); - stream->outputUnit = NULL; + if (stream->outputUnit != NULL && stream->outputUnit != stream->inputUnit) + AudioComponentInstanceDispose(stream->outputUnit); + stream->outputUnit = NULL; - if (stream->inputUnit != NULL) - AudioComponentInstanceDispose(stream->inputUnit); - stream->inputUnit = NULL; + if (stream->inputUnit != NULL) + AudioComponentInstanceDispose(stream->inputUnit); + stream->inputUnit = NULL; - free((void *)stream->inputRingBuffer.buffer); - stream->inputRingBuffer.buffer = NULL; + free((void *)stream->inputRingBuffer.buffer); + stream->inputRingBuffer.buffer = NULL; - free(stream->inputAudioBufferList.mBuffers[0].mData); - stream->inputAudioBufferList.mBuffers[0].mData = NULL; + free(stream->inputAudioBufferList.mBuffers[0].mData); + stream->inputAudioBufferList.mBuffers[0].mData = NULL; - result = destroyBlioRingBuffers(&stream->blio); - if (result) - return (result); + result = destroyBlioRingBuffers(&stream->blio); + if (result) + return (result); - if (stream->bufferProcessorIsInitialized) - PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); - if (stream->timingInformationMutexIsInitialized) - pthread_mutex_destroy(&stream->timingInformationMutex); + if (stream->bufferProcessorIsInitialized) + PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); + if (stream->timingInformationMutexIsInitialized) + pthread_mutex_destroy(&stream->timingInformationMutex); - PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); - PaUtil_FreeMemory(stream); + PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); + PaUtil_FreeMemory(stream); - return (result); + return (result); } -static PaError +static PaError StartStream(PaStream * s) { - PaIosCoreStream *stream = (PaIosCoreStream *) s; - OSStatus result = noErr; + PaIosCoreStream *stream = (PaIosCoreStream *) s; + OSStatus result = noErr; -#define ERR_WRAP(ios_err) do { \ - result = ios_err; \ - if (result != noErr) \ - return ERR(result); \ +#define ERR_WRAP(ios_err) do { \ + result = ios_err; \ + if (result != noErr) \ + return ERR(result); \ } while(0) - PaUtil_ResetBufferProcessor(&stream->bufferProcessor); + PaUtil_ResetBufferProcessor(&stream->bufferProcessor); - stream->state = ACTIVE; - if (stream->inputUnit) - ERR_WRAP(AudioOutputUnitStart(stream->inputUnit)); + stream->state = ACTIVE; + if (stream->inputUnit) + ERR_WRAP(AudioOutputUnitStart(stream->inputUnit)); - if (stream->outputUnit && stream->outputUnit != stream->inputUnit) - ERR_WRAP(AudioOutputUnitStart(stream->outputUnit)); + if (stream->outputUnit && stream->outputUnit != stream->inputUnit) + ERR_WRAP(AudioOutputUnitStart(stream->outputUnit)); - return paNoError; + return paNoError; #undef ERR_WRAP } static OSStatus BlockWhileAudioUnitIsRunning(AudioUnit audioUnit, AudioUnitElement element) { - Boolean isRunning; - - while (1) { - UInt32 s = sizeof(isRunning); - OSStatus err = AudioUnitGetProperty(audioUnit, - kAudioOutputUnitProperty_IsRunning, - kAudioUnitScope_Global, element, &isRunning, &s); - - if (err || isRunning == false) - return (err); - Pa_Sleep(100); - } - return (noErr); + Boolean isRunning; + + while (1) { + UInt32 s = sizeof(isRunning); + OSStatus err = AudioUnitGetProperty(audioUnit, + kAudioOutputUnitProperty_IsRunning, + kAudioUnitScope_Global, element, &isRunning, &s); + + if (err || isRunning == false) + return (err); + Pa_Sleep(100); + } + return (noErr); } -static PaError +static PaError FinishStoppingStream(PaIosCoreStream * stream) { - OSStatus result = noErr; - PaError paErr; + OSStatus result = noErr; + PaError paErr; -#define ERR_WRAP(ios_err) do { \ - result = ios_err; \ - if (result != noErr) \ - return ERR(result); \ +#define ERR_WRAP(ios_err) do { \ + result = ios_err; \ + if (result != noErr) \ + return ERR(result); \ } while(0) - if (stream->inputUnit == stream->outputUnit && stream->inputUnit) { - ERR_WRAP(AudioOutputUnitStop(stream->inputUnit)); - ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 0)); - ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 1)); - ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1)); - ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 0)); - } else { - if (stream->inputUnit) { - ERR_WRAP(AudioOutputUnitStop(stream->inputUnit)); - ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 1)); - ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1)); - } - if (stream->outputUnit) { - ERR_WRAP(AudioOutputUnitStop(stream->outputUnit)); - ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->outputUnit, 0)); - ERR_WRAP(AudioUnitReset(stream->outputUnit, kAudioUnitScope_Global, 0)); - } - } - if (stream->inputRingBuffer.buffer) { - PaUtil_FlushRingBuffer(&stream->inputRingBuffer); - memset((void *)stream->inputRingBuffer.buffer, 0, stream->inputRingBuffer.bufferSize); - if (stream->outputUnit) { - PaUtil_AdvanceRingBufferWriteIndex( - &stream->inputRingBuffer, stream->inputRingBuffer.bufferSize / - RING_BUFFER_ADVANCE_DENOMINATOR); - } - } - stream->xrunFlags = 0; - stream->state = STOPPED; - - paErr = resetBlioRingBuffers(&stream->blio); - if (paErr) - return (paErr); - - return (paNoError); + if (stream->inputUnit == stream->outputUnit && stream->inputUnit) { + ERR_WRAP(AudioOutputUnitStop(stream->inputUnit)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 0)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 1)); + ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1)); + ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 0)); + } else { + if (stream->inputUnit) { + ERR_WRAP(AudioOutputUnitStop(stream->inputUnit)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->inputUnit, 1)); + ERR_WRAP(AudioUnitReset(stream->inputUnit, kAudioUnitScope_Global, 1)); + } + if (stream->outputUnit) { + ERR_WRAP(AudioOutputUnitStop(stream->outputUnit)); + ERR_WRAP(BlockWhileAudioUnitIsRunning(stream->outputUnit, 0)); + ERR_WRAP(AudioUnitReset(stream->outputUnit, kAudioUnitScope_Global, 0)); + } + } + if (stream->inputRingBuffer.buffer) { + PaUtil_FlushRingBuffer(&stream->inputRingBuffer); + memset((void *)stream->inputRingBuffer.buffer, 0, stream->inputRingBuffer.bufferSize); + if (stream->outputUnit) { + PaUtil_AdvanceRingBufferWriteIndex( + &stream->inputRingBuffer, stream->inputRingBuffer.bufferSize / + RING_BUFFER_ADVANCE_DENOMINATOR); + } + } + stream->xrunFlags = 0; + stream->state = STOPPED; + + paErr = resetBlioRingBuffers(&stream->blio); + if (paErr) + return (paErr); + + return (paNoError); #undef ERR_WRAP } -static PaError +static PaError StopStream(PaStream * s) { - PaIosCoreStream *stream = (PaIosCoreStream *) s; - PaError paErr; + PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaError paErr; - stream->state = STOPPING; + stream->state = STOPPING; - if (stream->userOutChan > 0) { - size_t maxHostFrames = MAX(stream->inputFramesPerBuffer, stream->outputFramesPerBuffer); + if (stream->userOutChan > 0) { + size_t maxHostFrames = MAX(stream->inputFramesPerBuffer, stream->outputFramesPerBuffer); - paErr = waitUntilBlioWriteBufferIsEmpty(&stream->blio, stream->sampleRate, maxHostFrames); - } - return (FinishStoppingStream(stream)); + paErr = waitUntilBlioWriteBufferIsEmpty(&stream->blio, stream->sampleRate, maxHostFrames); + } + return (FinishStoppingStream(stream)); } -static PaError +static PaError AbortStream(PaStream * s) { - PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaIosCoreStream *stream = (PaIosCoreStream *) s; - stream->state = STOPPING; - return (FinishStoppingStream(stream)); + stream->state = STOPPING; + return (FinishStoppingStream(stream)); } -static PaError +static PaError IsStreamStopped(PaStream * s) { - PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaIosCoreStream *stream = (PaIosCoreStream *) s; - return (stream->state == STOPPED); + return (stream->state == STOPPED); } -static PaError +static PaError IsStreamActive(PaStream * s) { - PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaIosCoreStream *stream = (PaIosCoreStream *) s; - return (stream->state == ACTIVE || stream->state == STOPPING); + return (stream->state == ACTIVE || stream->state == STOPPING); } static double GetStreamCpuLoad(PaStream * s) { - PaIosCoreStream *stream = (PaIosCoreStream *) s; + PaIosCoreStream *stream = (PaIosCoreStream *) s; - return (PaUtil_GetCpuLoad(&stream->cpuLoadMeasurer)); + return (PaUtil_GetCpuLoad(&stream->cpuLoadMeasurer)); } diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_blocking.c b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.c index 236dccc56..4c2a990c7 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core_blocking.c +++ b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.c @@ -15,7 +15,7 @@ * Olivier Tristan for feedback and testing * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O * interface. - * + * * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -41,13 +41,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ @@ -76,14 +76,14 @@ */ static size_t computeSampleSizeFromFormat( PaSampleFormat format ) { - switch( format & (~paNonInterleaved) ) { - case paFloat32: return 4; - case paInt32: return 4; - case paInt24: return 3; - case paInt16: return 2; - case paInt8: case paUInt8: return 1; - default: return 0; - } + switch( format & (~paNonInterleaved) ) { + case paFloat32: return 4; + case paInt32: return 4; + case paInt24: return 3; + case paInt16: return 2; + case paInt8: case paUInt8: return 1; + default: return 0; + } } /* * Same as computeSampleSizeFromFormat, except that if @@ -91,14 +91,14 @@ static size_t computeSampleSizeFromFormat( PaSampleFormat format ) */ static size_t computeSampleSizeFromFormatPow2( PaSampleFormat format ) { - switch( format & (~paNonInterleaved) ) { - case paFloat32: return 4; - case paInt32: return 4; - case paInt24: return 4; - case paInt16: return 2; - case paInt8: case paUInt8: return 1; - default: return 0; - } + switch( format & (~paNonInterleaved) ) { + case paFloat32: return 4; + case paInt32: return 4; + case paInt24: return 4; + case paInt16: return 2; + case paInt8: case paUInt8: return 1; + default: return 0; + } } @@ -113,150 +113,149 @@ static size_t computeSampleSizeFromFormatPow2( PaSampleFormat format ) * * @param ringBufferSizeInFrames must be a power of 2 */ -PaError initializeBlioRingBuffers( - PaIosBlio *blio, - PaSampleFormat inputSampleFormat, - PaSampleFormat outputSampleFormat, - long ringBufferSizeInFrames, - int inChan, - int outChan ) +PaError initializeBlioRingBuffers(PaIosBlio *blio, + PaSampleFormat inputSampleFormat, + PaSampleFormat outputSampleFormat, + long ringBufferSizeInFrames, + int inChan, + int outChan ) { - void *data; - int result; - OSStatus err; - - /* zeroify things */ - bzero( blio, sizeof( PaIosBlio ) ); - /* this is redundant, but the buffers are used to check - if the buffers have been initialized, so we do it explicitly. */ - blio->inputRingBuffer.buffer = NULL; - blio->outputRingBuffer.buffer = NULL; - - /* initialize simple data */ - blio->ringBufferFrames = ringBufferSizeInFrames; - blio->inputSampleFormat = inputSampleFormat; - blio->inputSampleSizeActual = computeSampleSizeFromFormat(inputSampleFormat); - blio->inputSampleSizePow2 = computeSampleSizeFromFormatPow2(inputSampleFormat); // FIXME: WHY? - blio->outputSampleFormat = outputSampleFormat; - blio->outputSampleSizeActual = computeSampleSizeFromFormat(outputSampleFormat); - blio->outputSampleSizePow2 = computeSampleSizeFromFormatPow2(outputSampleFormat); - - blio->inChan = inChan; - blio->outChan = outChan; - blio->statusFlags = 0; - blio->errors = paNoError; + void *data; + int result; + OSStatus err; + + /* zeroify things */ + bzero( blio, sizeof( PaIosBlio ) ); + /* this is redundant, but the buffers are used to check + if the buffers have been initialized, so we do it explicitly. */ + blio->inputRingBuffer.buffer = NULL; + blio->outputRingBuffer.buffer = NULL; + + /* initialize simple data */ + blio->ringBufferFrames = ringBufferSizeInFrames; + blio->inputSampleFormat = inputSampleFormat; + blio->inputSampleSizeActual = computeSampleSizeFromFormat(inputSampleFormat); + blio->inputSampleSizePow2 = computeSampleSizeFromFormatPow2(inputSampleFormat); // FIXME: WHY? + blio->outputSampleFormat = outputSampleFormat; + blio->outputSampleSizeActual = computeSampleSizeFromFormat(outputSampleFormat); + blio->outputSampleSizePow2 = computeSampleSizeFromFormatPow2(outputSampleFormat); + + blio->inChan = inChan; + blio->outChan = outChan; + blio->statusFlags = 0; + blio->errors = paNoError; #ifdef PA_IOS_BLIO_MUTEX - blio->isInputEmpty = false; - blio->isOutputFull = false; + blio->isInputEmpty = false; + blio->isOutputFull = false; #endif - /* setup ring buffers */ + /* setup ring buffers */ #ifdef PA_IOS_BLIO_MUTEX - result = PaIosCore_SetUnixError( pthread_mutex_init(&(blio->inputMutex),NULL), 0 ); - if( result ) - goto error; - result = UNIX_ERR( pthread_cond_init( &(blio->inputCond), NULL ) ); - if( result ) - goto error; - result = UNIX_ERR( pthread_mutex_init(&(blio->outputMutex),NULL) ); - if( result ) - goto error; - result = UNIX_ERR( pthread_cond_init( &(blio->outputCond), NULL ) ); + result = PaIosCore_SetUnixError( pthread_mutex_init(&(blio->inputMutex),NULL), 0 ); + if( result ) + goto error; + result = UNIX_ERR( pthread_cond_init( &(blio->inputCond), NULL ) ); + if( result ) + goto error; + result = UNIX_ERR( pthread_mutex_init(&(blio->outputMutex),NULL) ); + if( result ) + goto error; + result = UNIX_ERR( pthread_cond_init( &(blio->outputCond), NULL ) ); #endif - if( inChan ) { - data = calloc( ringBufferSizeInFrames, blio->inputSampleSizePow2 * inChan ); - if( !data ) - { - result = paInsufficientMemory; - goto error; - } - - err = PaUtil_InitializeRingBuffer( - &blio->inputRingBuffer, - blio->inputSampleSizePow2 * inChan, - ringBufferSizeInFrames, - data ); - assert( !err ); - } - if( outChan ) { - data = calloc( ringBufferSizeInFrames, blio->outputSampleSizePow2 * outChan ); - if( !data ) - { - result = paInsufficientMemory; - goto error; - } - - err = PaUtil_InitializeRingBuffer( - &blio->outputRingBuffer, - blio->outputSampleSizePow2 * outChan, - ringBufferSizeInFrames, - data ); - assert( !err ); - } - - result = resetBlioRingBuffers( blio ); - if( result ) - goto error; - - return 0; - - error: - destroyBlioRingBuffers( blio ); - return result; + if( inChan ) { + data = calloc( ringBufferSizeInFrames, blio->inputSampleSizePow2 * inChan ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + err = PaUtil_InitializeRingBuffer( + &blio->inputRingBuffer, + blio->inputSampleSizePow2 * inChan, + ringBufferSizeInFrames, + data ); + assert( !err ); + } + if( outChan ) { + data = calloc( ringBufferSizeInFrames, blio->outputSampleSizePow2 * outChan ); + if( !data ) + { + result = paInsufficientMemory; + goto error; + } + + err = PaUtil_InitializeRingBuffer( + &blio->outputRingBuffer, + blio->outputSampleSizePow2 * outChan, + ringBufferSizeInFrames, + data ); + assert( !err ); + } + + result = resetBlioRingBuffers( blio ); + if( result ) + goto error; + + return 0; + +error: + destroyBlioRingBuffers( blio ); + return result; } #ifdef PA_IOS_BLIO_MUTEX PaError blioSetIsInputEmpty( PaIosBlio *blio, bool isEmpty ) { - PaError result = paNoError; - if( isEmpty == blio->isInputEmpty ) - goto done; - - /* we need to update the value. Here's what we do: - * - Lock the mutex, so noone else can write. - * - update the value. - * - unlock. - * - broadcast to all listeners. - */ - result = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); - if( result ) - goto done; - blio->isInputEmpty = isEmpty; - result = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); - if( result ) - goto done; - result = UNIX_ERR( pthread_cond_broadcast( &blio->inputCond ) ); - if( result ) - goto done; - - done: - return result; + PaError result = paNoError; + if( isEmpty == blio->isInputEmpty ) + goto done; + + /* we need to update the value. Here's what we do: + * - Lock the mutex, so noone else can write. + * - update the value. + * - unlock. + * - broadcast to all listeners. + */ + result = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); + if( result ) + goto done; + blio->isInputEmpty = isEmpty; + result = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); + if( result ) + goto done; + result = UNIX_ERR( pthread_cond_broadcast( &blio->inputCond ) ); + if( result ) + goto done; + +done: + return result; } PaError blioSetIsOutputFull( PaIosBlio *blio, bool isFull ) { - PaError result = paNoError; - if( isFull == blio->isOutputFull ) - goto done; - - /* we need to update the value. Here's what we do: - * - Lock the mutex, so noone else can write. - * - update the value. - * - unlock. - * - broadcast to all listeners. - */ - result = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); - if( result ) - goto done; - blio->isOutputFull = isFull; - result = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); - if( result ) - goto done; - result = UNIX_ERR( pthread_cond_broadcast( &blio->outputCond ) ); - if( result ) - goto done; - - done: - return result; + PaError result = paNoError; + if( isFull == blio->isOutputFull ) + goto done; + + /* we need to update the value. Here's what we do: + * - Lock the mutex, so noone else can write. + * - update the value. + * - unlock. + * - broadcast to all listeners. + */ + result = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); + if( result ) + goto done; + blio->isOutputFull = isFull; + result = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); + if( result ) + goto done; + result = UNIX_ERR( pthread_cond_broadcast( &blio->outputCond ) ); + if( result ) + goto done; + +done: + return result; } #endif @@ -265,42 +264,42 @@ PaError blioSetIsOutputFull( PaIosBlio *blio, bool isFull ) PaError resetBlioRingBuffers( PaIosBlio *blio ) { #ifdef PA_IOS__BLIO_MUTEX - int result; + int result; #endif - blio->statusFlags = 0; - if( blio->outputRingBuffer.buffer ) { - PaUtil_FlushRingBuffer( &blio->outputRingBuffer ); - /* Fill the buffer with zeros. */ - bzero( blio->outputRingBuffer.buffer, - blio->outputRingBuffer.bufferSize * blio->outputRingBuffer.elementSizeBytes ); - PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->ringBufferFrames ); - - /* Update isOutputFull. */ + blio->statusFlags = 0; + if( blio->outputRingBuffer.buffer ) { + PaUtil_FlushRingBuffer( &blio->outputRingBuffer ); + /* Fill the buffer with zeros. */ + bzero( blio->outputRingBuffer.buffer, + blio->outputRingBuffer.bufferSize * blio->outputRingBuffer.elementSizeBytes ); + PaUtil_AdvanceRingBufferWriteIndex( &blio->outputRingBuffer, blio->ringBufferFrames ); + + /* Update isOutputFull. */ #ifdef PA_IOS__BLIO_MUTEX - result = blioSetIsOutputFull( blio, toAdvance == blio->outputRingBuffer.bufferSize ); - if( result ) - goto error; + result = blioSetIsOutputFull( blio, toAdvance == blio->outputRingBuffer.bufferSize ); + if( result ) + goto error; #endif /* - printf( "------%d\n" , blio->outChan ); - printf( "------%d\n" , blio->outputSampleSize ); + printf( "------%d\n" , blio->outChan ); + printf( "------%d\n" , blio->outputSampleSize ); */ - } - if( blio->inputRingBuffer.buffer ) { - PaUtil_FlushRingBuffer( &blio->inputRingBuffer ); - bzero( blio->inputRingBuffer.buffer, - blio->inputRingBuffer.bufferSize * blio->inputRingBuffer.elementSizeBytes ); - /* Update isInputEmpty. */ + } + if( blio->inputRingBuffer.buffer ) { + PaUtil_FlushRingBuffer( &blio->inputRingBuffer ); + bzero( blio->inputRingBuffer.buffer, + blio->inputRingBuffer.bufferSize * blio->inputRingBuffer.elementSizeBytes ); + /* Update isInputEmpty. */ #ifdef PA_IOS__BLIO_MUTEX - result = blioSetIsInputEmpty( blio, true ); - if( result ) - goto error; + result = blioSetIsInputEmpty( blio, true ); + if( result ) + goto error; #endif - } - return paNoError; + } + return paNoError; #ifdef PA_IOS__BLIO_MUTEX - error: - return result; +error: + return result; #endif } @@ -308,29 +307,29 @@ PaError resetBlioRingBuffers( PaIosBlio *blio ) multiple times if there are no exceptions. */ PaError destroyBlioRingBuffers( PaIosBlio *blio ) { - PaError result = paNoError; - if( blio->inputRingBuffer.buffer ) { - free( blio->inputRingBuffer.buffer ); + PaError result = paNoError; + if( blio->inputRingBuffer.buffer ) { + free( blio->inputRingBuffer.buffer ); #ifdef PA_IOS__BLIO_MUTEX - result = UNIX_ERR( pthread_mutex_destroy( & blio->inputMutex ) ); - if( result ) return result; - result = UNIX_ERR( pthread_cond_destroy( & blio->inputCond ) ); - if( result ) return result; + result = UNIX_ERR( pthread_mutex_destroy( & blio->inputMutex ) ); + if( result ) return result; + result = UNIX_ERR( pthread_cond_destroy( & blio->inputCond ) ); + if( result ) return result; #endif - } - blio->inputRingBuffer.buffer = NULL; - if( blio->outputRingBuffer.buffer ) { - free( blio->outputRingBuffer.buffer ); + } + blio->inputRingBuffer.buffer = NULL; + if( blio->outputRingBuffer.buffer ) { + free( blio->outputRingBuffer.buffer ); #ifdef PA_IOS__BLIO_MUTEX - result = UNIX_ERR( pthread_mutex_destroy( & blio->outputMutex ) ); - if( result ) return result; - result = UNIX_ERR( pthread_cond_destroy( & blio->outputCond ) ); - if( result ) return result; + result = UNIX_ERR( pthread_mutex_destroy( & blio->outputMutex ) ); + if( result ) return result; + result = UNIX_ERR( pthread_cond_destroy( & blio->outputCond ) ); + if( result ) return result; #endif - } - blio->outputRingBuffer.buffer = NULL; + } + blio->outputRingBuffer.buffer = NULL; - return result; + return result; } /* @@ -339,86 +338,86 @@ PaError destroyBlioRingBuffers( PaIosBlio *blio ) * */ int BlioCallback( const void *input, void *output, unsigned long frameCount, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData ) + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) { - PaIosBlio *blio = (PaIosBlio*)userData; - ring_buffer_size_t framesAvailable; - ring_buffer_size_t framesToTransfer; - ring_buffer_size_t framesTransferred; - - /* set flags returned by OS: */ - OSAtomicOr32( statusFlags, &blio->statusFlags ) ; - - /* --- Handle Input Buffer --- */ - if( blio->inChan ) { - framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->inputRingBuffer ); - - /* check for underflow */ - if( framesAvailable < frameCount ) - { - OSAtomicOr32( paInputOverflow, &blio->statusFlags ); - framesToTransfer = framesAvailable; - } - else - { - framesToTransfer = (ring_buffer_size_t)frameCount; - } - - /* Copy the data from the audio input to the application ring buffer. */ - /*printf( "reading %d\n", toRead );*/ - framesTransferred = PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, framesToTransfer ); - assert( framesToTransfer == framesTransferred ); + PaIosBlio *blio = (PaIosBlio*)userData; + ring_buffer_size_t framesAvailable; + ring_buffer_size_t framesToTransfer; + ring_buffer_size_t framesTransferred; + + /* set flags returned by OS: */ + OSAtomicOr32( statusFlags, &blio->statusFlags ) ; + + /* --- Handle Input Buffer --- */ + if( blio->inChan ) { + framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->inputRingBuffer ); + + /* check for underflow */ + if( framesAvailable < frameCount ) + { + OSAtomicOr32( paInputOverflow, &blio->statusFlags ); + framesToTransfer = framesAvailable; + } + else + { + framesToTransfer = (ring_buffer_size_t)frameCount; + } + + /* Copy the data from the audio input to the application ring buffer. */ + /*printf( "reading %d\n", toRead );*/ + framesTransferred = PaUtil_WriteRingBuffer( &blio->inputRingBuffer, input, framesToTransfer ); + assert( framesToTransfer == framesTransferred ); #ifdef PA_IOS__BLIO_MUTEX - /* Priority inversion. See notes below. */ - blioSetIsInputEmpty( blio, false ); + /* Priority inversion. See notes below. */ + blioSetIsInputEmpty( blio, false ); #endif - } - - - /* --- Handle Output Buffer --- */ - if( blio->outChan ) { - framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer ); - - /* check for underflow */ - if( framesAvailable < frameCount ) - { - /* zero out the end of the output buffer that we do not have data for */ - framesToTransfer = framesAvailable; - - size_t bytesPerFrame = blio->outputSampleSizeActual * blio->outChan; - size_t offsetInBytes = framesToTransfer * bytesPerFrame; - size_t countInBytes = (frameCount - framesToTransfer) * bytesPerFrame; - bzero( ((char *)output) + offsetInBytes, countInBytes ); - - OSAtomicOr32( paOutputUnderflow, &blio->statusFlags ); - framesToTransfer = framesAvailable; - } - else - { - framesToTransfer = (ring_buffer_size_t)frameCount; - } - - /* copy the data */ - /*printf( "writing %d\n", toWrite );*/ - framesTransferred = PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, framesToTransfer ); - assert( framesToTransfer == framesTransferred ); + } + + + /* --- Handle Output Buffer --- */ + if( blio->outChan ) { + framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->outputRingBuffer ); + + /* check for underflow */ + if( framesAvailable < frameCount ) + { + /* zero out the end of the output buffer that we do not have data for */ + framesToTransfer = framesAvailable; + + size_t bytesPerFrame = blio->outputSampleSizeActual * blio->outChan; + size_t offsetInBytes = framesToTransfer * bytesPerFrame; + size_t countInBytes = (frameCount - framesToTransfer) * bytesPerFrame; + bzero( ((char *)output) + offsetInBytes, countInBytes ); + + OSAtomicOr32( paOutputUnderflow, &blio->statusFlags ); + framesToTransfer = framesAvailable; + } + else + { + framesToTransfer = (ring_buffer_size_t)frameCount; + } + + /* copy the data */ + /*printf( "writing %d\n", toWrite );*/ + framesTransferred = PaUtil_ReadRingBuffer( &blio->outputRingBuffer, output, framesToTransfer ); + assert( framesToTransfer == framesTransferred ); #ifdef PA_IOS__BLIO_MUTEX - /* We have a priority inversion here. However, we will only have to - wait if this was true and is now false, which means we've got - some room in the buffer. - Hopefully problems will be minimized. */ - blioSetIsOutputFull( blio, false ); + /* We have a priority inversion here. However, we will only have to + wait if this was true and is now false, which means we've got + some room in the buffer. + Hopefully problems will be minimized. */ + blioSetIsOutputFull( blio, false ); #endif - } + } - return paContinue; + return paContinue; } PaError ReadStream( PaStream* stream, - void *buffer, - unsigned long framesRequested ) + void *buffer, + unsigned long framesRequested ) { PaIosBlio *blio = & ((PaIosCoreStream*)stream) -> blio; char *cbuf = (char *) buffer; @@ -426,58 +425,58 @@ PaError ReadStream( PaStream* stream, VVDBUG(("ReadStream()\n")); while( framesRequested > 0 ) { - ring_buffer_size_t framesAvailable; - ring_buffer_size_t framesToTransfer; - ring_buffer_size_t framesTransferred; - do { - framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ); + ring_buffer_size_t framesAvailable; + ring_buffer_size_t framesToTransfer; + ring_buffer_size_t framesTransferred; + do { + framesAvailable = PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ); /* - printf( "Read Buffer is %%%g full: %ld of %ld.\n", - 100 * (float)avail / (float) blio->inputRingBuffer.bufferSize, - framesAvailable, blio->inputRingBuffer.bufferSize ); + printf( "Read Buffer is %%%g full: %ld of %ld.\n", + 100 * (float)avail / (float) blio->inputRingBuffer.bufferSize, + framesAvailable, blio->inputRingBuffer.bufferSize ); */ - if( framesAvailable == 0 ) { + if( framesAvailable == 0 ) { #ifdef PA_IOS_BLIO_MUTEX - /**block when empty*/ - ret = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); - if( ret ) - return ret; - while( blio->isInputEmpty ) { - ret = UNIX_ERR( pthread_cond_wait( &blio->inputCond, &blio->inputMutex ) ); + /**block when empty*/ + ret = UNIX_ERR( pthread_mutex_lock( &blio->inputMutex ) ); if( ret ) - return ret; - } - ret = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); - if( ret ) - return ret; + return ret; + while( blio->isInputEmpty ) { + ret = UNIX_ERR( pthread_cond_wait( &blio->inputCond, &blio->inputMutex ) ); + if( ret ) + return ret; + } + ret = UNIX_ERR( pthread_mutex_unlock( &blio->inputMutex ) ); + if( ret ) + return ret; #else - Pa_Sleep( PA_IOS_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); + Pa_Sleep( PA_IOS_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); #endif - } - } while( framesAvailable == 0 ); - framesToTransfer = (ring_buffer_size_t) MIN( framesAvailable, framesRequested ); - framesTransferred = PaUtil_ReadRingBuffer( &blio->inputRingBuffer, (void *)cbuf, framesToTransfer ); - cbuf += framesTransferred * blio->inputSampleSizeActual * blio->inChan; - framesRequested -= framesTransferred; - - if( framesToTransfer == framesAvailable ) { + } + } while( framesAvailable == 0 ); + framesToTransfer = (ring_buffer_size_t) MIN( framesAvailable, framesRequested ); + framesTransferred = PaUtil_ReadRingBuffer( &blio->inputRingBuffer, (void *)cbuf, framesToTransfer ); + cbuf += framesTransferred * blio->inputSampleSizeActual * blio->inChan; + framesRequested -= framesTransferred; + + if( framesToTransfer == framesAvailable ) { #ifdef PA_IOS_BLIO_MUTEX - /* we just emptied the buffer, so we need to mark it as empty. */ - ret = blioSetIsInputEmpty( blio, true ); - if( ret ) - return ret; - /* of course, in the meantime, the callback may have put some sats - in, so - so check for that, too, to avoid a race condition. */ - /* FIXME - this does not seem to fix any race condition. */ - if( PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ) ) { - blioSetIsInputEmpty( blio, false ); - /* FIXME - why check? ret has not been set? */ - if( ret ) + /* we just emptied the buffer, so we need to mark it as empty. */ + ret = blioSetIsInputEmpty( blio, true ); + if( ret ) return ret; - } + /* of course, in the meantime, the callback may have put some sats + in, so + so check for that, too, to avoid a race condition. */ + /* FIXME - this does not seem to fix any race condition. */ + if( PaUtil_GetRingBufferReadAvailable( &blio->inputRingBuffer ) ) { + blioSetIsInputEmpty( blio, false ); + /* FIXME - why check? ret has not been set? */ + if( ret ) + return ret; + } #endif - } + } } /* Report either paNoError or paInputOverflowed. */ @@ -487,8 +486,8 @@ PaError ReadStream( PaStream* stream, /* report underflow only once: */ if( ret ) { - OSAtomicAnd32( (uint32_t)(~paInputOverflow), &blio->statusFlags ); - ret = paInputOverflowed; + OSAtomicAnd32( (uint32_t)(~paInputOverflow), &blio->statusFlags ); + ret = paInputOverflowed; } return ret; @@ -496,8 +495,8 @@ PaError ReadStream( PaStream* stream, PaError WriteStream( PaStream* stream, - const void *buffer, - unsigned long framesRequested ) + const void *buffer, + unsigned long framesRequested ) { PaIosCoreStream *iosStream = (PaIosCoreStream*)stream; PaIosBlio *blio = &iosStream->blio; @@ -510,58 +509,58 @@ PaError WriteStream( PaStream* stream, ring_buffer_size_t framesToTransfer; ring_buffer_size_t framesTransferred; - do { - framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ); + do { + framesAvailable = PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ); /* - printf( "Write Buffer is %%%g full: %ld of %ld.\n", - 100 - 100 * (float)avail / (float) blio->outputRingBuffer.bufferSize, - framesAvailable, blio->outputRingBuffer.bufferSize ); + printf( "Write Buffer is %%%g full: %ld of %ld.\n", + 100 - 100 * (float)avail / (float) blio->outputRingBuffer.bufferSize, + framesAvailable, blio->outputRingBuffer.bufferSize ); */ - if( framesAvailable == 0 ) { + if( framesAvailable == 0 ) { #ifdef PA_IOS_BLIO_MUTEX - /*block while full*/ - ret = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); - if( ret ) - return ret; - while( blio->isOutputFull ) { - ret = UNIX_ERR( pthread_cond_wait( &blio->outputCond, &blio->outputMutex ) ); + /*block while full*/ + ret = UNIX_ERR( pthread_mutex_lock( &blio->outputMutex ) ); if( ret ) - return ret; - } - ret = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); - if( ret ) - return ret; + return ret; + while( blio->isOutputFull ) { + ret = UNIX_ERR( pthread_cond_wait( &blio->outputCond, &blio->outputMutex ) ); + if( ret ) + return ret; + } + ret = UNIX_ERR( pthread_mutex_unlock( &blio->outputMutex ) ); + if( ret ) + return ret; #else - Pa_Sleep( PA_IOS_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); + Pa_Sleep( PA_IOS_BLIO_BUSY_WAIT_SLEEP_INTERVAL ); #endif - } - } while( framesAvailable == 0 && iosStream->state != STOPPING ); + } + } while( framesAvailable == 0 && iosStream->state != STOPPING ); - if( iosStream->state == STOPPING ) - { - break; - } + if( iosStream->state == STOPPING ) + { + break; + } - framesToTransfer = MIN( framesAvailable, framesRequested ); - framesTransferred = PaUtil_WriteRingBuffer( &blio->outputRingBuffer, (void *)cbuf, framesToTransfer ); - cbuf += framesTransferred * blio->outputSampleSizeActual * blio->outChan; - framesRequested -= framesTransferred; + framesToTransfer = MIN( framesAvailable, framesRequested ); + framesTransferred = PaUtil_WriteRingBuffer( &blio->outputRingBuffer, (void *)cbuf, framesToTransfer ); + cbuf += framesTransferred * blio->outputSampleSizeActual * blio->outChan; + framesRequested -= framesTransferred; #ifdef PA_IOS_BLIO_MUTEX - if( framesToTransfer == framesAvailable ) { - /* we just filled up the buffer, so we need to mark it as filled. */ - ret = blioSetIsOutputFull( blio, true ); - if( ret ) - return ret; - /* of course, in the meantime, we may have emptied the buffer, so - so check for that, too, to avoid a race condition. */ - if( PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ) ) { - blioSetIsOutputFull( blio, false ); - /* FIXME remove or review this code, does not fix race, ret not set! */ - if( ret ) + if( framesToTransfer == framesAvailable ) { + /* we just filled up the buffer, so we need to mark it as filled. */ + ret = blioSetIsOutputFull( blio, true ); + if( ret ) return ret; - } - } + /* of course, in the meantime, we may have emptied the buffer, so + so check for that, too, to avoid a race condition. */ + if( PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ) ) { + blioSetIsOutputFull( blio, false ); + /* FIXME remove or review this code, does not fix race, ret not set! */ + if( ret ) + return ret; + } + } #endif } @@ -634,4 +633,3 @@ signed long GetStreamWriteAvailable( PaStream* stream ) return PaUtil_GetRingBufferWriteAvailable( &blio->outputRingBuffer ); } - diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_blocking.h b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.h index 6afb59041..9287a85b3 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core_blocking.h +++ b/src/hostapi/coreaudio_ios/pa_ios_core_blocking.h @@ -15,7 +15,7 @@ * Olivier Tristan for feedback and testing * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O * interface. - * + * * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -41,13 +41,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_internal.h b/src/hostapi/coreaudio_ios/pa_ios_core_internal.h index 58f46609e..6ba3ce2f9 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core_internal.h +++ b/src/hostapi/coreaudio_ios/pa_ios_core_internal.h @@ -15,7 +15,7 @@ * Olivier Tristan for feedback and testing * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O * interface. - * + * * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -41,13 +41,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ @@ -89,7 +89,7 @@ PaError PaIosCore_Initialize(PaUtilHostApiRepresentation **hostApi, PaHostApiInd } #endif /* __cplusplus */ -#define RING_BUFFER_ADVANCE_DENOMINATOR 4 +#define RING_BUFFER_ADVANCE_DENOMINATOR 4 PaError ReadStream(PaStream* stream, void *buffer, unsigned long frames); PaError WriteStream(PaStream* stream, const void *buffer, unsigned long frames); @@ -140,21 +140,21 @@ typedef struct PaIosCoreStream AudioTimeStamp startTime; volatile uint32_t xrunFlags; /* PaStreamCallbackFlags*/ volatile enum { - STOPPED = 0, /* playback is completely stopped, - and the user has called StopStream(). */ - CALLBACK_STOPPED = 1, /* callback has requested stop, - but user has not yet called StopStream(). */ - STOPPING = 2, /* The stream is in the process of closing - because the user has called StopStream. - This state is just used internally; - externally it is indistinguishable from - ACTIVE.*/ - ACTIVE = 3 /* The stream is active and running. */ + STOPPED = 0, /* playback is completely stopped, + and the user has called StopStream(). */ + CALLBACK_STOPPED = 1, /* callback has requested stop, + but user has not yet called StopStream(). */ + STOPPING = 2, /* The stream is in the process of closing + because the user has called StopStream. + This state is just used internally; + externally it is indistinguishable from + ACTIVE.*/ + ACTIVE = 3 /* The stream is active and running. */ } state; double sampleRate; PaIosCoreDeviceProperties inputProperties; PaIosCoreDeviceProperties outputProperties; - + /* data updated by main thread and notifications, protected by timingInformationMutex */ int timingInformationMutexIsInitialized; pthread_mutex_t timingInformationMutex; @@ -163,7 +163,7 @@ typedef struct PaIosCoreStream Float64 timestampOffsetCombined; Float64 timestampOffsetInputDevice; Float64 timestampOffsetOutputDevice; - + /* Offsets in seconds to be applied to Apple timestamps to convert them to PA timestamps. * While the io proc is active, the following values are only accessed and manipulated by the ioproc */ Float64 timestampOffsetCombined_ioProcCopy; diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c index 5d936ee30..0b42db0da 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c +++ b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c @@ -69,157 +69,157 @@ PaError PaIosCore_SetUnixError(int err, int line) { - PaError ret; - const char *errorText; + PaError ret; + const char *errorText; - if (err == 0) - return paNoError; + if (err == 0) + return paNoError; - ret = paNoError; + ret = paNoError; - errorText = strerror(err); + errorText = strerror(err); - if (err == ENOMEM) - ret = paInsufficientMemory; - else - ret = paInternalError; + if (err == ENOMEM) + ret = paInsufficientMemory; + else + ret = paInternalError; - PaUtil_SetLastHostErrorInfo(paCoreAudio, err, errorText); + PaUtil_SetLastHostErrorInfo(paCoreAudio, err, errorText); - return (ret); + return (ret); } PaError PaIosCore_SetError(OSStatus error, int line, int isError) { - PaError result; - const char *errorType; - const char *errorText; - - switch (error) { - case kAudioServicesNoError: - return paNoError; - case kAudioFormatUnspecifiedError: - errorText = "Unspecified Audio Format Error"; - result = paInternalError; - break; - case kAudioFormatUnknownFormatError: - errorText = "Audio Format: Unknown Format Error"; - result = paInternalError; - break; - case kAudioFormatBadPropertySizeError: - errorText = "Audio Format: Bad Property Size"; - result = paInternalError; - break; - case kAudioFormatUnsupportedPropertyError: - errorText = "Audio Format: Unsupported Property Error"; - result = paInternalError; - break; - case kAudioUnitErr_InvalidProperty: - errorText = "Audio Unit: Invalid Property"; - result = paInternalError; - break; - case kAudioUnitErr_InvalidParameter: - errorText = "Audio Unit: Invalid Parameter"; - result = paInternalError; - break; - case kAudioUnitErr_NoConnection: - errorText = "Audio Unit: No Connection"; - result = paInternalError; - break; - case kAudioUnitErr_FailedInitialization: - errorText = "Audio Unit: Initialization Failed"; - result = paInternalError; - break; - case kAudioUnitErr_TooManyFramesToProcess: - errorText = "Audio Unit: Too Many Frames"; - result = paInternalError; - break; - case kAudioUnitErr_IllegalInstrument: - errorText = "Audio Unit: Illegal Instrument"; - result = paInternalError; - break; - case kAudioUnitErr_InstrumentTypeNotFound: - errorText = "Audio Unit: Instrument Type Not Found"; - result = paInternalError; - break; - case kAudioUnitErr_InvalidFile: - errorText = "Audio Unit: Invalid File"; - result = paInternalError; - break; - case kAudioUnitErr_UnknownFileType: - errorText = "Audio Unit: Unknown File Type"; - result = paInternalError; - break; - case kAudioUnitErr_FileNotSpecified: - errorText = "Audio Unit: File Not Specified"; - result = paInternalError; - break; - case kAudioUnitErr_FormatNotSupported: - errorText = "Audio Unit: Format Not Supported"; - result = paInternalError; - break; - case kAudioUnitErr_Uninitialized: - errorText = "Audio Unit: Unitialized"; - result = paInternalError; - break; - case kAudioUnitErr_InvalidScope: - errorText = "Audio Unit: Invalid Scope"; - result = paInternalError; - break; - case kAudioUnitErr_PropertyNotWritable: - errorText = "Audio Unit: PropertyNotWritable"; - result = paInternalError; - break; - case kAudioUnitErr_InvalidPropertyValue: - errorText = "Audio Unit: Invalid Property Value"; - result = paInternalError; - break; - case kAudioUnitErr_PropertyNotInUse: - errorText = "Audio Unit: Property Not In Use"; - result = paInternalError; - break; - case kAudioUnitErr_Initialized: - errorText = "Audio Unit: Initialized"; - result = paInternalError; - break; - case kAudioUnitErr_InvalidOfflineRender: - errorText = "Audio Unit: Invalid Offline Render"; - result = paInternalError; - break; - case kAudioUnitErr_Unauthorized: - errorText = "Audio Unit: Unauthorized"; - result = paInternalError; - break; - case kAudioUnitErr_CannotDoInCurrentContext: - errorText = "Audio Unit: cannot do in current context"; - result = paInternalError; - break; - default: - errorText = "Unknown Error"; - result = paInternalError; - break; - } - - if (isError) - errorType = "Error"; - else - errorType = "Warning"; - - char str[20]; - - /* see if it appears to be a 4-char-code */ - *(UInt32 *) (str + 1) = CFSwapInt32HostToBig(error); - - if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { - str[0] = str[5] = '\''; - str[6] = '\0'; - } else { - /* no, format it as an integer */ - snprintf(str, sizeof(str), "%d", (int)error); - } - - PaUtil_SetLastHostErrorInfo(paCoreAudio, error, errorText); - - return (result); + PaError result; + const char *errorType; + const char *errorText; + + switch (error) { + case kAudioServicesNoError: + return paNoError; + case kAudioFormatUnspecifiedError: + errorText = "Unspecified Audio Format Error"; + result = paInternalError; + break; + case kAudioFormatUnknownFormatError: + errorText = "Audio Format: Unknown Format Error"; + result = paInternalError; + break; + case kAudioFormatBadPropertySizeError: + errorText = "Audio Format: Bad Property Size"; + result = paInternalError; + break; + case kAudioFormatUnsupportedPropertyError: + errorText = "Audio Format: Unsupported Property Error"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidProperty: + errorText = "Audio Unit: Invalid Property"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidParameter: + errorText = "Audio Unit: Invalid Parameter"; + result = paInternalError; + break; + case kAudioUnitErr_NoConnection: + errorText = "Audio Unit: No Connection"; + result = paInternalError; + break; + case kAudioUnitErr_FailedInitialization: + errorText = "Audio Unit: Initialization Failed"; + result = paInternalError; + break; + case kAudioUnitErr_TooManyFramesToProcess: + errorText = "Audio Unit: Too Many Frames"; + result = paInternalError; + break; + case kAudioUnitErr_IllegalInstrument: + errorText = "Audio Unit: Illegal Instrument"; + result = paInternalError; + break; + case kAudioUnitErr_InstrumentTypeNotFound: + errorText = "Audio Unit: Instrument Type Not Found"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidFile: + errorText = "Audio Unit: Invalid File"; + result = paInternalError; + break; + case kAudioUnitErr_UnknownFileType: + errorText = "Audio Unit: Unknown File Type"; + result = paInternalError; + break; + case kAudioUnitErr_FileNotSpecified: + errorText = "Audio Unit: File Not Specified"; + result = paInternalError; + break; + case kAudioUnitErr_FormatNotSupported: + errorText = "Audio Unit: Format Not Supported"; + result = paInternalError; + break; + case kAudioUnitErr_Uninitialized: + errorText = "Audio Unit: Unitialized"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidScope: + errorText = "Audio Unit: Invalid Scope"; + result = paInternalError; + break; + case kAudioUnitErr_PropertyNotWritable: + errorText = "Audio Unit: PropertyNotWritable"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidPropertyValue: + errorText = "Audio Unit: Invalid Property Value"; + result = paInternalError; + break; + case kAudioUnitErr_PropertyNotInUse: + errorText = "Audio Unit: Property Not In Use"; + result = paInternalError; + break; + case kAudioUnitErr_Initialized: + errorText = "Audio Unit: Initialized"; + result = paInternalError; + break; + case kAudioUnitErr_InvalidOfflineRender: + errorText = "Audio Unit: Invalid Offline Render"; + result = paInternalError; + break; + case kAudioUnitErr_Unauthorized: + errorText = "Audio Unit: Unauthorized"; + result = paInternalError; + break; + case kAudioUnitErr_CannotDoInCurrentContext: + errorText = "Audio Unit: cannot do in current context"; + result = paInternalError; + break; + default: + errorText = "Unknown Error"; + result = paInternalError; + break; + } + + if (isError) + errorType = "Error"; + else + errorType = "Warning"; + + char str[20]; + + /* see if it appears to be a 4-char-code */ + *(UInt32 *) (str + 1) = CFSwapInt32HostToBig(error); + + if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) { + str[0] = str[5] = '\''; + str[6] = '\0'; + } else { + /* no, format it as an integer */ + snprintf(str, sizeof(str), "%d", (int)error); + } + + PaUtil_SetLastHostErrorInfo(paCoreAudio, error, errorText); + + return (result); } diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.h b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.h index 0dd624fa2..a5ddd5592 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.h +++ b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.h @@ -15,7 +15,7 @@ * Olivier Tristan for feedback and testing * Glenn Zelniker and Z-Systems engineering for sponsoring the Blocking I/O * interface. - * + * * * Based on the Open Source API proposed by Ross Bencina * Copyright (c) 1999-2002 Ross Bencina, Phil Burk @@ -41,13 +41,13 @@ */ /* - * The text above constitutes the entire PortAudio license; however, + * The text above constitutes the entire PortAudio license; however, * the PortAudio community also makes the following non-binding requests: * * Any person wishing to distribute modifications to the Software is * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the * license above. */ @@ -76,7 +76,7 @@ #define MAX(a, b) (((a)<(b))?(b):(a)) #endif -#define ERR(ios_error) PaIosCore_SetError(ios_error, __LINE__, 1 ) +#define ERR(ios_error) PaIosCore_SetError(ios_error, __LINE__, 1 ) #define WARNING(ios_error) PaIosCore_SetError(ios_error, __LINE__, 0 ) @@ -114,8 +114,8 @@ # define VVDBUG(MSG) #endif -#define UNIX_ERR(err) \ - PaIosCore_SetUnixError(err, __LINE__) +#define UNIX_ERR(err) \ + PaIosCore_SetUnixError(err, __LINE__) PaError PaIosCore_SetUnixError(int err, int line); PaError PaIosCore_SetError(OSStatus error, int line, int isError); From 54ed1952baa21fb4891a5417eb5637bd13e48912 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 10 Feb 2024 00:20:33 +0100 Subject: [PATCH 5/6] Fix return type formatting issues --- src/hostapi/coreaudio_ios/pa_ios_core.c | 71 +++++++------------ .../coreaudio_ios/pa_ios_core_utilities.c | 6 +- 2 files changed, 29 insertions(+), 48 deletions(-) diff --git a/src/hostapi/coreaudio_ios/pa_ios_core.c b/src/hostapi/coreaudio_ios/pa_ios_core.c index 1f19573da..0d49c46eb 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core.c +++ b/src/hostapi/coreaudio_ios/pa_ios_core.c @@ -105,8 +105,7 @@ static double GetStreamCpuLoad(PaStream * stream); /* * Callback called when starting or stopping a stream. */ -static void -startStopCallback( +static void startStopCallback( void *inRefCon, AudioUnit ci, AudioUnitPropertyID inID, @@ -134,8 +133,8 @@ startStopCallback( sfc(stream->streamRepresentation.userData); } -static void -FillDeviceInfo(PaIosAUHAL * auhalHostApi, +static void FillDeviceInfo( + PaIosAUHAL * auhalHostApi, PaDeviceInfo * deviceInfo, PaHostApiIndex hostApiIndex) { @@ -154,8 +153,7 @@ FillDeviceInfo(PaIosAUHAL * auhalHostApi, deviceInfo->defaultHighOutputLatency = 0.080; } -PaError -PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex hostApiIndex) +PaError PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex hostApiIndex) { PaError result = paNoError; PaIosAUHAL *auhalHostApi = NULL; @@ -233,8 +231,7 @@ PaIosCore_Initialize(PaUtilHostApiRepresentation ** hostApi, PaHostApiIndex host return (result); } -static void -Terminate(struct PaUtilHostApiRepresentation *hostApi) +static void Terminate(struct PaUtilHostApiRepresentation *hostApi) { PaIosAUHAL *auhalHostApi = (PaIosAUHAL *) hostApi; @@ -245,8 +242,8 @@ Terminate(struct PaUtilHostApiRepresentation *hostApi) PaUtil_FreeMemory(auhalHostApi); } -static PaError -IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, +static PaError IsFormatSupported( + struct PaUtilHostApiRepresentation *hostApi, const PaStreamParameters * inputParameters, const PaStreamParameters * outputParameters, double sampleRate) @@ -297,8 +294,7 @@ IsFormatSupported(struct PaUtilHostApiRepresentation *hostApi, } /* ================================================================================= */ -static void -InitializeDeviceProperties(PaIosCoreDeviceProperties * deviceProperties) +static void InitializeDeviceProperties(PaIosCoreDeviceProperties * deviceProperties) { memset(deviceProperties, 0, sizeof(PaIosCoreDeviceProperties)); deviceProperties->sampleRate = 1.0; /* Better than random. @@ -307,8 +303,7 @@ InitializeDeviceProperties(PaIosCoreDeviceProperties * deviceProperties) deviceProperties->samplePeriod = 1.0 / deviceProperties->sampleRate; } -static Float64 -CalculateSoftwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) +static Float64 CalculateSoftwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) { UInt32 latencyFrames = deviceProperties->bufferFrameSize + deviceProperties->deviceLatency + deviceProperties->safetyOffset; @@ -316,8 +311,7 @@ CalculateSoftwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDevice * sampleRate but faster */ } -static Float64 -CalculateHardwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) +static Float64 CalculateHardwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDeviceProperties * deviceProperties) { return deviceProperties->deviceLatency * deviceProperties->samplePeriod; /* same as dividing by * sampleRate but faster */ @@ -327,8 +321,7 @@ CalculateHardwareLatencyFromProperties(PaIosCoreStream * stream, PaIosCoreDevice * from the device properties. The final results of this calculation * will be used in the audio callback function. */ -static void -UpdateTimeStampOffsets(PaIosCoreStream * stream) +static void UpdateTimeStampOffsets(PaIosCoreStream * stream) { Float64 inputSoftwareLatency = 0.0; Float64 inputHardwareLatency = 0.0; @@ -351,8 +344,7 @@ UpdateTimeStampOffsets(PaIosCoreStream * stream) pthread_mutex_unlock(&stream->timingInformationMutex); } -static PaError -OpenAndSetupOneAudioUnit( +static PaError OpenAndSetupOneAudioUnit( const PaIosCoreStream * stream, const PaStreamParameters * inStreamParams, const PaStreamParameters * outStreamParams, @@ -533,8 +525,8 @@ OpenAndSetupOneAudioUnit( return (paResult); } -static long -computeRingBufferSize(const PaStreamParameters * inputParameters, +static long computeRingBufferSize( + const PaStreamParameters * inputParameters, const PaStreamParameters * outputParameters, long inputFramesPerBuffer, long outputFramesPerBuffer, @@ -583,8 +575,8 @@ computeRingBufferSize(const PaStreamParameters * inputParameters, return ringSize; } -static PaError -OpenStream(struct PaUtilHostApiRepresentation *hostApi, +static PaError OpenStream( + struct PaUtilHostApiRepresentation *hostApi, PaStream ** s, const PaStreamParameters * inputParameters, const PaStreamParameters * outputParameters, @@ -937,8 +929,8 @@ GetStreamTime(PaStream * s) * Called by the AudioUnit API to process audio from the sound card. * This is where the magic happens. */ -static OSStatus -AudioIOProc(void *inRefCon, +static OSStatus AudioIOProc( + void *inRefCon, AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, UInt32 inBusNumber, @@ -1248,8 +1240,7 @@ AudioIOProc(void *inRefCon, return noErr; } -static PaError -CloseStream(PaStream * s) +static PaError CloseStream(PaStream * s) { PaIosCoreStream *stream = (PaIosCoreStream *) s; PaError result = paNoError; @@ -1286,8 +1277,7 @@ CloseStream(PaStream * s) return (result); } -static PaError -StartStream(PaStream * s) +static PaError StartStream(PaStream * s) { PaIosCoreStream *stream = (PaIosCoreStream *) s; OSStatus result = noErr; @@ -1311,8 +1301,7 @@ StartStream(PaStream * s) #undef ERR_WRAP } -static OSStatus -BlockWhileAudioUnitIsRunning(AudioUnit audioUnit, AudioUnitElement element) +static OSStatus BlockWhileAudioUnitIsRunning(AudioUnit audioUnit, AudioUnitElement element) { Boolean isRunning; @@ -1329,8 +1318,7 @@ BlockWhileAudioUnitIsRunning(AudioUnit audioUnit, AudioUnitElement element) return (noErr); } -static PaError -FinishStoppingStream(PaIosCoreStream * stream) +static PaError FinishStoppingStream(PaIosCoreStream * stream) { OSStatus result = noErr; PaError paErr; @@ -1379,8 +1367,7 @@ FinishStoppingStream(PaIosCoreStream * stream) #undef ERR_WRAP } -static PaError -StopStream(PaStream * s) +static PaError StopStream(PaStream * s) { PaIosCoreStream *stream = (PaIosCoreStream *) s; PaError paErr; @@ -1395,8 +1382,7 @@ StopStream(PaStream * s) return (FinishStoppingStream(stream)); } -static PaError -AbortStream(PaStream * s) +static PaError AbortStream(PaStream * s) { PaIosCoreStream *stream = (PaIosCoreStream *) s; @@ -1404,24 +1390,21 @@ AbortStream(PaStream * s) return (FinishStoppingStream(stream)); } -static PaError -IsStreamStopped(PaStream * s) +static PaError IsStreamStopped(PaStream * s) { PaIosCoreStream *stream = (PaIosCoreStream *) s; return (stream->state == STOPPED); } -static PaError -IsStreamActive(PaStream * s) +static PaError IsStreamActive(PaStream * s) { PaIosCoreStream *stream = (PaIosCoreStream *) s; return (stream->state == ACTIVE || stream->state == STOPPING); } -static double -GetStreamCpuLoad(PaStream * s) +static double GetStreamCpuLoad(PaStream * s) { PaIosCoreStream *stream = (PaIosCoreStream *) s; diff --git a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c index 0b42db0da..1343107fc 100644 --- a/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c +++ b/src/hostapi/coreaudio_ios/pa_ios_core_utilities.c @@ -66,8 +66,7 @@ #include -PaError -PaIosCore_SetUnixError(int err, int line) +PaError PaIosCore_SetUnixError(int err, int line) { PaError ret; const char *errorText; @@ -89,8 +88,7 @@ PaIosCore_SetUnixError(int err, int line) return (ret); } -PaError -PaIosCore_SetError(OSStatus error, int line, int isError) +PaError PaIosCore_SetError(OSStatus error, int line, int isError) { PaError result; const char *errorType; From d81fda8814ddbeef651d011613ca768d7c7daf22 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sat, 10 Feb 2024 00:24:55 +0100 Subject: [PATCH 6/6] Add iOS CI workflow --- .github/workflows/cmake.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index b56cde4c3..1eac06f34 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -59,6 +59,12 @@ jobs: cmake_generator: "Unix Makefiles" cmake_options: -DCMAKE_FRAMEWORK=ON + - name: iOS Clang cross compilation + os: macOS-latest + vcpkg_triplet: arm64-ios + cmake_generator: "Unix Makefiles" + cmake_options: + -DCMAKE_SYSTEM_NAME=iOS runs-on: ${{ matrix.os }} name: ${{ matrix.name }}