Merge dev with main #27
54
src/robot_interface/utils/microphone.py
Normal file
54
src/robot_interface/utils/microphone.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def choose_mic_interactive(audio):
|
||||||
|
"""
|
||||||
|
Choose a microphone to use, interactively in the CLI.
|
||||||
|
|
||||||
|
:param audio: An instance of PyAudio to use.
|
||||||
|
:type audio: pyaudio.PyAudio
|
||||||
|
|
||||||
|
:return: A dictionary from PyAudio containing information about the microphone to use, or None
|
||||||
|
if there is no microphone.
|
||||||
|
:rtype: dict | None
|
||||||
|
"""
|
||||||
|
device_count = audio.get_device_count()
|
||||||
|
if device_count == 0: return None
|
||||||
|
|
||||||
|
print("Found {} audio devices:".format(device_count))
|
||||||
|
for i in range(device_count):
|
||||||
|
print("- {}: {}".format(i, audio.get_device_info_by_index(i)["name"]))
|
||||||
|
|
||||||
|
microphone_index = None
|
||||||
|
while microphone_index is None:
|
||||||
|
chosen = raw_input("Which device would you like to use?\n> ")
|
||||||
|
try:
|
||||||
|
chosen = int(chosen)
|
||||||
|
if chosen < 0 or chosen >= device_count: raise ValueError()
|
||||||
|
microphone_index = chosen
|
||||||
|
except ValueError:
|
||||||
|
print("Please enter a number between 0 and {}".format(device_count-1))
|
||||||
|
|
||||||
|
chosen_microphone = audio.get_device_info_by_index(microphone_index)
|
||||||
|
logger.info("Chose microphone \"{}\"".format(chosen_microphone["name"]))
|
||||||
|
return chosen_microphone
|
||||||
|
|
||||||
|
|
||||||
|
def choose_mic_default(audio):
|
||||||
|
"""
|
||||||
|
Get the system's default microphone to use.
|
||||||
|
|
||||||
|
:param audio: An instance of PyAudio to use.
|
||||||
|
:type audio: pyaudio.PyAudio
|
||||||
|
|
||||||
|
:return: A dictionary from PyAudio containing information about the microphone to use, or None
|
||||||
|
if there is no microphone.
|
||||||
|
:rtype: dict | None
|
||||||
|
"""
|
||||||
|
device_count = audio.get_device_count()
|
||||||
|
if device_count == 0: return None
|
||||||
|
|
||||||
|
default_device = audio.get_default_input_device_info()
|
||||||
|
return default_device
|
||||||
107
test/unit/test_microphone_utils.py
Normal file
107
test/unit/test_microphone_utils.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import functools
|
||||||
|
import random
|
||||||
|
from StringIO import StringIO
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pyaudio
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from robot_interface.utils.microphone import choose_mic_default, choose_mic_interactive
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pyaudio_instance():
|
||||||
|
audio = pyaudio.PyAudio()
|
||||||
|
yield audio
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_mic_default(pyaudio_instance):
|
||||||
|
"""
|
||||||
|
The result must contain at least "index", as this is used to identify the microphone.
|
||||||
|
The "name" is used for logging, so it should also exist.
|
||||||
|
It must have one or more channels.
|
||||||
|
Lastly it must be capable of sending at least 16000 samples per second.
|
||||||
|
"""
|
||||||
|
result = choose_mic_default(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], long)
|
||||||
|
|
||||||
|
assert "name" in result
|
||||||
|
assert isinstance(result["name"], (str, unicode))
|
||||||
|
|
||||||
|
assert "maxInputChannels" in result
|
||||||
|
assert isinstance(result["maxInputChannels"], long)
|
||||||
|
assert result["maxInputChannels"] > 0
|
||||||
|
|
||||||
|
assert "defaultSampleRate" in result
|
||||||
|
assert isinstance(result["defaultSampleRate"], float)
|
||||||
|
assert result["defaultSampleRate"] >= 16000
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_input_not_int(pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
First mock an input that's not an integer, then a valid integer. There should be no errors.
|
||||||
|
"""
|
||||||
|
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["not an integer", "0"])
|
||||||
|
fake_out = StringIO()
|
||||||
|
mocker.patch.object(sys, "stdout", fake_out)
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
assert result["index"] == 0
|
||||||
|
|
||||||
|
assert mock_input.called
|
||||||
|
|
||||||
|
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_negative_index(pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
Make sure that the interactive method does not allow negative integers as input.
|
||||||
|
"""
|
||||||
|
mock_input = mocker.patch("__builtin__.raw_input", side_effect=["-1", "0"])
|
||||||
|
fake_out = StringIO()
|
||||||
|
mocker.patch.object(sys, "stdout", fake_out)
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
assert result["index"] == 0
|
||||||
|
|
||||||
|
assert mock_input.called
|
||||||
|
|
||||||
|
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_index_too_high(pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
Make sure that the interactive method does not allow indices higher than the highest mic index.
|
||||||
|
"""
|
||||||
|
real_count = pyaudio_instance.get_device_count()
|
||||||
|
mock_input = mocker.patch("__builtin__.raw_input", side_effect=[str(real_count), "0"])
|
||||||
|
fake_out = StringIO()
|
||||||
|
mocker.patch.object(sys, "stdout", fake_out)
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
assert result["index"] == 0
|
||||||
|
|
||||||
|
assert mock_input.called
|
||||||
|
|
||||||
|
assert any(p.startswith("Please enter a number") for p in fake_out.getvalue().splitlines())
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_mic_interactive_random_index(pyaudio_instance, mocker):
|
||||||
|
"""
|
||||||
|
Get a random index from the list of available mics, make sure it's correct.
|
||||||
|
"""
|
||||||
|
real_count = pyaudio_instance.get_device_count()
|
||||||
|
random_index = random.randrange(real_count)
|
||||||
|
mocker.patch("__builtin__.raw_input", side_effect=[str(random_index)])
|
||||||
|
|
||||||
|
result = choose_mic_interactive(pyaudio_instance)
|
||||||
|
assert "index" in result
|
||||||
|
assert isinstance(result["index"], (int, long))
|
||||||
|
assert result["index"] == random_index
|
||||||
Reference in New Issue
Block a user