PAYATHON 2026

如何在 Tkinter 中实时显示 Vosk 语音识别结果?

数据小许

结论

不要在 Tkinter 主线程中运行持续读取音频的循环。题目中的代码先执行 while True,程序会一直停在循环里,无法到达 root.mainloop(),窗口事件也就无法正常处理。应将音频读取和 Vosk 识别放到工作线程,再用线程安全的 queue.Queue 将结果交给主线程,由主线程通过 root.after() 更新 Label。Tkinter 控件也应由创建它们的主线程操作。

下面的示例保留了 Model、KaldiRecognizer 和 PyAudio 的基本用法,并通过队列传递识别文本。

示例代码

import json
import queue
import threading
import tkinter as tk
from tkinter import ttk

import pyaudio
from vosk import KaldiRecognizer, Model

model = Model("vosk-model-small-cn-0.22")
rec = KaldiRecognizer(model, 16000)

p = pyaudio.PyAudio()
stream = p.open(
    format=pyaudio.paInt16,
    channels=1,
    rate=16000,
    input=True,
    frames_per_buffer=8000,
)

result_queue = queue.Queue()
stop_event = threading.Event()


def recognize_audio():
    """在工作线程中读取音频并把识别文本放入队列。"""
    try:
        while not stop_event.is_set():
            data = stream.read(4000, exception_on_overflow=False)

            if rec.AcceptWaveform(data):
                result = json.loads(rec.Result())
                text = result.get("text", "")
            else:
                # PartialResult() 可用于显示尚未完成的识别内容。
                result = json.loads(rec.PartialResult())
                text = result.get("partial", "")

            if text:
                result_queue.put(text)
    except OSError:
        # 窗口关闭时音频流可能被关闭,此时结束工作线程。
        pass


def update_label():
    """在 Tkinter 主线程中读取队列并更新控件。"""
    try:
        while True:
            text = result_queue.get_nowait()
            label.config(text=text)
    except queue.Empty:
        pass

    if not stop_event.is_set():
        root.after(50, update_label)


def close_window():
    stop_event.set()
    root.destroy()


root = tk.Tk()
root.title("Vosk 实时语音识别")
label = ttk.Label(root, text="正在等待语音…", wraplength=500)
label.pack(padx=16, pady=16)
root.protocol("WM_DELETE_WINDOW", close_window)

worker = threading.Thread(target=recognize_audio, daemon=True)
worker.start()
root.after(50, update_label)
root.mainloop()

stream.stop_stream()
stream.close()
p.terminate()

说明与注意事项

  • recognize_audio() 在工作线程中执行 stream.read() 和 Vosk 识别,不会直接访问 label 或其他 Tkinter 控件。
  • update_label() 由 root.after() 安排在 Tkinter 主线程中运行,从队列取出文本并更新标签。不要在工作线程中调用 label.config(),因为 Tkinter 通常要求在主线程中操作控件。
  • 识别器返回完整结果时,示例读取 Result();识别尚未完成时,则读取 PartialResult(),因此标签也会显示临时识别文本。若只想显示完整句子,可以移除 else 分支。
  • stream.read() 可能会等待音频数据返回,停止事件要等这次读取结束后才会被检查。示例使用较短的读取块,关闭窗口后线程通常会在下一次循环检查时退出。实际设备或参数不同,结束时间也可能不同。
  • 模型加载和音频流打开都发生在进入 mainloop() 之前。如果初始化过程让窗口迟迟无法显示,可以将初始化移入后台线程,再通过队列把初始化完成或错误状态交回主线程。

备注:内容仅供参考。