
    qiY                    (   d dl mZ d dlZd dlmZ d dlmZmZmZm	Z	 d dl
Zd dlZerd dlZddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZ 	 dZdZdZdZ G d d      ZddZ  G d d      Z!e G d d             Z" G d d      Z# G d d      Z$y)    )annotationsN)	dataclass)TYPE_CHECKINGAnyAsyncIteratorOptional   )AudioSource)
AudioFrame)AudioProcessingModule)
AudioMixer)AudioStream)Tracki  i  i  c                  0    e Zd ZdZddZddZd	dZd
dZy)_AudioStreamIteratora  Adapter to convert AudioStream (AsyncIterator[AudioFrameEvent]) to AsyncIterator[AudioFrame].

    This adapter wraps an AudioStream and extracts the frame from each AudioFrameEvent,
    making it compatible with AudioMixer which expects AsyncIterator[AudioFrame].
    c                    || _         y N)_audio_stream)selfaudio_streams     K/opt/pipecat/venv/lib/python3.12/site-packages/livekit/rtc/media_devices.py__init__z_AudioStreamIterator.__init__E   s
    )    c                    | S r    r   s    r   	__aiter__z_AudioStreamIterator.__aiter__H   s    r   c                j   K   | j                   j                          d {   }|j                  S 7 wr   )r   	__anext__frame)r   events     r   r   z_AudioStreamIterator.__anext__K   s,     ((2244{{ 5s   313c                T   K   | j                   j                          d {    y 7 wr   )r   acloser   s    r   r#   z_AudioStreamIterator.acloseO   s       '')))s   (&(N)r   r   returnNone)r$   zAsyncIterator[AudioFrame])r$   r   r$   r%   )__name__
__module____qualname____doc__r   r   r   r#   r   r   r   r   r   >   s    **r   r   c                2    | xs t        j                         S r   )asyncioget_event_looploops    r   _ensure_loopr0   S   s    +7))++r   c                  (    e Zd ZdZddZddZddZy)	_APMDelayEstimatora'  Thread-safe store for last known output (render) delay in seconds.

    The sounddevice callbacks are invoked on PortAudio's threads. This helper allows
    sharing the latest output delay measurement with the input callback so we can set
    APM's combined stream delay (render + capture).
    c                D    t        j                         | _        d| _        y )N        )	threadingLock_lock_output_delay_secr   s    r   r   z_APMDelayEstimator.__init___   s    ^^%
(+r   c                f    | j                   5  t        |      | _        d d d        y # 1 sw Y   y xY wr   )r7   floatr8   )r   	delay_secs     r   set_output_delayz#_APMDelayEstimator.set_output_delayc   s+    ZZ 	6%*9%5D"	6 	6 	6s   '0c                ^    | j                   5  | j                  cd d d        S # 1 sw Y   y xY wr   )r7   r8   r   s    r   get_output_delayz#_APMDelayEstimator.get_output_delayg   s'    ZZ 	*))	* 	* 	*s   #,Nr&   )r;   r:   r$   r%   )r$   r:   )r'   r(   r)   r*   r   r<   r>   r   r   r   r2   r2   W   s    ,6*r   r2   c                  L    e Zd ZU dZded<   ded<   ded<   ded	<   d
ed<   ddZy)InputCapturea  Holds resources for an active audio input capture.

    Attributes:
        source: `rtc.AudioSource` that receives captured frames. This can be
            published as a `LocalAudioTrack`.
        input_stream: Underlying `sounddevice.InputStream`.
        task: Async task that drains a queue and calls `source.capture_frame`.
        apm: Optional `rtc.AudioProcessingModule` used to process 10 ms frames
            (AEC, NS, HPF, AGC). When performing echo cancellation, pass this
            instance to `open_output_player` so reverse frames are provided.
        delay_estimator: Internal helper used to combine capture and render delays.
    r
   sourcez'sd.InputStream'input_streamzasyncio.TasktaskOptional[AudioProcessingModule]apmOptional[_APMDelayEstimator]delay_estimatorc                t  K   | j                   rI| j                   j                         s/| j                   j                          	 | j                    d{    	 | j
                  j                          | j
                  j                          y7 :# t        j                  $ r Y Mw xY w# t        $ r Y yw xY ww)z,Stop capture and close underlying resources.N)	rC   donecancelr,   CancelledErrorrB   stopclose	Exceptionr   s    r   r#   zInputCapture.aclose   s     99TYY^^-IIii	""$##%  )) 
  		sZ   AB8B BB 4B) B8B B&#B8%B&&B8)	B52B84B55B8Nr&   )r'   r(   r)   r*   __annotations__r#   r   r   r   r@   r@   l   s,     ""
	((11r   r@   c                  b    e Zd ZdZeeedddd	 	 	 	 	 	 	 	 	 	 	 	 	 d	dZd
dZd
dZ	ddZ
ddZy)OutputPlayera  Simple audio output helper using `sounddevice.OutputStream`.

    When `apm_for_reverse` is provided, this player will feed the same PCM it
    renders (in 10 ms frames) into the APM reverse path so that echo
    cancellation can correlate mic input with speaker output.

    The OutputPlayer includes an internal `AudioMixer` for convenient multi-track
    playback. Use `add_track()` and `remove_track()` to dynamically manage tracks,
    then call `start()` to begin playback.
    Nsample_ratenum_channels	blocksizeapm_for_reverseoutput_devicerG   c               "    dd l }| _        | _        | _        | _        t                _        t        j                          _	        d  _
        d _        | _        d  _        i  _        d fd} |j                  |d||||       _        y )Nr   Fc                   |dz  }t        j                        }||k\  rLj                  d | }t        j                  |t        j                  |      | d d df<   j                  d |= nr|dkD  r\t        j                  j                  d | t        j                  |dz        | d |dz  df<   d| |dz  d df<   j                  d |= n| j                  d       	 t        |j                  |j                  z
        }j                  j                  j                  |       j                  |t        z  }t        |      D ]j  }	|	t        z  }
