Audio Capture Sources¶
A capture source owns an audio input device independently of any voice session, so that a consumer which must listen before a session exists — a wake word, a level meter — no longer has to hand the device over at the moment the person starts speaking.
Normative reference: RFC Section 12.12. Narrative guide: Shared Microphone Capture.
Quick start¶
from roomkit.voice.capture import LocalMicSource
from roomkit.voice.backends.local import LocalAudioBackend
mic = LocalMicSource(sample_rate=24000, backlog_seconds=10)
mic.start()
# Listen with no session in sight. Enqueue only — never block the callback.
detector = mic.subscribe(enqueue, name="wakeword")
# The backend becomes a subscriber rather than the device's owner.
transport = LocalAudioBackend(source=mic)
mark = mic.mark() # at SPEECH_START
await channel.start_session( # once the trigger matched
room_id, participant_id, connection=None,
metadata={"capture_since": mark},
)
Install the device implementation with pip install roomkit[local-audio].
See the full example for a complete runnable script.
Source ABC¶
AudioCaptureSource ¶
AudioCaptureSource(*, sample_rate=16000, channels=1, sample_width=2, block_duration_ms=20, backlog_seconds=DEFAULT_BACKLOG_SECONDS, max_backlog_bytes=None)
Bases: ABC
A continuous source of AudioFrame shared by several consumers.
Lifetime is explicit: start() and stop() are the only things that
acquire and release the device. Dropping to zero subscribers does not stop
capture, and gaining one does not start it — which is what makes it safe
for a wake-word detector to detach for the duration of a session.
Frames are emitted raw. Echo cancellation is per-session (it needs the reference signal of what that session plays) and belongs downstream, in the backend, after fan-out. A subscriber still attached while a session is playing hears the far end unattenuated. Resampling is likewise the subscriber's concern: a source has one format.
block_duration_ms
property
¶
Nominal duration of one captured block, in milliseconds.
input_latency_ms
property
¶
Device input latency, when the implementation can report one.
Used to seed AEC stream delay. None means unknown.
subscribe ¶
Attach a consumer, optionally replaying the backlog from since.
The callback runs synchronously on the capture thread. It MUST NOT perform unbounded work: enqueue the frame and return. The source guarantees no isolation between subscribers — one slow subscriber degrades capture for all of them.
Ordering is total: every replayed frame is delivered before every live one, including frames captured during the replay itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
CaptureFrameCallback
|
Invoked once per frame. |
required |
since
|
CaptureMark | None
|
Replay from this mark before going live. A mark whose
position has been evicted replays what remains and sets
|
None
|
name
|
str | None
|
Diagnostic label used in logs and slow-subscriber warnings. |
None
|
Returns:
| Type | Description |
|---|---|
CaptureSubscription
|
The subscription handle. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Marks and subscriptions¶
CaptureMark
dataclass
¶
A position in a source's backlog.
Opaque: the fields are an implementation detail and MUST NOT be
interpreted by callers. Obtain one from :meth:AudioCaptureSource.mark
and hand it back to :meth:AudioCaptureSource.subscribe.
CaptureSubscription ¶
Implementations¶
LocalMicSource ¶
LocalMicSource(*, input_device=None, sample_rate=16000, channels=1, block_duration_ms=20, backlog_seconds=DEFAULT_BACKLOG_SECONDS, max_backlog_bytes=None)
Bases: AudioCaptureSource
Capture source owning the system microphone.
input_latency_ms
property
¶
PortAudio's reported input latency, once the stream is open.
MockCaptureSource ¶
MockCaptureSource(*, sample_rate=16000, channels=1, block_duration_ms=20, backlog_seconds=DEFAULT_BACKLOG_SECONDS, max_backlog_bytes=None)
Bases: AudioCaptureSource
Capture source driven by explicit feed() calls.
Frames are dispatched on the calling thread, which stands in for the capture thread. Tests that need to exercise the catch-up path can feed from a second thread while a subscriber is replaying.
The subscriber contract¶
Fan-out is synchronous on the capture thread — that is what keeps echo cancellation's capture/reference timing in step. A subscriber must not perform unbounded work in the callback: enqueue the frame and return. The source guarantees no isolation between subscribers, times each callback, and warns by name when one runs long.
Frames are emitted raw, pre-AEC: echo cancellation is per-session and applied downstream in the backend. A subscriber left attached during a call hears the far end unattenuated.