Permalink
Browse files

Update release processes, add Microphone.list_working_microphones() t…

…o test every microphone to ensure they can actually record audio
  • Loading branch information...
Uberi committed Dec 6, 2017
1 parent b24d057 commit 376153bab1fb79ff705b73f328793de713ca9ccb
Showing with 70 additions and 14 deletions.
  1. +2 −0 .github/ISSUE_TEMPLATE.md
  2. +1 −1 README.rst
  3. +4 −4 make-release.sh
  4. +18 −1 reference/library-reference.rst
  5. +45 −8 speech_recognition/__init__.py
@@ -36,4 +36,6 @@ My **PyAudio library version** is <INSERT VERSION HERE> / I don't have PyAudio i
My **microphones** are: (You can check this by running `python -c "import speech_recognition as sr;print(sr.Microphone.list_microphone_names())"`.)
My **working microphones** are: (You can check this by running `python -c "import speech_recognition as sr;print(sr.Microphone.list_working_microphones())"`.)
I **installed PocketSphinx from** <INSERT SOURCE HERE>. (For example, from the Debian repositories, from Homebrew, or from the source code.)
View
@@ -278,7 +278,7 @@ To install/reinstall the library locally, run ``python setup.py install`` in the
Before a release, the version number is bumped in ``README.rst`` and ``speech_recognition/__init__.py``. Version tags are then created using ``git config gpg.program gpg2 && git config user.signingkey DB45F6C431DE7C2DCD99FF7904882258A4063489 && git tag -s VERSION_GOES_HERE -m "Version VERSION_GOES_HERE"``.
Releases are done by running ``make-release.sh`` to build the Python source packages, sign them, and upload them to PyPI.
Releases are done by running ``make-release.sh VERSION_GOES_HERE`` to build the Python source packages, sign them, and upload them to PyPI.
Testing
~~~~~~~
View
@@ -5,8 +5,8 @@ set -e # give an error if any command finishes with a non-zero exit code
set -u # give an error if we reference unset variables
set -o pipefail # for a pipeline, if any of the commands fail with a non-zero exit code, fail the entire pipeline with that exit code
echo "if the following doesn't work, make sure you have your account set up properly with 'python3 setup.py register'"
echo "Making release for SpeechRecognition-$1"
# make sure we use GnuPG 2 rather than GnuPG 1
sudo ln --force "$(which gpg2)" dist/gpg
PATH=./dist:$PATH python3 setup.py bdist_wheel upload --sign
python setup.py bdist_wheel
gpg2 --detach-sign -a dist/SpeechRecognition-$1-*.whl
twine upload dist/SpeechRecognition-$1-*.whl dist/SpeechRecognition-$1-*.whl.asc
@@ -31,7 +31,7 @@ Instances of this class are context managers, and are designed to be used with `
Returns a list of the names of all available microphones. For microphones where the name can't be retrieved, the list entry contains ``None`` instead.
The index of each microphone's name is the same as its device index when creating a ``Microphone`` instance - indices in this list can be used as values of ``device_index``.
The index of each microphone's name in the returned list is the same as its device index when creating a ``Microphone`` instance - if you want to use the microphone at index 3 in the returned list, use ``Microphone(device_index=3)``.
To create a ``Microphone`` instance by name:
@@ -42,6 +42,23 @@ To create a ``Microphone`` instance by name:
if microphone_name == "HDA Intel HDMI: 0 (hw:0,3)":
m = Microphone(device_index=i)
``Microphone.list_working_microphones() -> Dict[int, str]``
-----------------------------------------------------------
Returns a dictionary mapping device indices to microphone names, for microphones that are currently hearing sounds. When using this function, ensure that your microphone is unmuted and make some noise at it to ensure it will be detected as working.
Each key in the returned dictionary can be passed to the ``Microphone`` constructor to use that microphone. For example, if the return value is ``{3: "HDA Intel PCH: ALC3232 Analog (hw:1,0)"}``, you can do ``Microphone(device_index=3)`` to use that microphone.
To create a ``Microphone`` instance for the first working microphone:
.. code:: python
for device_index in Microphone.list_working_microphones():
m = Microphone(device_index=device_index)
break
else:
print("No working microphones found!")
``AudioFile(filename_or_fileobject: Union[str, io.IOBase]) -> AudioFile``
-------------------------------------------------------------------------
@@ -86,9 +86,8 @@ def __init__(self, device_index=None, sample_rate=None, chunk_size=1024):
device_info = audio.get_device_info_by_index(device_index) if device_index is not None else audio.get_default_input_device_info()
assert isinstance(device_info.get("defaultSampleRate"), (float, int)) and device_info["defaultSampleRate"] > 0, "Invalid device info returned from PyAudio: {}".format(device_info)
sample_rate = int(device_info["defaultSampleRate"])
except Exception:
finally:
audio.terminate()
raise
self.device_index = device_index
self.format = self.pyaudio_module.paInt16 # 16-bit int sampling
@@ -118,7 +117,7 @@ def list_microphone_names():
"""
Returns a list of the names of all available microphones. For microphones where the name can't be retrieved, the list entry contains ``None`` instead.
The index of each microphone's name is the same as its device index when creating a ``Microphone`` instance - indices in this list can be used as values of ``device_index``.
The index of each microphone's name in the returned list is the same as its device index when creating a ``Microphone`` instance - if you want to use the microphone at index 3 in the returned list, use ``Microphone(device_index=3)``.
"""
audio = Microphone.get_pyaudio().PyAudio()
try:
@@ -130,20 +129,58 @@ def list_microphone_names():
audio.terminate()
return result
@staticmethod
def list_working_microphones():
"""
Returns a dictionary mapping device indices to microphone names, for microphones that are currently hearing sounds. When using this function, ensure that your microphone is unmuted and make some noise at it to ensure it will be detected as working.
Each key in the returned dictionary can be passed to the ``Microphone`` constructor to use that microphone. For example, if the return value is ``{3: "HDA Intel PCH: ALC3232 Analog (hw:1,0)"}``, you can do ``Microphone(device_index=3)`` to use that microphone.
"""
pyaudio_module = Microphone.get_pyaudio()
audio = pyaudio_module.PyAudio()
try:
result = {}
for device_index in range(audio.get_device_count()):
device_info = audio.get_device_info_by_index(device_index)
device_name = device_info.get("name")
assert isinstance(device_info.get("defaultSampleRate"), (float, int)) and device_info["defaultSampleRate"] > 0, "Invalid device info returned from PyAudio: {}".format(device_info)
try:
# read audio
pyaudio_stream = audio.open(
input_device_index=device_index, channels=1, format=pyaudio_module.paInt16,
rate=int(device_info["defaultSampleRate"]), input=True
)
try:
buffer = pyaudio_stream.read(1024)
if not pyaudio_stream.is_stopped(): pyaudio_stream.stop_stream()
finally:
pyaudio_stream.close()
except Exception:
continue
# compute RMS of debiased audio
energy = -audioop.rms(buffer, 2)
energy_bytes = chr(energy & 0xFF) + chr((energy >> 8) & 0xFF) if bytes is str else bytes([energy & 0xFF, (energy >> 8) & 0xFF]) # Python 2 compatibility
debiased_energy = audioop.rms(audioop.add(buffer, energy_bytes * (len(buffer) // 2), 2), 2)
if debiased_energy > 30: # probably actually audio
result[device_index] = device_name
finally:
audio.terminate()
return result
def __enter__(self):
assert self.stream is None, "This audio source is already inside a context manager"
self.audio = self.pyaudio_module.PyAudio()
try:
self.stream = Microphone.MicrophoneStream(
self.audio.open(
input_device_index=self.device_index, channels=1,
format=self.format, rate=self.SAMPLE_RATE, frames_per_buffer=self.CHUNK,
input=True, # stream is an input stream
input_device_index=self.device_index, channels=1, format=self.format,
rate=self.SAMPLE_RATE, frames_per_buffer=self.CHUNK, input=True,
)
)
except Exception:
finally:
self.audio.terminate()
raise
return self
def __exit__(self, exc_type, exc_value, traceback):

0 comments on commit 376153b

Please sign in to comment.