import AVFoundation import os /// SPSC-ish jitter ring (interleaved float, `channels` per frame), drain thread → render /// callback. The unfair lock is held for microseconds; fine at render-callback rates. Priming: /// reads return silence until enough is buffered (at least `prefill`, and at least one /// packet more than the device's render quantum — large-buffer devices would otherwise /// chronically out-demand the prefill and oscillate prime → dropout → re-prime), and an /// underrun re-primes, concealing jitter as one short dip instead of sustained crackle. /// All counts stay whole frames (multiples of `channels`), so the interleave can never slip. final class AudioRing: @unchecked Sendable { private var buf: [Float] private var readIdx = 0 private var writeIdx = 0 private var primed = false private var renderQuantum = 0 private let prefill: Int private let highWater: Int private let channels: Int private let lock = OSAllocatedUnfairLock() /// `capacity`/`prefill` in samples (interleaved — `channels` per frame, both whole frames). init(capacity: Int, prefill: Int, channels: Int) { buf = [Float](repeating: 0, count: capacity) self.prefill = prefill self.channels = channels highWater = prefill * 4 } func write(_ samples: UnsafePointer, count: Int) { lock.lock() defer { lock.unlock() } let capacity = buf.count // A single write larger than the whole ring would push readIdx PAST writeIdx below // (inverting the valid range — corruption). It never happens (one decoded packet is far // under capacity), but guard rather than corrupt. guard count <= capacity else { return } if writeIdx + count - readIdx > capacity { readIdx = writeIdx + count - capacity // overflow: drop oldest } for i in 0.. highWater { readIdx = writeIdx - prefill * 2 } } /// Fills `out` completely (silence beyond what's buffered). func read(into out: UnsafeMutablePointer, count: Int) { lock.lock() defer { lock.unlock() } renderQuantum = max(renderQuantum, count) let available = writeIdx - readIdx if !primed { // One 5 ms host packet (240 frames × channels) of slack beyond the device's demand. if available >= max(prefill, renderQuantum + 240 * channels) { primed = true } else { for i in 0.. AVAudioChannelLayout? { let labels: [AudioChannelLabel] switch channels { case 6: labels = [ kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, ] case 8: labels = [ kAudioChannelLabel_Left, kAudioChannelLabel_Right, kAudioChannelLabel_Center, kAudioChannelLabel_LFEScreen, kAudioChannelLabel_LeftSurround, kAudioChannelLabel_RightSurround, // wire RL/RR (back) kAudioChannelLabel_LeftSurroundDirect, kAudioChannelLabel_RightSurroundDirect, // wire SL/SR (side) ] default: return nil } let size = MemoryLayout.size + (labels.count - 1) * MemoryLayout.stride let raw = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: 16) defer { raw.deallocate() } let layout = raw.bindMemory(to: AudioChannelLayout.self, capacity: 1) layout.pointee.mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions layout.pointee.mChannelBitmap = AudioChannelBitmap(rawValue: 0) layout.pointee.mNumberChannelDescriptions = UInt32(labels.count) // `mChannelDescriptions` is the C variable-length tail array (declared `[1]`, over-allocated // above). Scope the pointer with `withUnsafeMutablePointer` — taking `&…mChannelDescriptions` // inline yields a pointer valid only for that expression, so building a buffer from it that // outlives the call is a dangling-pointer bug. Inside the closure it stays valid while we fill it. withUnsafeMutablePointer(to: &layout.pointee.mChannelDescriptions) { tail in let descs = UnsafeMutableBufferPointer(start: tail, count: labels.count) for (i, lbl) in labels.enumerated() { descs[i] = AudioChannelDescription( mChannelLabel: lbl, mChannelFlags: AudioChannelFlags(rawValue: 0), mCoordinates: (0, 0, 0)) } } return AVAudioChannelLayout(layout: layout) }