|
t        z   }||kD  r y | |
|df   }t        |j!                         j"                  dt              }	 j                  j%                  |       l y y # t        $ r Y w xY w# t        $ r Y w xY w)N   )dtypecountr   r	   )len_buffernp
frombufferint16fillr:   outputBufferDacTimecurrentTime_delay_estimatorr<   rN   _apmFRAME_SAMPLESranger   tobytes_sample_rateprocess_reverse_stream)outdataframe_count	time_infostatusbytes_needed	availablechunkoutput_delay_sec
num_chunksistartendrender_chunkrender_framer   s                 r   	_callbackz(OutputPlayer.__init__.<locals>._callback   s   &?L DLL)IL(]l3 "e288; W1LL,/Q/1}}LL),BHHIQRN0()q.(!+, 01	Q(!+,LL),Q#()F)FI^I^)^#_ ((4))::;KL yy$(M9
z* A-E-/C[(#*59a<#8L#-$,,.0A0A1m$L		88F %  " % s%    A	F8 G8	GG	GGra   callbackr[   channelsdevice
sampleraterU   )
rl   
np.ndarrayrm   intrn   r   ro   r   r$   r%   )sounddevicerj   _num_channels
_blocksizerf   	bytearrayr^   r,   r6   _buffer_lock
_play_task_runningre   _mixer_track_streamsOutputStream_stream)	r   rS   rT   rU   rV   rW   rG   sdrz   s	   `        r   r   zOutputPlayer.__init__   s     	!')##	 {#LLN26 / -1  	+	Z 'r! "
r   c                  K   |j                   | j                  v rt        d|j                    d      | j                  &t	        | j
                  | j                        | _        t        || j
                  | j                        }t        |      }||f| j                  |j                   <   | j                  j                  |       yw)a  Add an audio track to the internal mixer for playback.

        This creates an `AudioStream` from the track and adds it to the internal
        mixer. The mixer is created lazily on first track addition. Call `start()`
        to begin playback of all added tracks.

        Args:
            track: The audio track to add (typically from a remote participant).

        Raises:
            ValueError: If the track is not an audio track or has already been added.
        zTrack z already added to playerNrS   rT   )
sidr   
ValueErrorr   r   rj   r   r   r   
add_stream)r   trackstreamstream_iterators       r   	add_trackzOutputPlayer.add_track   s      99+++veii[0HIJJ ;;$1B1BQUQcQcdDK U0A0APTPbPbc.v6*0/)BEII&/s   CC
c                *  K   | j                   j                  |j                  d      }|y|\  }}| j                  	 | j                  j	                  |       	 |j                          d{    y# t
        $ r Y %w xY w7 # t
        $ r Y yw xY ww)zRemove an audio track from the internal mixer.

        This removes the track's stream from the mixer and closes it.

        Args:
            track: The audio track to remove.
        N)r   popr   r   remove_streamrN   r#   )r   r   entryr   r   s        r   remove_trackzOutputPlayer.remove_track
  s      ##''		48="';;"))/:	--/!!	   " 		sX   ;BA3 B -B.B 2B3	A?<B>A??BB 	BBBBc                   K    j                   % j                   j                         st        d       j                  &t	         j
                   j                         _         fd}t        j                   |              _         yw)a~  Start playback of all tracks in the internal mixer.

        This begins a background task that consumes frames from the internal mixer
        and sends them to the output device. Tracks can be added or removed
        dynamically using `add_track()` and `remove_track()`.

        Raises:
            RuntimeError: If playback is already started or no mixer is available.
        NzPlayback already startedr   c                 >  K   d_         j                  j                          	 j                  2 3 d{   } j                   s n4j                  j                  | j                  j                                Kd_         	 j                  j                          j                  j                          y7 6 A# t        $ r Y yw xY w# d_         	 j                  j                          j                  j                          w # t        $ r Y w w xY wxY ww)z;Internal playback loop that consumes frames from the mixer.TNF)r   r   rv   r   r^   extenddatari   rL   rM   rN   )r    r   s    r   _playback_loopz*OutputPlayer.start.<locals>._playback_loop2  s      DMLL #';; > >%==LL''

(:(:(<= %LL%%'LL&&(>; ! 	 !&LL%%'LL&&(  s   "DC B<B:B<AC =D4B> 9D:B<<C >	C
D	C

DD4D
D	DDDDD)	r   rI   RuntimeErrorr   r   rj   r   r,   create_task)r   r   s   ` r   rv   zOutputPlayer.start"  so      ??&t/C/C/E9::;;$1B1BQUQcQcdDK	$ "--n.>?s   B	Bc                  K   d| _         | j                  I| j                  j                         s/| j                  j                          	 | j                   d{    t        | j                  j                               D ]  \  }}	 |j                          d{      | j                  j                          | j                  *	 | j                  j                          d{    d| _        	 | j                  j                          | j                  j                          y7 # t        j
                  $ r Y w xY w7 # t        $ r Y w xY w7 m# t        $ r Y vw xY w# t        $ r Y yw xY ww)zyStop playback and close the output stream.

        This also cleans up all added tracks and the internal mixer.
        FN)r   r   rI   rJ   r,   rK   listr   valuesr#   rN   clearr   r   rL   rM   )r   r   _s      r   r#   zOutputPlayer.acloseF  sO    
  ??&t/C/C/EOO""$oo%%
 d1188:; 	IFAmmo%%	
 	!!# ;;"kk((*** DK	LLLL / &))  &  +   		s   AE5D+ D)D+ )E5	EEE!(E5
E 'E(E ,E544E& (E5)D+ +E>E5 EE5E	EE5EE5E 	E# E5"E##E5&	E2/E51E22E5)rS   r   rT   r   rU   r   rV   rD   rW   Optional[int]rG   rF   r$   r%   )r   r   r$   r%   r&   )r'   r(   r)   r*   DEFAULT_SAMPLE_RATEDEFAULT_CHANNELS	BLOCKSIZEr   r   r   rv   r#   r   r   r   rQ   rQ      s    	 /,";?'+8<P
 P
 	P

 P
 9P
 %P
 6P
 
P
d080"@H$r   rQ   c                      e Zd ZdZdeeeed	 	 	 	 	 	 	 	 	 	 	 ddZddZddZ	ddZ
ddZd	d	d	d	dd
dd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 ddZdd	 	 	 ddZy)MediaDevicesa  High-level interface to native audio devices.

    - Device enumeration helpers.
    - Audio input capture into `rtc.AudioSource` with optional APM processing.
    - Audio output player that can feed APM reverse stream for AEC.

    Design notes:
    - APM operates on 10 ms frames; this module slices input/output audio into
      `FRAME_SAMPLES` for processing calls.
    - For AEC to be effective, render audio that could leak back into the mic
      should be played through `OutputPlayer` with the same `apm` instance.
    - Timing alignment: this helper does not attempt to set device latency on
      APM; for most setups the default behavior is acceptable.
    N)r/   input_sample_rateoutput_sample_raterT   rU   c               x    t        |      | _        || _        || _        || _        || _        d | _        d | _        y r   )r0   _loop_in_sr_out_sr	_channelsr   re   rf   )r   r/   r   r   rT   rU   s         r   r   zMediaDevices.__init__}  s;     "$'
