Skip to content

Commit

Permalink
Added arecord audio system and settings
Browse files Browse the repository at this point in the history
  • Loading branch information
synesthesiam committed Dec 14, 2018
1 parent 78221cc commit ebad05d
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 44 deletions.
57 changes: 18 additions & 39 deletions rhasspy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from flask import Flask, request, Response, jsonify, send_file, send_from_directory
from flask_cors import CORS
import pyaudio
import requests

import utils
Expand All @@ -25,6 +24,7 @@
from stt import transcribe_wav, maybe_load_decoder
from intent import best_intent
from train import train
from audio_recorder import PyAudioRecorder, ARecordAudioRecorder

# -----------------------------------------------------------------------------

Expand Down Expand Up @@ -547,52 +547,30 @@ def speech_to_intent(profile, wav_data, no_hass=False):

# -----------------------------------------------------------------------------

record_audio = None
record_mic = None
record_buffer = None
recorder = None

# Use arecord to start recording a WAV file to a temporary file
# Start recording a WAV file to a temporary buffer
@app.route('/api/start-recording', methods=['POST'])
def api_start_recording():
global record_audio, record_mic, record_buffer
global recorder
device_index = int(request.args.get('device', -1))
if device_index < 0:
device_index = None # default device

def stream_callback(data, frame_count, time_info, status):
global record_buffer
if record_buffer is None:
record_buffer = data
else:
record_buffer += data

return (data, pyaudio.paContinue)

record_audio = pyaudio.PyAudio()
data_format = record_audio.get_format_from_width(2) # 16-bit
record_mic = record_audio.open(format=data_format,
channels=1,
rate=16000,
input_device_index=device_index,
input=True,
stream_callback=stream_callback)

record_mic.start_stream()
profile = request_to_profile(request, profiles_dirs)
recorder = get_audio_recorder(profile)
recorder.start_recording(device_index)

return 'OK'

# Stop recording WAV file, transcribe, and get intent
@app.route('/api/stop-recording', methods=['POST'])
def api_stop_recording():
global decoders, record_audio, record_mic, record_buffer
global decoders, recorder
no_hass = request.args.get('nohass', 'false').lower() == 'true'

if (record_audio is not None) and (record_mic is not None) and (record_buffer is not None):
record_mic.stop_stream()
record_audio.terminate()

record_audio = None
record_mic = None
if recorder is not None:
record_buffer = recorder.stop_recording()
logging.debug('Stopped recording (got %s byte(s))' % len(record_buffer))

profile = request_to_profile(request, profiles_dirs)
Expand Down Expand Up @@ -632,15 +610,16 @@ def api_stop_recording():

@app.route('/api/microphones', methods=['GET'])
def api_microphones():
mics = {}
audio = pyaudio.PyAudio()
for i in range(audio.get_device_count()):
info = audio.get_device_info_by_index(i)
mics[i] = info['name']
profile = request_to_profile(request, profiles_dirs)
mics = get_audio_recorder(profile).get_microphones()
return jsonify(mics)

audio.terminate()
def get_audio_recorder(profile):
system = profile.microphone.get('system', 'pyaudio')
if system == 'arecord':
return ARecordAudioRecorder()

return jsonify(mics)
return PyAudioRecorder()

# -----------------------------------------------------------------------------

Expand Down
107 changes: 107 additions & 0 deletions rhasspy/audio_recorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import os
import re
import subprocess
import tempfile

class AudioRecorder:
def start_recording(self, profile):
pass

def stop_recording(self):
return []

def get_microphones(self):
return {}

# -----------------------------------------------------------------------------

class PyAudioRecorder(AudioRecorder):
def __init__(self):
self.audio = None
self.mic = None
self.buffer = None

def start_recording(self, device_index=None):
import pyaudio

def stream_callback(data, frame_count, time_info, status):
if self.buffer is None:
self.buffer = data
else:
self.buffer += data

return (data, pyaudio.paContinue)

self.audio = pyaudio.PyAudio()
data_format = self.audio.get_format_from_width(2) # 16-bit
self.mic = self.audio.open(format=data_format,
channels=1,
rate=16000,
input_device_index=device_index,
input=True,
stream_callback=stream_callback)

self.mic.start_stream()

def stop_recording(self):
self.mic.stop_stream()
self.audio.terminate()

return self.buffer

