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()

...