Skip to content
This repository was archived by the owner on May 6, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:

steps:
- checkout
- run: sudo apt-get install portaudio19-dev
- restore_cache:
keys:
- build-{{checksum "requirements.txt"}}
Expand All @@ -32,9 +33,7 @@ jobs:
ls -1 */__init__.py | sed -E 's|/.*||' | xargs mypy
- run: |
source ~/venv/bin/activate
python -m pytest
- run: |
source ~/venv/bin/activate
python -m pytest --cov=spokestack
python -m coveralls
- run: |
source ~/venv/bin/activate
Expand Down
2 changes: 2 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ pytest-cov
pytest-watch
pytest-mock
coveralls
# library
pyaudio
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile requirements.in
# pip-compile
#
appdirs==1.4.4 # via black, virtualenv
attrs==19.3.0 # via black, flake8-mypy, pytest
Expand Down Expand Up @@ -36,6 +36,7 @@ pluggy==0.13.1 # via pytest
pre-commit==2.6.0 # via -r requirements.in
py==1.9.0 # via pytest
py-cpuinfo==7.0.0 # via pytest-benchmark
pyaudio==0.2.11 # via -r requirements.in
pycodestyle==2.6.0 # via flake8
pyflakes==2.2.0 # via flake8
pyparsing==2.4.7 # via packaging
Expand Down
File renamed without changes.
75 changes: 75 additions & 0 deletions spokestack/mic/pyaudio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
This module uses pyaudio to receive input from a
device microphone
"""
import pyaudio # type: ignore


class PyAudioMicrophoneInput:
""" This class retrieves audio from an input device

:param sample_rate: desired sample rate for input
:type sample_rate: int
:param frame_width: desired frame width for input
:type frame_width: int
:param exception_on_overflow: produce exception for input overflow
:type exception_on_overflow: bool
"""

def __init__(
self, sample_rate: int, frame_width: int, exception_on_overflow: bool = True
) -> None:
self._sample_rate = sample_rate
self._frame_size = int(sample_rate / 1000 * frame_width)
self._exeception_on_overflow = exception_on_overflow
self._audio = pyaudio.PyAudio()
device = self._audio.get_default_input_device_info()
self._stream = self._audio.open(
input=True,
input_device_index=device["index"],
format=pyaudio.paInt16,
frames_per_buffer=self._frame_size,
channels=1,
rate=self._sample_rate,
start=False,
)

def read(self) -> bytes:
""" Reads a single frame of audio

:return: single frame of audio
:rtype: bytes
"""
return self._stream.read(
self._frame_size, exception_on_overflow=self._exeception_on_overflow
)

def start(self) -> None:
""" Starts the audio stream """
self._stream.start_stream()

def stop(self) -> None:
""" Stops the audio stream """
self._stream.stop_stream()

def close(self) -> None:
""" Closes the audio stream """
self._stream.close()

@property
def is_active(self) -> bool:
""" Stream active property

:return: 'True' if stream is active, 'False' otherwise
:rtype: bool
"""
return self._stream.is_active()

@property
def is_stopped(self) -> bool:
""" Stream stopped property

:return: 'True' if stream is stopped, 'False' otherwise
:rtype: bool
"""
return self._stream.is_stopped()
22 changes: 22 additions & 0 deletions tests/mic/test_audio_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
Mock tests for microphone input
"""
from unittest.mock import patch

from spokestack.mic.pyaudio import PyAudioMicrophoneInput


@patch("spokestack.mic.pyaudio.pyaudio")
def test_audio_input(mock_class):
mic = PyAudioMicrophoneInput(sample_rate=16000, frame_width=10)
mic._audio.open.assert_called()
# start audio stream
mic.start()
assert mic.is_active
# read a single frame
_ = mic.read()
mic._stream.read.assert_called()
# stop audio stream
mic.stop()
assert mic.is_stopped
mic.close()
5 changes: 0 additions & 5 deletions tests/test_dummy.py

This file was deleted.