pythonmultithreadingtkinterpython-sounddevice

Python: GUI freezing problem of thread using tkinter and sounddevice


I have a code for handle audio data from a sound device.

My code construct the GUI through tkinter and handles audio data through the sounddevice when the button is pressed.

I succeeded in handle audio data in real-time process using thread class.

When I press the Start button, the input sound from the microphone is perfectly output to the speaker.

However, there is a problem with the stop button.

when I press the stop button, my code try to kill the thread, but there is a GUI freezing occurs. And the thread doesn't die.

I made several attempts based on a lot of information in stack overflow, but all failed.

Please review my code and give me some advice.

This is my Code:

import sounddevice as sd
import numpy as np
import tkinter as tk
from tkinter import ttk
from threading import Thread


class StreamThread(Thread):
    def __init__(self):
        super().__init__()
        self.input_device_index = 0
        self.output_device_index = 4
        self.BLOCK_SHIFT = 128
        self.SAMPLING_RATE = 16000
        self.BLOCK_LEN = 512
        self.SOUND_DEVICE_LATENCY = 0.2

    def run(self):
        with sd.Stream(device=(self.input_device_index, self.output_device_index),
                   samplerate=self.SAMPLING_RATE, blocksize=self.BLOCK_SHIFT,
                   dtype=np.float32, latency=self.SOUND_DEVICE_LATENCY,
                   channels=1, callback=self.callback):
            input()  # Input start

    def callback(indata, outdata, frames, time, status):
        outdata[:] = indata

class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title("Please Help Me")
        self.geometry("400x300")
        self.resizable(0, 0)

        start_button = tk.Button(self, overrelief="solid", width=15,
                         command=lambda: start_button_clicked(),
                         text="Start", repeatdelay=1000, repeatinterval=100)
        start_button.grid(column=0, row=5)

        stop_button = tk.Button(self, overrelief="solid", width=15,
                         command=lambda: stop_button_clicked(),
                         text="Stop", repeatdelay=1000, repeatinterval=100)
        stop_button.grid(column=0, row=6)


def start_button_clicked():
    stream_thead.start()


def stop_button_clicked():
    # this is problem point
    if stream_thead.isAlive():
        sd.CallbackStop()
        sd.CallbackAbort()
        stream_thead.join()


if __name__ == "__main__":
    stream_thead = StreamThread()
    stream_thead.daemon = True  # set Daemon thread

    app = App()
    app.mainloop()

Solution

  • There are issues in your code:

    Below is modified code to fix the above issues:

    ...
    from threading import Thread, Event
    ...
    class StreamThread(Thread):
        ...
    
        def run(self):
            self.event = Event()
            with sd.Stream(device=(self.input_device_index, self.output_device_index),
                       samplerate=self.SAMPLING_RATE, blocksize=self.BLOCK_SHIFT,
                       dtype=np.float32, latency=self.SOUND_DEVICE_LATENCY,
                       channels=1, callback=self.callback) as self.stream:
                #input()  # Input start
                self.event.wait()
    
        def terminate(self):
            self.stream.abort() # abort the stream processing
            self.event.set() # break self.event.wait()
    
        def callback(self, indata, outdata, frames, time, status):
            outdata[:] = indata
    
    ...
    
    def stop_button_clicked():
        if stream_thread.is_alive():
            stream_thread.terminate()
            stream_thread.join()
    
    ...