Java调用whisper.cpp读取InputStream陷入无限循环问题排查
问题解答
1. 读取输出最后一行的更好方式
不要定时去读取流,而是单独开一个后台线程持续监听whisper.cpp的stdout输出,把每一行转录文本存入线程安全的缓存容器(比如ConcurrentLinkedQueue<String>或者CopyOnWriteArrayList<String>)。需要获取最后一行时,直接从容器中取最新的元素即可。这种方式既不会阻塞主线程,也能实时捕获所有输出。
2. reader.readLine()永远不为null的原因
whisper.cpp的examples/stream是持续运行的流式转录进程,只要进程没被终止,它的stdout流会一直处于打开状态,用于持续输出实时转录的文本。readLine()方法只有当流被关闭(比如进程退出)时才会返回null,否则会一直阻塞等待新的输出内容,所以你的代码里它永远不会返回null,导致循环停不下来。
3. 代码的错误点
- Checker的设计逻辑错误:你用定时任务每5秒调用
checkText,但checkText里直接调用reader.readLine()——这个方法会一直阻塞,直到有新输出或者流关闭。因为whisper.cpp进程一直在运行,所以readLine()永远不会返回null,导致这个Runnable一直卡在这一行,形成无限循环。 - 忽略了stderr的读取:如果不读取whisper.cpp的stderr输出,当进程的stderr缓冲区满时,进程会被阻塞,无法继续输出stdout内容,这也是常见的坑。
改进方案示例
改进后的Transcriber类
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.concurrent.ConcurrentLinkedQueue; public class Transcriber { private Process whisperProcess; private final ConcurrentLinkedQueue<String> transcriptLines = new ConcurrentLinkedQueue<>(); private volatile boolean running = false; public void start() throws IOException { ProcessBuilder pb = new ProcessBuilder("./path/to/stream", "--model", "./models/ggml-base.en.bin"); // 合并stderr到stdout,或者单独处理stderr pb.redirectErrorStream(true); whisperProcess = pb.start(); running = true; // 后台线程持续读取输出 new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(whisperProcess.getInputStream()))) { String line; while (running && (line = reader.readLine()) != null) { transcriptLines.add(line); // 可选:如果只需要保留最后N行,超出时移除最早的 if (transcriptLines.size() > 100) { transcriptLines.poll(); } } } catch (IOException e) { e.printStackTrace(); } finally { running = false; } }).start(); } // 获取最新的转录行 public String getLastTranscriptLine() { return transcriptLines.peekLast(); } public void stop() { running = false; if (whisperProcess != null) { whisperProcess.destroy(); } } }
改进后的Checker类
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Checker { private final Transcriber transcriber; private final String targetWord; private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); public Checker(Transcriber transcriber, String targetWord) { this.transcriber = transcriber; this.targetWord = targetWord; } public void startChecking() { // 每5秒检查一次最新的转录内容 scheduler.scheduleAtFixedRate(() -> { String lastLine = transcriber.getLastTranscriptLine(); if (lastLine != null && lastLine.contains(targetWord)) { System.out.println("检测到目标词汇:" + targetWord); // 执行你的业务逻辑 } }, 0, 5, TimeUnit.SECONDS); } public void stopChecking() { scheduler.shutdown(); } }
使用示例
public class Main { public static void main(String[] args) throws IOException { Transcriber transcriber = new Transcriber(); transcriber.start(); Checker checker = new Checker(transcriber, "hello"); checker.startChecking(); // 程序运行一段时间后停止 // Runtime.getRuntime().addShutdownHook(new Thread(() -> { // checker.stopChecking(); // transcriber.stop(); // })); } }
内容的提问来源于stack exchange,提问作者Elijah Crum
相关产品推荐
相关产品推荐