')%#>B59	r   c                    ddl } |j                         }g }t        |      D ]0  \  }}|j                  dd      dkD  s|j	                  d|i|       2 |S )zList available input devices.

        Returns a list of dictionaries with the `sounddevice` metadata and an
        added `index` key corresponding to the device index.
        r   Nmax_input_channelsindexr   query_devices	enumerategetappendr   r   devicesresultidxdevs         r   list_input_deviceszMediaDevices.list_input_devices  se     	!""""$')!'* 	5HCww+Q/!3w3s34	5 r   c                    ddl } |j                         }g }t        |      D ]0  \  }}|j                  dd      dkD  s|j	                  d|i|       2 |S )z+List available output devices with indices.r   Nmax_output_channelsr   r   r   s         r   list_output_devicesz MediaDevices.list_output_devices  sc     """"$')!'* 	5HCww,a014w3s34	5 r   c                p    ddl }|j                  j                  }t        |t        t
        f      r|d   S dS )z0Return the default input device index (or None).r   Nr   defaultr~   
isinstancer   tupler   r   r   s      r   default_input_devicez!MediaDevices.default_input_device  0     jj#C$7s1vATAr   c                p    ddl }|j                  j                  }t        |t        t
        f      r|d   S dS )z1Return the default output device index (or None).r   Nr	   r   r   s      r   default_output_devicez"MediaDevices.default_output_device  r   r   T2   )
