spatialaudio / python-sounddevice

:sound: Play and Record Sound with Python :snake:
https://python-sounddevice.readthedocs.io/
MIT License
980 stars 145 forks source link

python sounddevice reset seems to hang #394

Open geoffr98 opened 2 years ago

geoffr98 commented 2 years ago

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)  
mgeier commented 2 years ago

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?

geoffr98 commented 2 years ago

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

geoffr98 commented 2 years ago

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

mgeier commented 2 years ago

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?

geoffr98 commented 2 years ago

I have updated the code to:

`
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?

mgeier commented 2 years ago

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.