@@ -43,6 +43,8 @@ class Config:
4343 preconnect_debug_wav : Path | None
4444 agent_metadata : str
4545 agent_wait_timeout : float
46+ audio_input_device : int | None
47+ audio_output_device : int | None
4648
4749 @staticmethod
4850 def from_env () -> "Config" :
@@ -78,8 +80,44 @@ def from_env() -> "Config":
7880 ),
7981 agent_metadata = os .getenv ("LIVEKIT_AGENT_METADATA" , "" ),
8082 agent_wait_timeout = float (os .getenv ("LIVEKIT_AGENT_WAIT_TIMEOUT" , "30.0" )),
83+ audio_input_device = Config ._optional_int_from_env (
84+ "LIVEKIT_AUDIO_INPUT_DEVICE"
85+ ),
86+ audio_output_device = Config ._optional_int_from_env (
87+ "LIVEKIT_AUDIO_OUTPUT_DEVICE"
88+ ),
8189 )
8290
91+ @staticmethod
92+ def _optional_int_from_env (name : str ) -> int | None :
93+ raw = os .getenv (name )
94+ if raw is None or not raw .strip ():
95+ return None
96+
97+ try :
98+ return int (raw )
99+ except ValueError :
100+ raise RuntimeError (f"{ name } must be an integer device index" ) from None
101+
102+
103+ def _describe_audio_device (device_index : int | None , kind : str ) -> str :
104+ import sounddevice as sd # type: ignore[import-not-found, import-untyped]
105+
106+ selected_index = device_index
107+ if selected_index is None :
108+ default_device = sd .default .device
109+ if isinstance (default_device , (list , tuple )):
110+ selected_index = default_device [0 if kind == "input" else 1 ]
111+
112+ if selected_index is None :
113+ return "system default"
114+
115+ try :
116+ device = sd .query_devices (selected_index )
117+ return f"{ selected_index } ({ device ['name' ]} )"
118+ except Exception :
119+ return str (selected_index )
120+
83121
84122@dataclass (frozen = True )
85123class MicChunk :
@@ -573,17 +611,25 @@ async def main() -> None:
573611 shutdown_event = asyncio .Event ()
574612
575613 model = WakeWordModel (models = [str (config .wakeword_model )])
576- room = rtc .Room ()
577- lkapi = api .LiveKitAPI (config .url , config .api_key , config .api_secret )
578614 audio_queue : asyncio .Queue [MicChunk ] = asyncio .Queue (maxsize = 100 )
579615 devices = rtc .MediaDevices (
580616 input_sample_rate = SAMPLE_RATE ,
581617 output_sample_rate = OUTPUT_SAMPLE_RATE ,
582618 num_channels = NUM_CHANNELS ,
583619 blocksize = OUTPUT_FRAME_SAMPLES ,
584620 )
585- mic = devices .open_input (enable_aec = True )
586- player = devices .open_output ()
621+ logger .info (
622+ "using audio devices input=%s output=%s" ,
623+ _describe_audio_device (config .audio_input_device , "input" ),
624+ _describe_audio_device (config .audio_output_device , "output" ),
625+ )
626+ mic = devices .open_input (
627+ enable_aec = True ,
628+ input_device = config .audio_input_device ,
629+ )
630+ player = devices .open_output (output_device = config .audio_output_device )
631+ room = rtc .Room ()
632+ lkapi = api .LiveKitAPI (config .url , config .api_key , config .api_secret )
587633 background_tasks : set [asyncio .Task [None ]] = set ()
588634
589635 def _create_background_task (coro ) -> None :
0 commit comments