enable_aecnoise_suppressionhigh_pass_filterauto_gain_controlinput_devicequeue_capacityinput_channel_indexc                   ddl } j                  t         j                   j                        d|s|s|s|rt        ||||      
t               nd _         _        t        j                  |      	 	 	 	 	 	 	 	 	 	 d fd}	 |j                  |	d j                  | j                   j                        }
|
j                          dfd	}t        j                   |             }t        |
|
      S )a  Open the default (or chosen) audio input device and start capture.

        Frames are sliced into 10 ms chunks. If any processing option is enabled,
        an `AudioProcessingModule` is created and applied to each frame before it
        is queued for `AudioSource.capture_frame`.

        To enable AEC end-to-end, call `open_output()` after opening the input
        device. The output player will automatically use the input's APM for
        reverse stream processing, enabling echo cancellation.

        Args:
            enable_aec: Enable acoustic echo cancellation.
            noise_suppression: Enable noise suppression.
            high_pass_filter: Enable high-pass filtering.
            auto_gain_control: Enable automatic gain control.
            input_device: Optional input device index (default system device if None).
            queue_capacity: Max queued frames between callback and async pump.
            input_channel_index: Optional zero-based device channel to capture. If provided,
                only that channel is opened (via sounddevice mapping) and used as mono input.

        Returns:
            InputCapture: Holder with `source`, `apm`, and `aclose()`.
        r   Nr.   )echo_cancellationr   r   r   )maxsizec                   m	 t        |j                  |j                  z
        }rt        j                               nd}t	        t        ||z   dz  d            }	 j                  |       |t        z  }t        |      D ]  }|t        z  }	|	t        z   }
