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:
@@ -36,7 +36,8 @@ public class AudioRecorder {
|
||||
if let targetSampleRate = convertToSampleRate {
|
||||
// Validate sample rate
|
||||
guard AudioFormatConverter.isValidSampleRate(targetSampleRate) else {
|
||||
AudioTeeLogging.logger.error("Invalid sample rate", context: ["sample_rate": String(targetSampleRate)])
|
||||
AudioTeeLogging.logger.error(
|
||||
"Invalid sample rate", context: ["sample_rate": String(targetSampleRate)])
|
||||
self.converter = nil
|
||||
self.finalFormat = sourceFormat
|
||||
return
|
||||
@@ -108,14 +109,16 @@ public class AudioRecorder {
|
||||
let bufferList = inputData.pointee
|
||||
let firstBuffer = bufferList.mBuffers
|
||||
|
||||
guard firstBuffer.mData != nil && firstBuffer.mDataByteSize > 0 else {
|
||||
guard let sourcePointer = firstBuffer.mData, firstBuffer.mDataByteSize > 0 else {
|
||||
AudioTeeLogging.logger.error("Received empty audio buffer")
|
||||
return noErr
|
||||
}
|
||||
|
||||
// Append raw audio data to buffer
|
||||
let audioData = Data(bytes: firstBuffer.mData!, count: Int(firstBuffer.mDataByteSize))
|
||||
audioBuffer?.append(audioData)
|
||||
// Copy directly from the Core Audio buffer into our ring buffer.
|
||||
// This avoids creating an intermediate Data object (heap alloc + memcpy)
|
||||
// on every IO callback (~10ms). The pointer is valid for the duration
|
||||
// of this callback, so this is safe.
|
||||
audioBuffer?.append(from: sourcePointer, count: Int(firstBuffer.mDataByteSize))
|
||||
|
||||
processAudioBuffer()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user