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()
There are issues in your code:
input()
in a GUI application. What I understand you use input()
to put the thread task in a waiting state. Suggest to use threading.Event.wait()
instead.sd.CallbackStop()
and sd.CallbackAbort()
cannot break input()
. Use threading.Event.set()
to break threading.Event.wait()
.self
argument in def callback(indata, ...)
. It should be def callback(self, indata, ...)
.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()
...