diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1296b8eb14a4c..f6f6029d5222e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,8 +17,7 @@ jobs: vcpkg_buildtrees-root: C:\build vcpkg_bootstrap: .\bootstrap-vcpkg.bat vcpkg_triplet: x64-windows-release - vcpkg_host_triplet: x64-windows-release - vcpkg_overlay_ports: overlay/windows;overlay/ports + vcpkg_host_triplet: x64-windows-release check_disk_space: Get-PSDrive - os: macos-11 vcpkg_path: /Users/runner/mixxx-vcpkg @@ -26,8 +25,6 @@ jobs: vcpkg_bootstrap: ./bootstrap-vcpkg.sh vcpkg_triplet: x64-osx-min1015-release vcpkg_host_triplet: x64-osx-min1015-release - vcpkg_overlay_ports: overlay/osx:overlay/ports - vcpkg_packages_extras: check_disk_space: df -h - os: macos-11 vcpkg_path: /Users/runner/mixxx-vcpkg @@ -35,14 +32,10 @@ jobs: vcpkg_bootstrap: ./bootstrap-vcpkg.sh vcpkg_triplet: arm64-osx-min1100-release vcpkg_host_triplet: x64-osx-min1015-release - vcpkg_overlay_ports: overlay/osx:overlay/ports check_disk_space: df -h env: VCPKG_DEFAULT_TRIPLET: ${{ matrix.vcpkg_triplet }} VCPKG_DEFAULT_HOST_TRIPLET: ${{ matrix.vcpkg_host_triplet }} - # Using the relative path overlay/triplets does not work (https://github.com/microsoft/vcpkg/issues/18764) - VCPKG_OVERLAY_TRIPLETS: ${{ matrix.vcpkg_path }}/overlay/triplets - VCPKG_OVERLAY_PORTS: ${{ matrix.vcpkg_overlay_ports }} DEPS_BASE_NAME: mixxx-deps DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer MIXXX_VERSION: 2.5 diff --git a/overlay/windows/libid3tag/10_utf16.diff b/overlay/ports/libid3tag/10_utf16.diff similarity index 100% rename from overlay/windows/libid3tag/10_utf16.diff rename to overlay/ports/libid3tag/10_utf16.diff diff --git a/overlay/windows/libid3tag/11_unknown_encoding.diff b/overlay/ports/libid3tag/11_unknown_encoding.diff similarity index 100% rename from overlay/windows/libid3tag/11_unknown_encoding.diff rename to overlay/ports/libid3tag/11_unknown_encoding.diff diff --git a/overlay/windows/libid3tag/CMakeLists.txt b/overlay/ports/libid3tag/CMakeLists.txt similarity index 100% rename from overlay/windows/libid3tag/CMakeLists.txt rename to overlay/ports/libid3tag/CMakeLists.txt diff --git a/overlay/windows/libid3tag/CVE-2008-2109.patch b/overlay/ports/libid3tag/CVE-2008-2109.patch similarity index 100% rename from overlay/windows/libid3tag/CVE-2008-2109.patch rename to overlay/ports/libid3tag/CVE-2008-2109.patch diff --git a/overlay/windows/libid3tag/id3tagConfig.cmake.in b/overlay/ports/libid3tag/id3tagConfig.cmake.in similarity index 100% rename from overlay/windows/libid3tag/id3tagConfig.cmake.in rename to overlay/ports/libid3tag/id3tagConfig.cmake.in diff --git a/overlay/windows/libid3tag/portfile.cmake b/overlay/ports/libid3tag/portfile.cmake similarity index 100% rename from overlay/windows/libid3tag/portfile.cmake rename to overlay/ports/libid3tag/portfile.cmake diff --git a/overlay/windows/libid3tag/vcpkg.json b/overlay/ports/libid3tag/vcpkg.json similarity index 100% rename from overlay/windows/libid3tag/vcpkg.json rename to overlay/ports/libid3tag/vcpkg.json diff --git a/overlay/ports/portaudio/0001-Add-basic-support-for-iOS-to-portaudio.patch b/overlay/ports/portaudio/0001-Add-basic-support-for-iOS-to-portaudio.patch new file mode 100644 index 00000000000000..fb0c68a418f93b --- /dev/null +++ b/overlay/ports/portaudio/0001-Add-basic-support-for-iOS-to-portaudio.patch @@ -0,0 +1,2893 @@ +From a7d5277e396614149add738cc5bdd83a7080056b Mon Sep 17 00:00:00 2001 +From: Hans Petter Selasky +Date: Sun, 14 Jul 2019 10:21:29 +0200 +Subject: [PATCH 1/3] 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 0000000..e4e0386 +--- /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 4ac3ab6..18301c2 100644 +--- a/src/common/pa_hostapi.h ++++ b/src/common/pa_hostapi.h +@@ -146,6 +146,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 0000000..37d605e +--- /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 0000000..236dccc +--- /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 0000000..6afb590 +--- /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 0000000..58f4660 +--- /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 0000000..5d936ee +--- /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 0000000..0dd624f +--- /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 85fefb2..74b7ade 100644 +--- a/src/os/unix/pa_unix_hostapis.c ++++ b/src/os/unix/pa_unix_hostapis.c +@@ -51,6 +51,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. +@@ -100,6 +101,10 @@ PaUtilHostApiInitializer *paHostApiInitializers[] = + PaMacCore_Initialize, + #endif + ++#if PA_USE_COREAUDIO_IOS ++ PaIosCore_Initialize, ++#endif ++ + #if PA_USE_SKELETON + PaSkeleton_Initialize, + #endif +-- +2.43.0 + diff --git a/overlay/ports/portaudio/0002-Update-CMakeLists-with-iOS-implementation.patch b/overlay/ports/portaudio/0002-Update-CMakeLists-with-iOS-implementation.patch new file mode 100644 index 00000000000000..83a4376c39a97c --- /dev/null +++ b/overlay/ports/portaudio/0002-Update-CMakeLists-with-iOS-implementation.patch @@ -0,0 +1,64 @@ +From c5ea7a7458d74dd1aebceab6d4a5a6f533c6f8ba Mon Sep 17 00:00:00 2001 +Date: Mon, 29 Jan 2024 01:48:56 +0100 +Subject: [PATCH 2/3] Update CMakeLists with iOS implementation + +--- + CMakeLists.txt | 35 +++++++++++++++++++++++++---------- + 1 file changed, 25 insertions(+), 10 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 3d81062..218ba7c 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -267,14 +267,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) + +@@ -290,8 +301,12 @@ elseif(UNIX) + "${COREFOUNDATION_LIBRARY}" + "${CORESERVICES_LIBRARY}" + ) +- 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) +-- +2.43.0 + diff --git a/overlay/ports/portaudio/0003-Fix-renamed-memory-allocation-functions.patch b/overlay/ports/portaudio/0003-Fix-renamed-memory-allocation-functions.patch new file mode 100644 index 00000000000000..85ff60533031d4 --- /dev/null +++ b/overlay/ports/portaudio/0003-Fix-renamed-memory-allocation-functions.patch @@ -0,0 +1,51 @@ +From 0769e313b3ccf7f4d157331dafe0b492385245ec Mon Sep 17 00:00:00 2001 +Date: Mon, 5 Feb 2024 01:16:06 +0100 +Subject: [PATCH 3/3] 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 37d605e..c246ed8 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; +-- +2.43.0 + diff --git a/overlay/ports/portaudio/portfile.cmake b/overlay/ports/portaudio/portfile.cmake index c68b6d34f1cae9..e5f52bcbeceeee 100644 --- a/overlay/ports/portaudio/portfile.cmake +++ b/overlay/ports/portaudio/portfile.cmake @@ -3,6 +3,10 @@ vcpkg_from_github( REPO PortAudio/portaudio REF c8b9dd2dfc1c12230f172876a0117f42d32e48b2 SHA512 8aa489de52c40068dc87c7a6b89e5b2fd4d10f57f69b800796c6f5e2c0db71a8094a85a06b4168d6d164a79d868b28adfd525ca4e7a8d3a0193a94face569b65 + PATCHES + "0001-Add-basic-support-for-iOS-to-portaudio.patch" + "0002-Update-CMakeLists-with-iOS-implementation.patch" + "0003-Fix-renamed-memory-allocation-functions.patch" ) string(COMPARE EQUAL ${VCPKG_LIBRARY_LINKAGE} dynamic PA_BUILD_SHARED) diff --git a/overlay/ports/portaudio/vcpkg.json b/overlay/ports/portaudio/vcpkg.json index e8b7d00753e80a..06bdf252069284 100644 --- a/overlay/ports/portaudio/vcpkg.json +++ b/overlay/ports/portaudio/vcpkg.json @@ -1,6 +1,7 @@ { "name": "portaudio", "version-date": "2023-04-04", + "port-version": 1, "description": "PortAudio Portable Cross-platform Audio I/O API PortAudio is a free, cross-platform, open-source, audio I/O library. It lets you write simple audio programs in 'C' or C++ that will compile and run on many platforms including Windows, Macintosh OS X, and Unix (OSS/ALSA). It is intended to promote the exchange of audio software between developers on different platforms. Many applications use PortAudio for Audio I/O.", "homepage": "https://app.assembla.com/spaces/portaudio/wiki", "license": "MIT", diff --git a/overlay/osx/protobuf/compile_options.patch b/overlay/ports/protobuf/compile_options.patch similarity index 100% rename from overlay/osx/protobuf/compile_options.patch rename to overlay/ports/protobuf/compile_options.patch diff --git a/overlay/osx/protobuf/fix-default-proto-file-path.patch b/overlay/ports/protobuf/fix-default-proto-file-path.patch similarity index 100% rename from overlay/osx/protobuf/fix-default-proto-file-path.patch rename to overlay/ports/protobuf/fix-default-proto-file-path.patch diff --git a/overlay/osx/protobuf/fix-static-build.patch b/overlay/ports/protobuf/fix-static-build.patch similarity index 100% rename from overlay/osx/protobuf/fix-static-build.patch rename to overlay/ports/protobuf/fix-static-build.patch diff --git a/overlay/osx/protobuf/portfile.cmake b/overlay/ports/protobuf/portfile.cmake similarity index 100% rename from overlay/osx/protobuf/portfile.cmake rename to overlay/ports/protobuf/portfile.cmake diff --git a/overlay/osx/protobuf/protobuf-targets-vcpkg-protoc.cmake b/overlay/ports/protobuf/protobuf-targets-vcpkg-protoc.cmake similarity index 100% rename from overlay/osx/protobuf/protobuf-targets-vcpkg-protoc.cmake rename to overlay/ports/protobuf/protobuf-targets-vcpkg-protoc.cmake diff --git a/overlay/osx/protobuf/vcpkg-cmake-wrapper.cmake b/overlay/ports/protobuf/vcpkg-cmake-wrapper.cmake similarity index 100% rename from overlay/osx/protobuf/vcpkg-cmake-wrapper.cmake rename to overlay/ports/protobuf/vcpkg-cmake-wrapper.cmake diff --git a/overlay/osx/protobuf/vcpkg.json b/overlay/ports/protobuf/vcpkg.json similarity index 100% rename from overlay/osx/protobuf/vcpkg.json rename to overlay/ports/protobuf/vcpkg.json diff --git a/ports/soundtouch/portfile.cmake b/ports/soundtouch/portfile.cmake index 6290fad978648c..501094ddd2f3b9 100644 --- a/ports/soundtouch/portfile.cmake +++ b/ports/soundtouch/portfile.cmake @@ -3,7 +3,7 @@ vcpkg_from_github( GITHUB_HOST https://codeberg.org REPO soundtouch/soundtouch REF ${VERSION} - SHA512 3d4d6314d6fccb3cb899aee36b90799bb9bf7cf1aad9ff2fff6b18c73e68be7e0e00a3f4e31d5dd340c979236f5474b7ef95d51ddb813f353dde920bdef4cb51 + SHA512 93f757b2c1abe16be589e0d191e6c0416c5980843bd416cd5cb820b65a705d98081c0fc7ca0d9880af54b5343318262c77ba39a096bb240ceec084e93ceef964 HEAD_REF master ) diff --git a/ports/soundtouch/vcpkg.json b/ports/soundtouch/vcpkg.json index 6b68d46f7c8184..db166e6369bf3f 100644 --- a/ports/soundtouch/vcpkg.json +++ b/ports/soundtouch/vcpkg.json @@ -1,7 +1,7 @@ { "name": "soundtouch", "version": "2.3.2", - "port-version": 1, + "port-version": 2, "description": "SoundTouch is an open-source audio processing library for changing the Tempo, Pitch and Playback Rates of audio streams or audio files.", "homepage": "https://www.surina.net/soundtouch", "supports": "!uwp", diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 00000000000000..c829b73b9905aa --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1,8 @@ +{ + "overlay-ports": [ + "overlay/ports" + ], + "overlay-triplets": [ + "overlay/triplets" + ] +} diff --git a/vcpkg.json b/vcpkg.json index 2e279c4c78f86a..d0a16f13a2f058 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,10 +1,35 @@ { "name": "mixxx", - "version": "2.4.0", + "version": "2.5.0", "description": "Mixxx is free cross platform DJ software", "homepage": "https://mixxx.org/", "license": "GPL-2.0-or-later", "supports": "!uwp", + "default-features": ["qt6"], + "features": { + "qt5": { + "description": "Build with Qt 5", + "dependencies": [ + "qt5-base", + "qt5-declarative", + "qt5-script", + "qt5-svg", + "qt5-translations", + "qtkeychain" + ] + }, + "qt6": { + "description": "Build with Qt 6", + "dependencies": [ + "qt5compat", + "qtbase", + "qtdeclarative", + "qtsvg", + "qttranslations", + "qtkeychain-qt6" + ] + } + }, "dependencies": [ "ableton-link", "benchmark", @@ -15,13 +40,16 @@ "grantlee", "gtest", "hidapi", - "hss1394", + { + "name": "hss1394", + "platform": "windows | osx" + }, "libdjinterop", "libebur128", "libflac", { "name": "libid3tag", - "platform": "!osx" + "platform": "windows" }, "libkeyfinder", { @@ -49,12 +77,6 @@ "portmidi", "protobuf", "pthreads", - "qt5compat", - "qtbase", - "qtdeclarative", - "qtsvg", - "qttranslations", - "qtkeychain-qt6", "rubberband", "soundtouch", "taglib",