Repository: MycroftAI/mycroft-core

Issue:
Raspbian audio input freeze
As described in forum posts https://community.mycroft.ai/t/speech-rec-for-mycroftpi-using-kinect/1267/7 and https://community.mycroft.ai/t/mic-just-stopped-working-weird-right-and-will-not-come-back-local-usb-or-mic-jack/2191/17 the audio input on a raspberry pi installation goes dead after a few minutes. Mycroft cannot record any more audio input and the mic level display in the cli just freezes.

It occurred to me both with a regular picroft image on a rpi3 as well as an install on raspbian stretch from the master branch of the repo with two different usb microphones. In my setup the bug is reproducable every time when starting a mycroft instance, however it does not seem to be widespread, so i don´t know what is causing it specifically.

In the code the problem originates in the MutableStream class where at some point calls to the pyaduio `stream.read()` method only result in IOErrors. IOErrors are thrown on input overflows as described in http://stackoverflow.com/questions/10733903/pyaudio-input-overflowed. The AudioProducer Thread therefore only handles those errors, no audio is processed.

This seems to be a pyaudio internal issue on the raspberry pi. 
It can however be fixed easily by passing a parameter `exception_on_overflow=False` to the pyaudio `stream.read()` method.
  


Discussion:
Thanks for the great detective work. I'll start testing it out and see if there seem to be any downside to using this parameter on any other device. (but it sounds quite safeso should be no worries)

Resolving patch:
diff --git a/mycroft/client/speech/mic.py b/mycroft/client/speech/mic.py
--- a/mycroft/client/speech/mic.py
+++ b/mycroft/client/speech/mic.py
@@ -61,7 +61,18 @@ def mute(self):
     def unmute(self):
         self.muted = False
 
-    def read(self, size):
+    def read(self, size, of_exc=False):
+        """
+            Read data from stream.
+
+            Arguments:
+                size (int): Number of bytes to read
+                of_exc (bool): flag determining if the audio producer thread
+                               should throw IOError at overflows.
+
+            Returns:
+                Data read from device
+        """
         frames = collections.deque()
         remaining = size
         while remaining > 0:
@@ -69,7 +80,8 @@ def read(self, size):
             if to_read == 0:
                 sleep(.01)
                 continue
-            result = self.wrapped_stream.read(to_read)
+            result = self.wrapped_stream.read(to_read,
+                                              exception_on_overflow=of_exc)
             frames.append(result)
             remaining -= to_read
 
@@ -169,6 +181,8 @@ def __init__(self, wake_word_recognizer):
         self.upload_config = listener_config.get('wake_word_upload')
         self.wake_word_name = wake_word_recognizer.key_phrase
 
+        self.overflow_exc = listener_config.get('overflow_exception', False)
+
         speech_recognition.Recognizer.__init__(self)
         self.wake_word_recognizer = wake_word_recognizer
         self.audio = pyaudio.PyAudio()
@@ -197,9 +211,8 @@ def __init__(self, wake_word_recognizer):
         except (requests.HTTPError, requests.ConnectionError, AttributeError):
             self.account_id = '0'
 
-    @staticmethod
-    def record_sound_chunk(source):
-        return source.stream.read(source.CHUNK)
+    def record_sound_chunk(self, source):
+        return source.stream.read(source.CHUNK, self.overflow_exc)
 
     @staticmethod
     def calc_energy(sound_chunk, sample_width):


Test patch: