Python: 使用 tkinter 和 sounddevice 的线程的 GUI 冻结问题
Python: GUI freezing problem of thread using tkinter and sounddevice
我有一个代码可以处理来自声音设备的音频数据。
我的代码通过 tkinter
构建 GUI,并在按下按钮时通过 sounddevice
处理音频数据。
我成功地使用线程class实时处理了音频数据。
当我按下开始按钮时,麦克风的输入声音完美地输出到扬声器。
但是,停止按钮有问题。
当我按下停止按钮时,我的代码尝试终止线程,但出现了 GUI 冻结。而且线程不会死。
我根据stack overflow中的大量信息进行了多次尝试,但都失败了。
请检查我的代码并给我一些建议。
这是我的代码:
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()
您的代码中存在问题:
- 在 GUI 应用程序中使用控制台
input()
。据我了解,您使用 input()
将线程任务置于等待状态。建议改用threading.Event.wait()
。
sd.CallbackStop()
和 sd.CallbackAbort()
不能打破 input()
。使用 threading.Event.set()
打破 threading.Event.wait()
.
def callback(indata, ...)
中缺少 self
参数。应该是 def callback(self, indata, ...)
.
下面是修正上述问题的修改代码:
...
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()
...
我有一个代码可以处理来自声音设备的音频数据。
我的代码通过 tkinter
构建 GUI,并在按下按钮时通过 sounddevice
处理音频数据。
我成功地使用线程class实时处理了音频数据。
当我按下开始按钮时,麦克风的输入声音完美地输出到扬声器。
但是,停止按钮有问题。
当我按下停止按钮时,我的代码尝试终止线程,但出现了 GUI 冻结。而且线程不会死。
我根据stack overflow中的大量信息进行了多次尝试,但都失败了。
请检查我的代码并给我一些建议。
这是我的代码:
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()
您的代码中存在问题:
- 在 GUI 应用程序中使用控制台
input()
。据我了解,您使用input()
将线程任务置于等待状态。建议改用threading.Event.wait()
。 sd.CallbackStop()
和sd.CallbackAbort()
不能打破input()
。使用threading.Event.set()
打破threading.Event.wait()
.def callback(indata, ...)
中缺少self
参数。应该是def callback(self, indata, ...)
.
下面是修正上述问题的修改代码:
...
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()
...