From 0008bd3081e739bec6c9fe087de1254b99f1a740 Mon Sep 17 00:00:00 2001 From: superdooper86 Date: Fri, 17 Jul 2026 11:19:28 +0200 Subject: [PATCH] fix: own system audio buffers before conversion --- Meetingnotes.xcodeproj/project.pbxproj | 8 ++--- meetingnotes/Managers/AudioManager.swift | 42 ++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Meetingnotes.xcodeproj/project.pbxproj b/Meetingnotes.xcodeproj/project.pbxproj index acfec83..a361152 100644 --- a/Meetingnotes.xcodeproj/project.pbxproj +++ b/Meetingnotes.xcodeproj/project.pbxproj @@ -276,7 +276,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = G9LVHZAJNX; ENABLE_HARDENED_RUNTIME = YES; @@ -290,7 +290,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.1.8; + MARKETING_VERSION = 1.1.9; ONLY_ACTIVE_ARCH = NO; OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI"; PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes; @@ -312,7 +312,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 21; DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\""; DEVELOPMENT_TEAM = G9LVHZAJNX; ENABLE_HARDENED_RUNTIME = YES; @@ -326,7 +326,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.1.8; + MARKETING_VERSION = 1.1.9; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI"; PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes; diff --git a/meetingnotes/Managers/AudioManager.swift b/meetingnotes/Managers/AudioManager.swift index d432c52..88a38d1 100644 --- a/meetingnotes/Managers/AudioManager.swift +++ b/meetingnotes/Managers/AudioManager.swift @@ -309,7 +309,7 @@ final class AudioManager: NSObject, ObservableObject { try tap.run(on: tapQueue) { [weak self] _, inputData, _, _, _ in guard let self else { return } self.processAudioBuffer( - { AVAudioPCMBuffer(pcmFormat: inputFormat, bufferListNoCopy: inputData, deallocator: nil) }, + { self.copyAudioBuffer(from: inputData, format: inputFormat) }, converter: converter, targetFormat: targetFormat, source: .system @@ -319,6 +319,42 @@ final class AudioManager: NSObject, ObservableObject { Task { await self.restartSystemAudioTap() } } } + + private func copyAudioBuffer( + from inputData: UnsafePointer, + format: AVAudioFormat + ) -> AVAudioPCMBuffer? { + guard let borrowedBuffer = AVAudioPCMBuffer( + pcmFormat: format, + bufferListNoCopy: inputData, + deallocator: nil + ), borrowedBuffer.frameLength > 0, + let ownedBuffer = AVAudioPCMBuffer( + pcmFormat: format, + frameCapacity: borrowedBuffer.frameLength + ) else { return nil } + + ownedBuffer.frameLength = borrowedBuffer.frameLength + let sourceBuffers = UnsafeMutableAudioBufferListPointer( + UnsafeMutablePointer(mutating: inputData) + ) + let destinationBuffers = UnsafeMutableAudioBufferListPointer( + ownedBuffer.mutableAudioBufferList + ) + guard sourceBuffers.count == destinationBuffers.count else { return nil } + + for index in 0.. AVAudioPCMBuffer?, @@ -326,8 +362,8 @@ final class AudioManager: NSObject, ObservableObject { targetFormat: AVAudioFormat, source: AudioSource ) { - // Keep callback-owned buffers alive until conversion finishes. Teardown - // takes this same lock before invalidating the Core Audio process tap. + // The system callback copies its borrowed Core Audio memory while this + // lock prevents teardown, then conversion operates on the owned copy. audioFileLock.lock() defer { audioFileLock.unlock() } guard isAcceptingAudio,