Skip to content

Commit

Permalink
Update release processes, add Microphone.list_working_microphones() t…
Browse files Browse the repository at this point in the history
…o test every microphone to ensure they can actually record audio
  • Loading branch information
Uberi committed Dec 6, 2017
1 parent b24d057 commit 376153b
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 14 deletions.
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~
Expand Down
8 changes: 4 additions & 4 deletions make-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
19 changes: 18 additions & 1 deletion reference/library-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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``
-------------------------------------------------------------------------

Expand Down
53 changes: 45 additions & 8 deletions speech_recognition/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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):
Expand Down

0 comments on commit 376153b

Please sign in to comment.