optimise hot-path audio pipeline: zero-copy ring buffer, pre-allocated converter buffers

- Replace Swift Array<UInt8> ring buffer with UnsafeMutableRawPointer to
  eliminate COW ref-count checks on every write/read
- Add append(from:count:) to copy directly from Core Audio buffer pointer
  into the ring buffer, removing the per-callback Data heap allocation
- Pre-allocate AVAudioPCMBuffer pair in AudioFormatConverter and reuse
  across transform() calls (lazy init, capacity-checked)
- Fix float-to-int truncation in output frame count calculation (ceil)
- Add comprehensive AudioBuffer test suite (12 tests) including proper
  wrap-around coverage for both append and read paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Payne
2026-03-06 21:32:42 +00:00
parent 1cd2e83060
commit 85975d6cc3
8 changed files with 423 additions and 73 deletions
@@ -8,7 +8,7 @@ public class AudioTapManager {
private var deviceID: AudioObjectID?
public init() {}
deinit {
AudioTeeLogging.logger.debug("Cleaning up audio tap manager")
@@ -76,7 +76,8 @@ public class AudioTapManager {
AudioTeeLogging.logger.debug(
"AudioHardwareCreateProcessTap completed", context: ["status": String(status)])
guard status == kAudioHardwareNoError else {
AudioTeeLogging.logger.error("Failed to create audio tap", context: ["status": String(status)])
AudioTeeLogging.logger.error(
"Failed to create audio tap", context: ["status": String(status)])
throw AudioTeeError.tapCreationFailed(status)
}
@@ -115,7 +116,8 @@ public class AudioTapManager {
let status = AudioHardwareCreateAggregateDevice(description as CFDictionary, &deviceID)
guard status == kAudioHardwareNoError else {
AudioTeeLogging.logger.error("Failed to create aggregate device", context: ["status": String(status)])
AudioTeeLogging.logger.error(
"Failed to create aggregate device", context: ["status": String(status)])
throw AudioTeeError.aggregateDeviceCreationFailed(status)
}