@@ -120,9 +120,6 @@ async def aclose(self) -> None:
120120class OutputPlayer :
121121 """Simple audio output helper using `sounddevice.OutputStream`.
122122
123- When `apm_for_reverse` is provided, this player will feed the same PCM it
124- renders (in 10 ms frames) into the APM reverse path so that echo
125- cancellation can correlate mic input with speaker output.
126123 """
127124
128125 def __init__ (
@@ -262,6 +259,10 @@ def __init__(
262259 self ._channels = num_channels
263260 self ._blocksize = blocksize
264261 self ._delay_estimator : Optional [_APMDelayEstimator ] = None
262+ # Internal: last opened input's APM instance (if any). automatically associated with output player for AEC.
263+ self ._apm : Optional [AudioProcessingModule ] = None
264+ # Track a single output player
265+ self ._output : Optional [OutputPlayer ] = None
265266
266267 # Device enumeration
267268 def list_input_devices (self ) -> list [dict [str , Any ]]:
@@ -314,9 +315,9 @@ def open_input(
314315 an `AudioProcessingModule` is created and applied to each frame before it
315316 is queued for `AudioSource.capture_frame`.
316317
317- To enable AEC end-to-end, pass the returned `apm` to
318- `open_output(apm_for_reverse=...)` and route remote audio through
319- that player so reverse frames are provided to APM .
318+ To enable AEC end-to-end, open the output on the same `MediaDevices`
319+ instance after opening input with processing enabled. The APM will be
320+ automatically associated so reverse frames are provided for AEC .
320321
321322 Args:
322323 enable_aec: Enable acoustic echo cancellation.
@@ -341,11 +342,18 @@ def open_input(
341342 high_pass_filter = high_pass_filter ,
342343 auto_gain_control = auto_gain_control ,
343344 )
344- delay_estimator : Optional [_APMDelayEstimator ] = (
345- _APMDelayEstimator () if apm is not None else None
346- )
347- # Store the shared estimator on the device helper so the output player can reuse it
348- self ._delay_estimator = delay_estimator
345+ # Ensure we have a shared delay estimator when processing is enabled
346+ if self ._delay_estimator is None :
347+ self ._delay_estimator = _APMDelayEstimator ()
348+ # Store APM internally for automatic association with output
349+ self ._apm = apm
350+ # Update existing output player so order of creation doesn't matter
351+ if self ._output is not None :
352+ try :
353+ self ._output ._apm = self ._apm
354+ self ._output ._delay_estimator = self ._delay_estimator
355+ except Exception :
356+ pass
349357
350358 # Queue from callback to async task
351359 q : asyncio .Queue [AudioFrame ] = asyncio .Queue (maxsize = queue_capacity )
@@ -439,20 +447,30 @@ async def _pump() -> None:
439447 def open_output (
440448 self ,
441449 * ,
442- apm_for_reverse : Optional [AudioProcessingModule ] = None ,
443450 output_device : Optional [int ] = None ,
444451 ) -> OutputPlayer :
445- """Create an `OutputPlayer` for rendering and (optionally) AEC reverse .
452+ """Create an `OutputPlayer` for rendering.
446453
447454 Args:
448- apm_for_reverse: Pass the APM used by the audio input device to enable AEC.
449455 output_device: Optional output device index (default system device if None).
450456 """
451- return OutputPlayer (
457+ # If an output player already exists, warn and return it
458+ if self ._output is not None :
459+ logging .warning ("OutputPlayer already created on this MediaDevices; returning existing instance" )
460+ return self ._output
461+
462+ # Ensure we have a shared delay estimator so output can report render delay
463+ if self ._delay_estimator is None :
464+ self ._delay_estimator = _APMDelayEstimator ()
465+
466+ player = OutputPlayer (
452467 sample_rate = self ._out_sr ,
453468 num_channels = self ._channels ,
454469 blocksize = self ._blocksize ,
455- apm_for_reverse = apm_for_reverse ,
470+ apm_for_reverse = self . _apm ,
456471 output_device = output_device ,
457472 delay_estimator = self ._delay_estimator ,
458473 )
474+ # Track player for future APM/delay updates when input is opened later
475+ self ._output = player
476+ return player
0 commit comments