python-sounddevice icon indicating copy to clipboard operation
python-sounddevice copied to clipboard

python sounddevice reset seems to hang

Open geoffr98 opened this issue 3 years ago • 6 comments

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)  

    


    

geoffr98 avatar Feb 18 '22 15:02 geoffr98

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?

mgeier avatar Feb 22 '22 19:02 mgeier

Yes, This is a cross-post from https://stackoverflow.com/q/71161494/

geoffr98 avatar Feb 24 '22 16:02 geoffr98

I've reposted the question with a sample program which will run.

geoffr98 avatar Feb 24 '22 16:02 geoffr98

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?

mgeier avatar Mar 13 '22 20:03 mgeier

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?

geoffr98 avatar Mar 18 '22 13:03 geoffr98

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.

mgeier avatar Apr 04 '22 17:04 mgeier