Skip to content

Fix FrameProcessor lifecycle for selector based noise cancellation#5433

Open
Topherhindman wants to merge 4 commits intomainfrom
fix-selector-noise-cancellation
Open

Fix FrameProcessor lifecycle for selector based noise cancellation#5433
Topherhindman wants to merge 4 commits intomainfrom
fix-selector-noise-cancellation

Conversation

@Topherhindman
Copy link
Copy Markdown
Contributor

Summary

This PR fixes the lifecycle for FrameProcessor-based input noise cancellation when AudioInputOptions.noise_cancellation is provided as a NoiseCancellationSelector.

Before this change, if a selector returned a FrameProcessor (for example ai_coustics), the processor was passed to rtc.AudioStream.from_track() but was not stored as the active input processor. As a result, it never received _on_stream_info_updated() or _on_credentials_updated(), which caused processors that depend on that configuration to run uninitialized.

While fixing that, this PR also addresses two related lifecycle gaps:

  • closed processors could remain referenced after teardown and still receive later lifecycle calls
  • active processors were not deterministically closed on final input shutdown

Root cause

_ParticipantAudioInputStream.__init__() only initialized self._processor when noise_cancellation was passed directly as a FrameProcessor.

When noise_cancellation was a selector, the resolved value was only known later in _create_stream(), but that resolved processor was not stored on the input stream. This broke the path in _on_track_available() that injects stream info and credentials into the active processor.

Changes

  • store selector-returned FrameProcessors as the active processor in _create_stream()
  • clear stale processor references in _close_stream()
  • clear stale processor state when a selector returns a non-FrameProcessor value
  • close the active processor during aclose() for deterministic shutdown

When noise_cancellation is a NoiseCancellationSelector (callable)
that returns a FrameProcessor, the framework never called
_on_stream_info_updated or _on_credentials_updated on it. This
caused plugins like ai_coustics to log "Missing configuration"
on every frame and return audio unprocessed.

The root cause: _create_stream() resolved the selector but only
passed the result to AudioStream.from_track() without storing it
as self._processor. The lifecycle calls in _on_track_available()
check self._processor, which was None for the selector path.

Store the selector-returned FrameProcessor as self._processor in
_create_stream() so the existing lifecycle code can find it.
After _close_stream() closes a FrameProcessor, self._processor
was left pointing at the closed object. This caused stale
lifecycle calls in two scenarios:

1. A selector returns a FrameProcessor for one track but
   NoiseCancellationOptions for the next — the old closed
   processor still received _on_stream_info_updated and
   _on_credentials_updated calls.

2. A track is unpublished with no replacement (e.g., user
   switches microphones, client reconnects) — subsequent
   _on_token_refreshed calls hit the closed processor.

Clear self._processor in _close_stream() after closing it, and
in _create_stream() when a selector returns a non-FrameProcessor.
_ParticipantInputStream.aclose() closed the audio stream and
unregistered room callbacks but never called _close() on an
active FrameProcessor. This left processor cleanup dependent
on garbage collection rather than deterministic teardown.

This is a pre-existing gap that affects both direct and
selector-backed FrameProcessors (e.g., ai_coustics, Krisp).
@chenghao-mou chenghao-mou requested a review from a team April 13, 2026 16:24
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant