Skip to content

Commit

Permalink
Merge branch 'nonblocking-callback-api' into passthrough
Browse files Browse the repository at this point in the history
  • Loading branch information
romanroibu committed Dec 10, 2019
2 parents 4f031fd + bbf841e commit 919a325
Show file tree
Hide file tree
Showing 12 changed files with 520 additions and 149 deletions.
1 change: 1 addition & 0 deletions examples/capture_nonblocking_pyaudio2pyav.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(self, out_path):
self._raw_buffer_file = None
self._timestamps_list = None

@property
def is_opened(self) -> bool:
return self._raw_buffer_file is None or self._timestamps_list is None

Expand Down
7 changes: 3 additions & 4 deletions examples/enumerate_devices.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
def main():
import pprint

from pupil_audio.utils.pyaudio import get_all_inputs, get_all_outputs
from pupil_audio.utils.pyaudio import get_default_input, get_default_output
from pupil_audio.utils.pyaudio import DeviceInfo

input_devices, output_devices = get_all_inputs(), get_all_outputs()
input_default, output_default = get_default_input(), get_default_output()
input_devices, output_devices = DeviceInfo.inputs_by_name(), DeviceInfo.outputs_by_name()
input_default, output_default = DeviceInfo.default_input(), DeviceInfo.default_output()

pp = pprint.PrettyPrinter(indent=4)

Expand Down
33 changes: 33 additions & 0 deletions examples/enumerate_devices_raw_pyaudio.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,47 @@ def main():

pp = pprint.PrettyPrinter(indent=4)

apis = get_apis()
devices = get_devices_from_all_apis()

print("-" * 80)

print("APIS:")
pp.pprint(apis)

print("-" * 80)

print("DEVICES:")
pp.pprint(devices)

print("-" * 80)


def get_apis():
import pyaudio

session = pyaudio.PyAudio()

result = {}

try:
host_api_count = session.get_host_api_count()

api_indices = range(host_api_count)

for host_api_index in api_indices:

host_info = session.get_host_api_info_by_index(host_api_index)
key = host_info["name"]

result[key] = host_info

return result
finally:
session.terminate()



def get_devices_from_all_apis():
import itertools
import pyaudio
Expand Down
69 changes: 69 additions & 0 deletions examples/monitor_devices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import time
import pprint

from pupil_audio.nonblocking import PyAudioDeviceMonitor
from pupil_audio.nonblocking import PyAudioBackgroundDeviceMonitor


class _LoggingMixin:
def log_prefix(self):
return type(self).__name__

def print_devices_by_name(self, devices_by_name):
print("-" * 80)
print(f"{self.log_prefix()}: {list(devices_by_name.keys())}")

def update(self):
super().update()
self.print_devices_by_name(self.devices_by_name)


class _LoggingDeviceMonitor(_LoggingMixin, PyAudioDeviceMonitor):
def log_prefix(self):
return "FOREGROUND"


class _LoggingBackgroundDeviceMonitor(_LoggingMixin, PyAudioBackgroundDeviceMonitor):
def log_prefix(self):
return "BACKGROUND"


def main(use_foreground=False, use_background=True, delay=0.3):
assert use_foreground or use_background, "At least one flag should be true"

if use_foreground:
fg_monitor = _LoggingDeviceMonitor()

if use_background:
bg_monitor = _LoggingBackgroundDeviceMonitor()
bg_monitor.start()

try:
while True:
if use_foreground:
fg_monitor.update()
time.sleep(delay)
except KeyboardInterrupt:
pass
finally:
if use_foreground:
fg_monitor.cleanup()
if use_background:
bg_monitor.cleanup()


if __name__ == "__main__":
import click

@click.command()
@click.option("--foreground", is_flag=True, help="TODO")
@click.option("--background", is_flag=True, help="TODO")
@click.option("--delay", default=0.3, help="TODO")
def cli(foreground, background, delay):
main(
use_foreground=foreground,
use_background=background,
delay=delay,
)