def get_microphones(self):
import pyaudio

mics = {}
audio = pyaudio.PyAudio()
for i in range(audio.get_device_count()):
info = audio.get_device_info_by_index(i)
mics[i] = info['name']

audio.terminate()

return mics

# -----------------------------------------------------------------------------

class ARecordAudioRecorder(AudioRecorder):
def start_recording(self, profile):
self.record_file = tempfile.NamedTemporaryFile(suffix='.wav', mode='wb+')
self.record_proc = subprocess.Popen(['arecord',
'-r', '16000',
'-f', 'S16_LE',
'-c', '1',
'-t', 'wav',
self.record_file.name],
close_fds=True)

def stop_recording(self):
self.record_proc.terminate()
self.record_file.seek(0)
data = open(self.record_file.name, 'rb').read()
try:
os.unlink(self.record_file.name)
except:
pass

return data

def get_microphones(self):
output = subprocess.check_output(['arecord', '-L'])\
.decode().splitlines()

mics = {}
name, description = None, None

# Parse output of arecord -L
for line in output:
line = line.rstrip()
if re.match(r'^\s', line):
description = line.strip()
else:
if name is not None:
mics[name] = description

name = line.strip()

return mics
2 changes: 1 addition & 1 deletion rhasspy/config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Rhasspy Assistant",
"slug": "rhasspy",
"version": "1.12",
"version": "1.13",
"description": "Offline voice assistant for Home Assistant",
"startup": "application",
"boot": "auto",
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion rhasspy/dist/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/img/favicon.png><link rel=stylesheet href=/css/bootstrap.min.css><link rel=stylesheet href=/css/fontawesome-all.min.css><link rel=stylesheet href=/css/main.css><title>Rhasspy Voice Assistant</title><link href=/css/app.acd79e7b.css rel=preload as=style><link href=/js/app.facf817a.js rel=preload as=script><link href=/js/chunk-vendors.fd9734c4.js rel=preload as=script><link href=/css/app.acd79e7b.css rel=stylesheet></head><body><noscript><strong>Please enable Javascript to continue.</strong></noscript><div id=app></div><script src=/js/jquery-3.3.1.slim.min.js></script><script src=/js/axios.min.js></script><script src=/js/vue-axios.min.js></script><script src=/js/bootstrap.min.js></script><script src=/js/chunk-vendors.fd9734c4.js></script><script src=/js/app.facf817a.js></script></body></html>
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/img/favicon.png><link rel=stylesheet href=/css/bootstrap.min.css><link rel=stylesheet href=/css/fontawesome-all.min.css><link rel=stylesheet href=/css/main.css><title>Rhasspy Voice Assistant</title><link href=/css/app.778eaa10.css rel=preload as=style><link href=/js/app.75eca726.js rel=preload as=script><link href=/js/chunk-vendors.fd9734c4.js rel=preload as=script><link href=/css/app.778eaa10.css rel=stylesheet></head><body><noscript><strong>Please enable Javascript to continue.</strong></noscript><div id=app></div><script src=/js/jquery-3.3.1.slim.min.js></script><script src=/js/axios.min.js></script><script src=/js/vue-axios.min.js></script><script src=/js/bootstrap.min.js></script><script src=/js/chunk-vendors.fd9734c4.js></script><script src=/js/app.75eca726.js></script></body></html>
2 changes: 2 additions & 0 deletions rhasspy/dist/js/app.75eca726.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions rhasspy/dist/js/app.75eca726.js.map

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions rhasspy/dist/js/app.facf817a.js

This file was deleted.

1 change: 0 additions & 1 deletion rhasspy/dist/js/app.facf817a.js.map

This file was deleted.

1 change: 1 addition & 0 deletions rhasspy/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def load_profile(self):
self.intent = self.json.get('intent', {})
self.wake = self.json.get('wake', {})
self.training = self.json.get('training', {})
self.microphone = self.json.get('microphone', {})

def read_path(self, *path_parts):
for profiles_dir in self.profiles_dirs:
Expand Down
3 changes: 3 additions & 0 deletions rhasspy/profiles/defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
},
"system": "fuzzywuzzy"
},
"microphone": {
"system": "pyaudio"
},
"rhasspy": {
"default_profile": "en",
"listen_on_start": false,
Expand Down

0 comments on commit ebad05d

Please sign in to comment.