Skip to content

Commit abd9460

Browse files
committed
refactor(audio): fix device selection and eliminate duplication in sounddevice backend
The original get_input_device_id() and get_output_device_id() had two issues: 1. Both incorrectly returned sd.default.device[1] (default OUTPUT device) as fallback 2. Nearly identical code duplicated between the two methods This caused failures when: - get_input_device_id() returned an output-only device for recording - get_output_device_id() always returned the same device regardless of type Changes: - Extract shared logic into _find_device_id(name_contains, device_type) helper - Use dynamic channel_key: "max_input_channels" or "max_output_channels" - Proper fallback: search for first device with appropriate channel type - Raise clear RuntimeError if no suitable device exists - Fix typo: logger.warningt → logger.warning This ensures both methods always return valid devices of the correct type.
1 parent e5b1c4e commit abd9460

File tree

1 file changed

+37
-21
lines changed

1 file changed

+37
-21
lines changed

src/reachy_mini/media/audio_sounddevice.py

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -181,40 +181,56 @@ def _clean_up_thread() -> None:
181181
daemon=True,
182182
).start()
183183

184-
def get_output_device_id(self, name_contains: str) -> int:
185-
"""Return the output device id whose name contains the given string (case-insensitive).
184+
def _find_device_id(
185+
self, name_contains: str, device_type: str
186+
) -> int:
187+
"""Find device ID by name and type with fallback logic.
188+
189+
Args:
190+
name_contains: Substring to search for in device name (case-insensitive)
191+
device_type: Either "input" or "output"
186192
187-
If not found, return the default output device id.
193+
Returns:
194+
Device index
195+
196+
Raises:
197+
RuntimeError: If no device with appropriate channels found
188198
"""
189199
devices = sd.query_devices()
200+
channel_key = f"max_{device_type}_channels"
190201

202+
# Search for device by name
191203
for idx, dev in enumerate(devices):
192204
if (
193205
name_contains.lower() in dev["name"].lower()
194-
and dev["max_output_channels"] > 0
206+
and dev.get(channel_key, 0) > 0
195207
):
196208
return idx
197-
# Return default output device if not found
209+
210+
# Log warning if named device not found
198211
self.logger.warning(
199-
f"No output device found containing '{name_contains}', using default."
212+
f"No {device_type} device found containing '{name_contains}', using fallback."
213+
)
214+
215+
# Fallback: return first device with appropriate channels
216+
for idx, dev in enumerate(devices):
217+
if dev.get(channel_key, 0) > 0:
218+
return idx
219+
220+
raise RuntimeError(
221+
f"No {device_type} audio device with {device_type} channels found."
200222
)
201-
return int(sd.default.device[1])
223+
224+
def get_output_device_id(self, name_contains: str) -> int:
225+
"""Return the output device id whose name contains the given string (case-insensitive).
226+
227+
If not found, return the first available output device.
228+
"""
229+
return self._find_device_id(name_contains, "output")
202230

203231
def get_input_device_id(self, name_contains: str) -> int:
204232
"""Return the input device id whose name contains the given string (case-insensitive).
205233
206-
If not found, return the default input device id.
234+
If not found, return the first available input device.
207235
"""
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])
236+
return self._find_device_id(name_contains, "input")

0 commit comments

Comments
 (0)