cli()
8 changes: 4 additions & 4 deletions examples/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
def get_user_selected_input_name() -> str:
from pupil_audio.utils.pyaudio import get_all_inputs, get_default_input
from pupil_audio.utils.pyaudio import DeviceInfo

default_device = get_default_input()
default_device = DeviceInfo.default_input()
if default_device is None:
print("No default input device availabe!")
exit(-1)

input_names = sorted(device.name for device in get_all_inputs().values())
default_name = get_default_input().name
input_names = sorted(device.name for device in DeviceInfo.inputs_by_name().values())
default_name = DeviceInfo.default_input().name

print("-" * 80)
print("PLEASE SELECT INPUT DEVICE:")
Expand Down
4 changes: 4 additions & 0 deletions pupil_audio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .utils.pyaudio import PyAudioManager, HostApiInfo, DeviceInfo, TimeInfo
from .nonblocking.pyaudio import PyAudioDeviceSource, PyAudioDeviceMonitor, PyAudioBackgroundDeviceMonitor
from .nonblocking.pyaudio2pyav import PyAudio2PyAVCapture, PyAudio2PyAVTranscoder
from .nonblocking.pyav import PyAVFileSink
26 changes: 13 additions & 13 deletions pupil_audio/blocking/pyaudio.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import numpy as np
import pyaudio

import pupil_audio.utils.pyaudio as pyaudio_utils
from pupil_audio.utils.pyaudio import PyAudioManager, HostApiInfo, DeviceInfo

from .base import Codec
from .base import InputStreamWithCodec
Expand Down Expand Up @@ -81,16 +81,16 @@ def _format_from_dtype(dtype):
class PyAudioDeviceInputStream(InputStreamWithCodec[str]):

def __init__(self, name, channels=None, frame_rate=None, format=None, dtype=None):
device_info = pyaudio_utils.get_input_by_name(name)
frame_rate = frame_rate or device_info.get("defaultSampleRate", None)
channels = channels or device_info.get("maxInputChannels", None)
device_info = DeviceInfo.named_input(name)
frame_rate = frame_rate or device_info.default_sample_rate
channels = channels or device_info.max_input_channels

assert frame_rate is not None
assert channels is not None
assert frame_rate
assert channels

