@@ -30,17 +30,16 @@ def __init__(
3030 self .stream = None
3131 self ._output_stream = None
3232 self ._buffer : List [npt .NDArray [np .float32 ]] = []
33- self ._device_id = self .get_output_device_id ("respeaker" )
34- self ._samplerate = (
35- - 1
36- ) # will be set on first use to avoid issues if device is not present (CI)
33+ self ._output_device_id = self .get_output_device_id ("respeaker" )
34+ self ._input_device_id = self .get_input_device_id ("respeaker" )
3735
3836 def start_recording (self ) -> None :
3937 """Open the audio input stream, using ReSpeaker card if available."""
4038 self .stream = sd .InputStream (
4139 blocksize = self .frames_per_buffer ,
42- device = self ._device_id ,
40+ device = self ._input_device_id ,
4341 callback = self ._callback ,
42+ samplerate = self .SAMPLE_RATE ,
4443 )
4544 if self .stream is None :
4645 raise RuntimeError ("Failed to open SoundDevice audio stream." )
@@ -69,14 +68,6 @@ def get_audio_sample(self) -> Optional[bytes | npt.NDArray[np.float32]]:
6968 self .logger .debug ("No audio data available in buffer." )
7069 return None
7170
72- def get_audio_samplerate (self ) -> int :
73- """Return the samplerate of the audio device."""
74- if self ._samplerate == - 1 :
75- self ._samplerate = int (
76- sd .query_devices (self ._device_id )["default_samplerate" ]
77- )
78- return self ._samplerate
79-
8071 def stop_recording (self ) -> None :
8172 """Close the audio stream and release resources."""
8273 if self .stream is not None :
@@ -97,8 +88,8 @@ def push_audio_sample(self, data: bytes) -> None:
9788 def start_playing (self ) -> None :
9889 """Open the audio output stream."""
9990 self ._output_stream = sd .OutputStream (
100- samplerate = self .get_audio_samplerate () ,
101- device = self ._device_id ,
91+ samplerate = self .SAMPLE_RATE ,
92+ device = self ._output_device_id ,
10293 channels = 1 ,
10394 )
10495 if self ._output_stream is None :
@@ -121,9 +112,9 @@ def play_sound(self, sound_file: str, autoclean: bool = False) -> None:
121112
122113 data , samplerate_in = sf .read (file_path , dtype = "float32" )
123114
124- if samplerate_in != self .get_audio_samplerate () :
115+ if samplerate_in != self .SAMPLE_RATE :
125116 data = scipy .signal .resample (
126- data , int (len (data ) * (self .get_audio_samplerate () / samplerate_in ))
117+ data , int (len (data ) * (self .SAMPLE_RATE / samplerate_in ))
127118 )
128119 if data .ndim > 1 : # convert to mono
129120 data = np .mean (data , axis = 1 )
@@ -157,8 +148,8 @@ def callback(
157148 event = threading .Event ()
158149
159150 self ._output_stream = sd .OutputStream (
160- samplerate = self .get_audio_samplerate () ,
161- device = self ._device_id ,
151+ samplerate = self .SAMPLE_RATE ,
152+ device = self ._output_device_id ,
162153 channels = 1 ,
163154 callback = callback ,
164155 finished_callback = event .set , # release the device when done
@@ -198,10 +189,32 @@ def get_output_device_id(self, name_contains: str) -> int:
198189 devices = sd .query_devices ()
199190
200191 for idx , dev in enumerate (devices ):
201- if name_contains .lower () in dev ["name" ].lower ():
192+ if (
193+ name_contains .lower () in dev ["name" ].lower ()
194+ and dev ["max_output_channels" ] > 0
195+ ):
202196 return idx
203197 # Return default output device if not found
204198 self .logger .warning (
205199 f"No output device found containing '{ name_contains } ', using default."
206200 )
207201 return int (sd .default .device [1 ])
202+
203+ def get_input_device_id (self , name_contains : str ) -> int :
204+ """Return the input device id whose name contains the given string (case-insensitive).
205+
206+ If not found, return the default input device id.
207+ """
208+ devices = sd .query_devices ()
209+
210+ for idx , dev in enumerate (devices ):
211+ if (
212+ name_contains .lower () in dev ["name" ].lower ()
213+ and dev ["max_input_channels" ] > 0
214+ ):
215+ return idx
216+ # Return default input device if not found
217+ self .logger .warning (
218+ f"No input device found containing '{ name_contains } ', using default."
219+ )
220+ return int (sd .default .device [1 ])
0 commit comments