Skip to content

MicCapture

MicCapture is part of the AgentSquadAudio product. It taps AVAudioEngine’s input node, converts the hardware stream to PCM16 @ 24 kHz mono, and yields frames through a bounded AsyncStream<Data>.

import AgentSquadAudio

public init(sampleRate: Double = 24_000, maxBufferedFrames: Int = 16)
ParameterDefaultNotes
sampleRate24_000Target sample rate in Hz. Must match what the realtime runtime expects.
maxBufferedFrames16Capacity of the internal AsyncStream. Under back-pressure the oldest frames are dropped — the tap thread never blocks.

public let frames: AsyncStream<Data> // yields PCM16 little-endian mono frames
public func start() async throws // installs tap, starts engine, requests mic permission (iOS)
public func stop() async // stops engine, removes tap, finishes the stream

start() is idempotent — a second call before stop() is a no-op. Calling start() again after stop() is not supported; create a new instance.


public enum MicCaptureError: Error, Equatable {
case permissionDenied // user denied mic access (iOS only)
case converterUnavailable // AVAudioConverter could not be initialised for the hardware format
}

On iOS, start() requests microphone access before installing the tap:

  • iOS 17+ — uses AVAudioApplication.requestRecordPermission
  • iOS 16 — falls back to AVAudioSession.requestRecordPermission (the deprecated overload; no compiler warning fires at the iOS 16 deployment floor)

If the user denies permission, start() throws MicCaptureError.permissionDenied.


On iOS, start() calls the internal VoiceAudioSession.activate() helper before installing the tap. It configures the shared AVAudioSession:

category: .playAndRecord
mode: .voiceChat
options: [.defaultToSpeaker, .allowBluetoothHFP]

VoiceAudioSession is idempotent — re-activating an already-active session is a no-op. On macOS the call is compiled out (#if os(iOS)) and the system default audio device is used.


let mic = MicCapture() // 24 kHz, 16-frame queue
try await mic.start()
for await frame in mic.frames {
// frame: Data containing PCM16 little-endian mono samples
}
await mic.stop()

Pass MicCapture directly to RealtimeRuntime — the runtime only cares about the AudioInput protocol:

let runtime = RealtimeRuntime(
input: MicCapture(),
output: AudioPlayback(),
// ... other config
)