python sounddevice reset seems to hang
I have some code running on a mac which is taking the microphone data from a bluetooth microphone to do voice commands, after which a response plays back on the speaker. Every now and again my code is no longer getting data from the microphone (I don't know why), so I tried adding a reset to the sounddevice, but that seems to hang - Can anyone provide some pointers to sort out why I might have these two problems.
Here is a version of my code with as much of the extraneous parts removed as I can. (Since it can take days before the problem happens I cannot confirm if this code shows the problem, but it should).
import sounddevice
import sys
import os
import queue
import threading
from datetime import datetime, timedelta
import time
class circularlist(object):
################################################
# Circular List Class
################################################
def __init__(self, size, data=[]):
"""Initialization"""
self.index = 0
self.size = size
self._data = list(data)[-size:]
def append(self, value):
"""Append an element"""
if len(self._data) == self.size:
self._data[self.index] = value
else:
self._data.append(value)
self.index = (self.index + 1) % self.size
def clear(self):
self._data.clear()
self._data = list(self._data)[-self.size:]
def __getitem__(self, key):
"""Get element by index, relative to the current index"""
if len(self._data) == self.size:
# The circular buffer is currently full.
if isinstance(key, slice):
return(self._data[(idx + self.index) % self.size] for idx in range(*key.indices(len(self._data))))
else:
if isinstance(key, slice):
return(self._data[(key + self.index) % self.size])
else:
return(self._data[(key + self.index) % self.size])
else:
# Circular buffer has not yet been filled
return(self._data[key])
def __len__(self):
return len(self._data)
def __repr__(self):
"""Return string representation"""
return self._data.__repr__() + ' (' + str(len(self._data)) + ' items)'
def Mic_callback(Audio_chunk, frames, time, status):
global Audio_rec_Buffer
# print(time)
if status:
print(status, file=sys.stderr)
Audio_rec_Buffer.put(Audio_chunk[:]) # Save to Audio buffer
def Audio_Recorder():
global Audio_rec_Buffer
global Record_Audio # Allows the GPIO thread to turn off the recording.
global Reset_Mic
global Mic_Device
global End_Program
global Audio_Block_Size
Buffer_Size = Audio_Block_Size * 5 * 2 # 5 Blocks of 2 bytes
try:
while Record_Audio: # latency=0.1
if Reset_Mic:
print("\t Resetting Mic.....")
sounddevice._terminate()
time.sleep(1)
sounddevice._initialize()
print("\t Mic is reset...")
Reset_Mic = False
try:
with sounddevice.RawInputStream(device=Mic_Device , samplerate=16000, channels=1, blocksize=Audio_Block_Size, dtype='int16', callback=Mic_callback):
time.sleep(10)
except Exception as Err_msg
print("ERROR: Unable to open record device: " + str(Mic_Device))
print("\tError: " + str(Err_msg))
for i in range(5, -1, -1): #Coundown from 6 to 0
time.sleep(1)
print("Retry again in " + str(i*1))
print("\t Reloading.....")
sounddevice._terminate()
time.sleep(1)
sounddevice._initialize()
print("\t Trying again...")
except Exception as Err_msg
print("ERROR2: Unable to open record device: " + str(Mic_Device))
print("\tError: " + str(Err_msg))
print("\t Exiting monitoring microphone!")
################################################
# Main Code
################################################
global Mic_Device
global Reset_Mic
global End_Program
global Audio_rec_Buffer
global Audio_Block_Size
Mic_Device = 'default'
Audio_Block_Size = 480 # Number of samples 480 #30ms - see math above
print("\n\n")
print("======================")
print("Starting ...")
print("======================")
try:
sounddevice.check_input_settings(device=Mic_Device, channels=1, dtype='int16', samplerate=16000)
except Exception as Err_msg
print("Unable to open record device: " + str(Mic_Device))
print("\tError: " + str(Err_msg))
print("Exiting...")
print("")
sys.exit(4)
else:
print(str(Mic_Device) + "input settings ok")
print("Setting up Audio Buffers")
Audio_rec_Buffer = queue.Queue() #
##################
# Start the Audio Recording Thread
##################
Record_Audio = True
Reset_Mic = False
End_Program = False
Audio_Recorder_Thread = threading.Thread(target=Audio_Recorder , args=())
Audio_Recorder_Thread.daemon = True
Audio_Recorder_Thread.start()
print("")
print("----------------------------------------------")
print("Listening ")
print("----------------------------------------------")
Debug_counter = 0
while not End_Program:
if Debug_counter == 5:
# This code was added as I was seeing the message (from below) that I could not get audio data from the Mic
# and it would repeatedly say this and never get audio data - as if the process getting the mic data was hung
# (although it never showed an error)
# BUT this code itself seems to hang when that happens.
print("\t Test- Resetting Mic.....")
sounddevice._terminate()
time.sleep(1)
sounddevice._initialize()
print("\t Test - Mic is reset...") #I never see this printout...
try:
audio_data = Audio_rec_Buffer.get(True,2) #Block for up to 2 second
except queue.Empty:
#Handle empty queue here
print("Could not get audio data from (" + str(Mic_Device) + ")" )
Debug_counter = Debug_counter +1
continue #go back to the start of the while loop.
#Now I work with the audio_data buffer (triggers etc)
Can you please provide valid Python code?
See https://python-sounddevice.readthedocs.io/en/0.4.4/CONTRIBUTING.html#reporting-problems
Also, if you cross-post, please provide links in both directions. I guess this is you: https://stackoverflow.com/q/71161494/, right?
Yes, This is a cross-post from https://stackoverflow.com/q/71161494/
I've reposted the question with a sample program which will run.
Thanks for the update!
Now it seems to be valid Python code and I can actually run it, that's good.
I still have the feeling that a few things are missing, for example: what is Log?
Anyway, this is far too much code for me to read.
Since it can take days before the problem happens I cannot confirm if this code shows the problem, but it should
Several days have passed, can you now confirm it?
What is supposed to happen in case of success? What is supposed to happen in case of failure?
I have updated the code to:
- replace Log with print so that should be clearer.
- remove some of the extraneous code (leftover bits I hadn't removed when posting here) `` Yes the problem has happened again. I had updated the code above so that getting the data from the buffer is a bit different as I wanted to print out more info. (just replace the code in the example above if your want to duplicate):
try:
audio_data = Audio_rec_Buffer.get(True,2) #Block for up to 2 second
except queue.Empty:
#Handle empty queue here
Log.Log2("Could not get audio data from (" + str(Mic_Device) + ")" )
Debug_counter = Debug_counter +1
try:
SDstatus = sounddevice.get_status()
Log.Log2("Sound Device Status: " + str(SDstatus))
except:
Log.Log2("Could not get Sound Device Status " )
Err_msg = PrintException()
Log.Log2("\tError: " + str(Err_msg),0)
# Since we can't get the status.. check the Mic recoding Thread.
if Audio_Recorder_Thread.is_alive():
Log.Log2("Audio Recorder is alive")
else:
Log.Log2("Audio Recorder is dead - restarting..")
Record_Audio = True
Reset_Mic = True #does sounddevice._terminate() & sounddevice._initialize()
End_Program = False
Audio_Recorder_Thread = threading.Thread(target=Audio_Recorder , args=())
Audio_Recorder_Thread.daemon = True
Audio_Recorder_Thread.start()
continue #go back to the start of the while loop.
With the above code when the problem happens the following is printed out: (The code was working for quite awhile before the error happened) ` 2022-03-17 19:57:03.512110 Could not get audio data from (None) 2022-03-17 19:57:03.513310 Could not get Sound Device Status 2022-03-17 19:57:03.514833 Error: Smart_Device.py_Exception 1050 <play()/rec()/playrec() was not called yet <class 'RuntimeError'>> 2022-03-17 19:57:03.515163 Audio Recorder is alive
`
I don't understand the Mic_Device being None - I don't change it, but wondering if it's a hint as to the problem?
This is still far too much code.
Just try to reduce it further, and you'll very likely find the problem yourself.
If not, please share the reduced code.