You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tauri JS应用中断开外部耳机后Vosk语音转文本失效的问题排查求助

Tauri JS应用中断开外部耳机后Vosk语音转文本失效的问题排查求助

大家好,我在开发Tauri应用时遇到了一个特别棘手的问题,想请各位帮忙分析排查:

我的应用流程是前端JS录制音频→处理后通过Rust handler传给Python子进程→用Vosk做实时语音转文本。目前的奇怪现象是:

  • 当华为FreeBuds Pro耳机连接时(哪怕我明确指定使用MacBook内置麦克风的ID),语音转文本工作完全正常
  • 断开耳机并刷新页面重新录制后,音频录制本身是正常的(能保存成有效音频文件),但Vosk返回的识别结果始终是空字符串"",完全无法识别语音

下面是我的相关代码,麻烦各位帮忙看看哪里可能出问题了:


前端JS代码

获取麦克风流

const micStream = await navigator.mediaDevices.getUserMedia({
        audio: { deviceId: micId, sampleRate: 16000, channelCount: 1 },
        video: false,
      });

      streamAudioToPython(micStream);

音频流处理并发送给Rust

const streamAudioToPython = async (micStream?: any) => {
    try {
      const audioContext = new AudioContext();
      await audioContext.audioWorklet.addModule('/processor.js');

      const mediaStreamSource = audioContext.createMediaStreamSource(
        micStream || stream
      );
      setMediaStreamSource(mediaStreamSource);
      const audioProcessor = new AudioWorkletNode(
        audioContext,
        'audio-processor'
      );

      setAudioProcessor(audioProcessor);

      audioProcessor.port.onmessage = (event) => {
        const regularArray = event.data;

        invoke('send_audio_to_python', {
          audioData: regularArray,
        });
      };

      mediaStreamSource.connect(audioProcessor);
    } catch (error) {
      console.error('Error~~~>', error);
    }
  };

AudioWorklet处理器(processor.js)

class AudioProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    const input = inputs[0];
    if (input) {
      const channelData = input[0];
      const int16Data = new Int16Array(channelData.length);
      for (let i = 0; i < channelData.length; i++) {
        int16Data[i] = Math.min(1, Math.max(-1, channelData[i])) * 0x7fff;
      }

      const byteArray = new Uint8Array(int16Data.buffer);
      const regularArray = Array.from(byteArray);

      this.port.postMessage(regularArray);
    }
    return true;
  }
}

registerProcessor('audio-processor', AudioProcessor);

Rust后端代码

main.rs

mod execute_python_scripts;
mod show_in_folder;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let app_handle = app.handle();
            execute_python_scripts::force_initialize_python_process(app_handle);
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            show_in_folder::show_in_folder,
            execute_python_scripts::send_audio_to_python,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

execute_python_scripts.rs

use chrono::Local;
use std::fs::{self, OpenOptions};
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Manager};

#[tauri::command]
pub async fn send_audio_to_python(audio_data: Vec<u8>) -> Result<(), String> {
    let mut child_guard = PYTHON_PROCESS
        .lock()
        .map_err(|_poisoned| "Mutex poisoned.".to_string())?;

    if let Some(child_process) = child_guard.as_mut() {
        match child_process.try_wait() {
            Ok(Some(_)) => {
                return Err("Python process is no longer running.".to_string());
            }
            Ok(None) => {
                let stdin = child_process.stdin.as_mut().ok_or("Failed to open stdin")?;
                stdin.write_all(&audio_data).map_err(|e| e.to_string())?;
            }
            Err(e) => {
                return Err(format!("Failed to check Python process status: {}", e));
            }
        }
    } else {
        return Err("No Python process is currently running.".to_string());
    }

    Ok(())
}

pub fn start_python_process(app_handle: AppHandle) -> std::process::Child {
    let log_dir = "log";

    let exists = Path::new(log_dir).exists();

    if !exists {
        fs::create_dir_all(log_dir).expect("Failed to create log directory");
    }

    let now = Local::now();
    let log_file_name = format!("{}/logs_{}.txt", log_dir, now.format("%Y-%m-%d_%H-%M-%S"));

    let mut log_file = OpenOptions::new()
        .create(true)
        .append(true)
        .open(&log_file_name)
        .expect("Failed to open or create log file");

    let mut child = Command::new("python3")
        .arg("python/audio_to_speech.py")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .expect("Failed to start Python process");

    if let Some(stdout) = child.stdout.take() {
        let reader = BufReader::new(stdout);
        let app_handle_clone = app_handle.clone();
        std::thread::spawn(move || {
            for line in reader.lines() {
                if let Ok(text) = line {
                    writeln!(log_file, "{}", text).expect("Failed to write to log file");
                    app_handle_clone
                        .emit_all("python-log", format!("{}", text))
                        .expect("Failed to emit Python log");
                }
            }
        });
    }

    if let Some(stderr) = child.stderr.take() {
        let reader = BufReader::new(stderr);
        std::thread::spawn(move || {
            for line in reader.lines() {
                if let Ok(text) = line {
                    eprintln!("Down===> {}", text);
                }
            }
        });
    }

    return child;
}

pub fn force_initialize_python_process(app_handle: AppHandle) {
    let mut process_guard = PYTHON_PROCESS.lock().unwrap();

    if process_guard.is_none() {
        *process_guard = Some(start_python_process(app_handle));
    }
}

lazy_static::lazy_static! {
    static ref PYTHON_PROCESS: Arc<Mutex<Option<Child>>> = Arc::new(Mutex::new(None));
}

Python语音识别代码(audio_to_speech.py)

import sys
import vosk
import numpy as np
import json

fs = 16000
model = vosk.Model(lang="en-us")
recognizer = vosk.KaldiRecognizer(model, fs)


def process_audio_data(audio_data):
    """Process audio data with Vosk recognizer."""

    if recognizer.AcceptWaveform(audio_data):
        text = recognizer.Result()
        text_json = json.loads(text)
        print("text-", text_json["text"], flush=True)
    else:
        partial = recognizer.PartialResult()
        partial_json = json.loads(partial)
        print("partial-", partial_json["partial"], flush=True)


def handle_stream():
    audio_data = sys.stdin.buffer.read(4096)

    if len(audio_data) == 0:
        print("No audio data received.", flush=True)
        return

    audio_data = np.frombuffer(audio_data, dtype=np.int16)

    audio_data_bytes = audio_data.tobytes()

    process_audio_data(audio_data_bytes)


if __name__ == "__main__":
    while True:
        handle_stream()

我已经确认录制的音频是有效的,但Vosk就是识别不出内容,有没有朋友遇到过类似的问题?或者能帮我分析下可能的原因?

备注:内容来源于stack exchange,提问作者Zeeshan Ahmad Khalil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 16:49:50