|
|kD  r y | |	|
df   }t        |j                         t        j                  j                        }	 j                  |       	 j                         sj!                  j"                  |        y # t        $ r Y w xY w# t        $ r Y w xY w# t        $ r Y Yw xY w# t        $ r Y w xY w)Nr4   g     @@r   )r   samples_per_channelrS   rT   )r:   rd   inputBufferAdcTimer>   r   maxset_stream_delay_msrN   rg   rh   r   ri   r   r   process_streamfullcall_soon_threadsafe
put_nowait)indatarm   rn   ro   input_delay_secrs   total_delay_ms
num_framesru   rv   rw   rr   r    rE   rG   r/   qr   s                r   _input_callbackz0MediaDevices.open_input.<locals>._input_callback  su    &+I,A,AID`D`,`&aOETo>>@AZ] % &)o@P.PTZ-Z\_)`%aN//?
 %5J:& M)m+$uSy!|,"(5 $!%	 ?**5166811!,,F+ %   $ %  ! sT   AD2  D# "E4,E#	D/,D2 .D//D2 2	D>=D>	EE	EEra   r{   c                    K   	 	 j                          d {   } 	 j                  |        d {    47 # t        j                  $ r Y y w xY w7  # t        $ r Y )w xY wwr   )r   r,   rK   capture_framerN   )r    r   rA   s    r   _pumpz&MediaDevices.open_input.<locals>._pump)  sk     "#%%'ME ..u555 )--  6  s^   A%; 9; A AA A%; AA%AA%A 	A"A%!A""A%)rA   rB   rC   rE   rG   )
r   r   rm   r   rn   r   ro   r   r$   r%   r&   )r   r   r
   r   r   r   r2   re   rf   r,   QueueInputStreamr   rv   r   r@   )r   r   r   r   r   r   r   r   r   r   rB   r   rC   rE   rG   r/   r   rA   s   `            @@@@@r   
open_inputzMediaDevices.open_input  s*   D 	!zzT[[$..tD/3*.>BS'","3!1"3	C %(O  	 !0	 (/}}^'L+	+	-0+	=@+	JM+	+	 +	^ &r~~$^^{{oo
 		 ""57+%+
 	
r   )rW   c                   t        | j                  | j                  | j                  | j                  || j
                        S )aP  Create an `OutputPlayer` for rendering and (optionally) AEC reverse.

        If an input device was opened with AEC enabled, the output player will
        automatically feed the APM's reverse stream for echo cancellation.

        Args:
            output_device: Optional output device index (default system device if None).
        rR   )rQ   r   r   r   rf   re   )r   rW   s     r   open_outputzMediaDevices.open_output?  s9     oo II' 11
 	
r   )r/   #Optional[asyncio.AbstractEventLoop]r   r   r   r   rT   r   rU   r   r$   r%   )r$   zlist[dict[str, Any]])r$   r   )r   boolr   r   r   r   r   r   r   r   r   r   r   r   r$   r@   )rW   r   r$   rQ   )r'   r(   r)   r*   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   m  s   $ 59!4"5,": 2: 	:
  : : : 
:$	BB  "&!%"&&* -1E
 E
  	E

 E
  E
 $E
 E
 +E
 
E
T (,
 %
 
	
r   r   )r/   r   r$   zasyncio.AbstractEventLoop)%
__future__r   r,   dataclassesr   typingr   r   r   r   numpyr_   r5   r   r    r
   audio_framer   rE   r   audio_mixerr   r   r   r   r   r   r   rg   r   r   r0   r2   r@   rQ   r   r   r   r   <module>r      s    #  ! > >    # & # % ,   	* **,* **      FZ Zzf
 f
r   