Files
Nick Payne 85975d6cc3 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>
2026-03-06 21:32:42 +00:00

111 lines
3.9 KiB
Swift

import AudioToolbox
import CoreAudio
import Foundation
public class AudioFormatManager {
public static func getDeviceFormat(deviceID: AudioObjectID) throws -> AudioStreamBasicDescription
{
// First, wait for the device to become alive/ready
let deviceReadyTimeout = 2.0 // 2 seconds max wait
let pollInterval = 0.1 // 100ms poll interval
let maxPolls = Int(deviceReadyTimeout / pollInterval)
AudioTeeLogging.logger.debug(
"Waiting for audio device to become ready", context: ["device_id": String(deviceID)])
// Poll device readiness
for poll in 1...maxPolls {
if isAudioDeviceValid(deviceID) {
AudioTeeLogging.logger.debug(
"Audio device is ready", context: ["device_id": String(deviceID), "polls": String(poll)])
break
}
if poll == maxPolls {
AudioTeeLogging.logger.info(
"Device did not become ready within timeout, proceeding anyway",
context: [
"device_id": String(deviceID),
"timeout_seconds": String(deviceReadyTimeout),
])
break
}
AudioTeeLogging.logger.info("------- not ready; retrying...")
Thread.sleep(forTimeInterval: pollInterval)
}
// Now attempt to get the stream format with limited retries
let maxRetries = 3 // Reduced since device should be ready
let retryDelayMs = 20 // Shorter delay since we've already waited for readiness
for attempt in 1...maxRetries {
var propertyAddress = getPropertyAddress(
selector: kAudioDevicePropertyStreamFormat,
scope: kAudioDevicePropertyScopeInput)
var propertySize = UInt32(MemoryLayout<AudioStreamBasicDescription>.stride)
var streamFormat = AudioStreamBasicDescription()
let status = AudioObjectGetPropertyData(
deviceID, &propertyAddress, 0, nil, &propertySize, &streamFormat)
if status == noErr {
AudioTeeLogging.logger.debug(
"Successfully retrieved device format", context: ["attempt": String(attempt)])
return streamFormat
}
AudioTeeLogging.logger.info(
"------- Failed to get stream format after device ready check, retrying...",
context: [
"attempt": String(attempt),
"max_retries": String(maxRetries),
"status": String(status),
"device_id": String(deviceID),
])
// Don't delay on the last attempt
if attempt < maxRetries {
Thread.sleep(forTimeInterval: Double(retryDelayMs) / 1000.0)
}
}
// If all attempts failed after device readiness confirmation, this is a genuine error
AudioTeeLogging.logger.error(
"Failed to get device format after device readiness check and retries",
context: [
"device_id": String(deviceID),
"device_was_ready": "true",
])
throw AudioTeeError.deviceFormatUnavailable(deviceID)
}
static func createMetadata(for format: AudioStreamBasicDescription) -> AudioStreamMetadata {
return AudioStreamMetadata(
sampleRate: format.mSampleRate,
channelsPerFrame: format.mChannelsPerFrame,
bitsPerChannel: format.mBitsPerChannel,
isFloat: format.mFormatFlags & kAudioFormatFlagIsFloat != 0,
captureMode: "audio",
deviceName: nil, // TODO: Get device name if needed
deviceUID: nil, // TODO: Get device UID if needed
encoding: format.mFormatFlags & kAudioFormatFlagIsFloat != 0 ? "pcm_f32le" : "pcm_s16le"
)
}
public static func logFormatInfo(_ format: AudioStreamBasicDescription) {
AudioTeeLogging.logger.debug(
"Using device's native format",
context: [
"channels": String(format.mChannelsPerFrame),
"sample_rate": String(format.mSampleRate),
"bits_per_channel": String(format.mBitsPerChannel),
"format_id": String(format.mFormatID),
"format_flags": String(format: "0x%08x", format.mFormatFlags),
"bytes_per_frame": String(format.mBytesPerFrame),
]
)
}
}