self.name = name
self.frame_rate = int(frame_rate)
self.session = pyaudio_utils.create_session()
self.session = PyAudioManager.acquire_shared_instance()
self._codec = PyAudioCodec(
frame_rate=frame_rate,
channels=channels,
Expand Down Expand Up @@ -124,7 +124,7 @@ def close(self):
self.stream = None
logger.debug("PyAudioDeviceInputStream closed")
if self.session is not None:
pyaudio_utils.destroy_session(self.session)
PyAudioManager.release_shared_instance(self.session)
self.stream = None

@property
Expand All @@ -137,16 +137,16 @@ def channels(self) -> int:

@property
def sample_width(self):
with pyaudio_utils.session_context() as session:
with PyAudioManager.shared_instance() as session:
return session.get_sample_size(self.format)

@staticmethod
def enumerate_devices():
return sorted(pyaudio_utils.get_all_inputs().values(), key=lambda x: x["index"])
return sorted(DeviceInfo.inputs_by_name().values(), key=lambda x: x.index)

@staticmethod
def default_device():
return pyaudio_utils.get_default_input()
return DeviceInfo.default_input()


class PyAudioDeviceOutputStream(OutputStreamWithCodec[str]):
Expand All @@ -156,8 +156,8 @@ def __init__(self):

@staticmethod
def enumerate_devices():
return sorted(pyaudio_utils.get_all_outputs().values(), key=lambda x: x["index"])
return sorted(DeviceInfo.outputs_by_name().values(), key=lambda x: x.index)

@staticmethod
def default_device():
return pyaudio_utils.get_default_output()
return DeviceInfo.default_output()
2 changes: 1 addition & 1 deletion pupil_audio/nonblocking/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .pyav import PyAVFileSink
from .pyaudio import PyAudioDeviceSource
from .pyaudio import PyAudioDeviceSource, PyAudioDeviceMonitor, PyAudioBackgroundDeviceMonitor
from .pyaudio2pyav import PyAudio2PyAVCapture, PyAudio2PyAVTranscoder
101 changes: 97 additions & 4 deletions pupil_audio/nonblocking/pyaudio.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,101 @@
import time
import queue
import logging
import threading
import typing as T

import pyaudio

import pupil_audio.utils.pyaudio as pyaudio_utils
from pupil_audio.utils.pyaudio import PyAudioManager, HostApiInfo, DeviceInfo, TimeInfo


logger = logging.getLogger(__name__)


class PyAudioDeviceMonitor:

# Public

def __init__(self):
self.__devices_by_name = {}

@property
def devices_by_name(self) -> T.Mapping[str, DeviceInfo]:
return self.__devices_by_name

@devices_by_name.setter
def devices_by_name(self, value: T.Mapping[str, DeviceInfo]):
self.__devices_by_name = value

def update(self):
self.devices_by_name = DeviceInfo.devices_by_name()

def cleanup(self):
pass


class PyAudioBackgroundDeviceMonitor(PyAudioDeviceMonitor):

# Public

def __init__(self, time_fn=time.monotonic):
super().__init__()
self.__time_fn = time_fn
self.__should_run = threading.Event()
self.__monitor_thread = None
self.__devices_by_name_lock = threading.RLock()

@property
def devices_by_name(self) -> T.Mapping[str, DeviceInfo]:
with self.__devices_by_name_lock:
return PyAudioDeviceMonitor.devices_by_name.fget(self)

@devices_by_name.setter
def devices_by_name(self, value: T.Mapping[str, DeviceInfo]):
with self.__devices_by_name_lock:
PyAudioDeviceMonitor.devices_by_name.fset(self, value)

@property
def is_running(self):
return self.__should_run.is_set()

def start(self):
if self.is_running:
return
self.__should_run.set()
self.__monitor_thread = threading.Thread(
name=f"{type(self).__name__}#{id(self)}",
target=self.__monitor_loop,
daemon=True,
)
self.__monitor_thread.start()

def stop(self):
self.__should_run.clear()
if self.__monitor_thread is not None:
self.__monitor_thread.join()
self.__monitor_thread = None

def cleanup(self):
self.stop()

# Private

def __monitor_loop(self, freq_hz=0.3):
exec_time = 1./freq_hz
time_fn = self.__time_fn

while self.is_running:
start_time = time_fn()

try:
self.update()
except Exception as err:
logger.error(err)

remaining_time = exec_time - (time_fn() - start_time)
if remaining_time > 0:
time.sleep(remaining_time)


class PyAudioDeviceSource():
Expand All @@ -21,7 +114,7 @@ def is_runnning(self) -> bool:

def start(self):
if self._session is None:
self._session = pyaudio_utils.create_session()
self._session = PyAudioManager.acquire_shared_instance()
if self._stream is None:
self._stream = self._session.open(
channels=self._channels,
Expand All @@ -39,11 +132,11 @@ def stop(self):
self._stream.close()
self._stream = None
if self._session is not None:
pyaudio_utils.destroy_session(self._session)
PyAudioManager.release_shared_instance(self._session)
self._session = None

def _stream_callback(self, in_data, frame_count, time_info, status):
time_info = pyaudio_utils.TimeInfo(time_info)
time_info = TimeInfo(time_info)
bytes_per_channel = pyaudio.get_sample_size(self._format)
theoretic_len = frame_count * self._channels * bytes_per_channel
assert theoretic_len == len(in_data)
Expand Down
Loading

0 comments on commit 919a325

Please sign in to comment.