import wave import argparse import os import sys import signal import time import utils import typing import subprocess import sox import re # To mono audio CHANNELS = 1 # Target rate is 16K RATE = 48000 CHUNK = 1024 # Time limitation 300 seconds TIME_LIMIT = 300 # Restart PyAudio def restart_audio(): return class AlsaRecorder: def __init__(self, device_name: str, channels: int = 1, rate: int = RATE, fname: str = None): self.channels = channels self.rate = rate self.device_name = device_name self.fname = fname def __exit__(self, exception, value, traceback): self.stop_recording() def close(self): self.stop_recording() def start_recording(self): utils.log(f'Start recording with device name {self.device_name}, channels {self.channels}, samplerate {self.rate} to {self.fname}') # /usr/bin/nice -n -5 cmd = f'/usr/bin/arecord -D {self.device_name} --format S16_LE --rate {self.rate} -c {self.channels} --buffer-size 262144 {self.fname}' utils.log_verbose(cmd) self.process_handle = subprocess.Popen(cmd.split()) return self def stop_recording(self): if self.process_handle: try: self.process_handle.send_signal(signal.SIGINT) self.process_handle.wait(timeout=5.0) except: utils.log_error(f'/usr/bin/arecord timeout on exit') self.process_handle = None utils.log(f'ALSA recording stopped.') return self @classmethod def find_default(cls) -> str: return find_alsa_usb_device('arecord') class AlsaPlayer: def __init__(self, device_name: str, channels: int = 1, rate: int = RATE, fname: str = None): self.channels = channels self.rate = rate self.device_name = device_name self.fname = fname def __exit__(self, exception, value, traceback): self.stop_playing() def close(self): self.stop_playing() def start_playing(self): utils.log(f'Start playing with device name {self.device_name}, channels {self.channels}, samplerate {self.rate} from {self.fname}') # /usr/bin/nice -n -5 cmd = f'/usr/bin/aplay -D {self.device_name} --format S16_LE --rate {self.rate} -c {self.channels} --buffer-size 128000 {self.fname}' utils.log_verbose(cmd) self.process_handle = subprocess.Popen(cmd.split()) return self def stop_playing(self): if self.process_handle: try: self.process_handle.send_signal(signal.SIGINT) self.process_handle.wait(timeout=5.0) except: utils.log_error(f'/usr/bin/aplay timeout on exit') self.process_handle = None utils.log(f'ALSA playing stopped.') return self @classmethod def find_default(cls) -> str: return find_alsa_usb_device('aplay') # utility should aplay or arecord def find_alsa_usb_device(utility: str) -> str: retcode, aplay_output = subprocess.getstatusoutput(f'/usr/bin/{utility} -l') if retcode != 0: return None # Parse data line by line pattern = r'card\s(?P\d+):(?P.+)device\s(?P\d+):(?P.+)' lines = aplay_output.splitlines() for l in lines: found = re.match(pattern, l) if found: if 'card_id' in found.groupdict() and 'card_name' in found.groupdict() and 'device_id' in found.groupdict() and 'device_name' in found.groupdict(): card_id = found.group('card_id') card_name = found.group('card_name') device_id = found.group('device_id') device_name = found.group('device_name') if 'usb' in card_name.lower() and 'usb' in device_name.lower(): return f'hw:{card_id},{device_id}' return None