Java集成whisper.cpp

Java集成whisper.cpp

windows11,idea,java25,javaFx25

下载cmake

下载Visual Studio 的 C++ 构建工具,Visual Studio Community 2022(免费版),勾选 Desktop development with C++

1.打开 Visual Studio(管理员),切换到 whisper.cpp 源代码目录:
E: cd E:\My_Dream\whisper.cpp 
2. 创建并配置构建目录(这会在 whisper.cpp 文件夹内创建一个名为 build 的子文件夹)
cmake -B build 
3.构建项目(使用 -j 可以并行编译;在 Windows 上默认使用所有可用核心)
cmake --build build --config Release -j 
成功构建后,可执行文件(如 whisper-cli.exe、main.exe 等)将位于 build\bin\Release\ 目录下。
在这里插入图片描述
推荐手动下载模型!!!
  1. 访问 Hugging Face 仓库。
  2. 找到文件 ggml-small.bin(不是 ggml-small.en.bin,除非您只用英语)。
  3. 点击下载(或直接链接:https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin)
  4. 将下载的文件放到您的项目目录下:E:\My_Dream\whisper.cpp\models\ggml-small.bin
  5. 如果 models 文件夹不存在,先创建它:mkdir models
  6. 验证:在命令提示符中运行 dir models,确认看到 ggml-small.bin 且文件大小约 488 MB。
重新构建 whisper.cpp(静态链接,避免 DLL 兼容问题)
进入项目目录:
cd E:\My_Dream\whisper.cpp 
删除旧构建目录(清理):
rmdir /s /q build 
重新配置并构建(明确指定静态共享库关闭):
cmake -B build -DBUILD_SHARED_LIBS=OFF cmake --build build --config Release -j 
第三步:运行测试
build\bin\Release\whisper-cli.exe -m models/ggml-small.bin -f samples/jfk.wav 
预期输出
[00:00:00.000-->00:00:11.000] And so, my fellow Americans: ask not what your country can dofor you—ask what you can dofor your country.
项目添加依赖
<!-- whisper-jni--><dependency><groupId>io.github.givimad</groupId><artifactId>whisper-jni</artifactId><version>1.7.1</version></dependency>
fxml页面与控制器
<?xml version="1.0" encoding="UTF-8"?><?import javafx.geometry.Insets?><?import javafx.scene.control.*?><?import javafx.scene.layout.*?><?import javafx.collections.FXCollections?><?import java.lang.String?><VBoxspacing="15.0"alignment="TOP_CENTER"xmlns="http://javafx.com/javafx"xmlns:fx="http://javafx.com/fxml/1"fx:controller="com.flower.controller.WhisperTranscribeController"><!-- 请自行创建对应 Controller --><padding><Insetstop="20"right="30"bottom="20"left="30"/></padding><Labeltext="本地语音转文字(Whisper)"style="-fx-font-size: 20px;-fx-font-weight: bold;"/><GridPanehgap="10"vgap="12"alignment="CENTER"><columnConstraints><ColumnConstraintshgrow="SOMETIMES"minWidth="120"/><ColumnConstraintshgrow="ALWAYS"/></columnConstraints><!-- 模型路径 --><Labeltext="模型文件:"GridPane.rowIndex="0"GridPane.columnIndex="0"/><HBoxspacing="8"alignment="CENTER_LEFT"GridPane.rowIndex="0"GridPane.columnIndex="1"><TextFieldfx:id="modelPathField"prefWidth="400"promptText="选择 ggml-*.bin 模型文件"/><Buttontext="浏览..."onAction="#chooseModelFile"/></HBox><!-- 音频文件路径 --><Labeltext="音频文件:"GridPane.rowIndex="1"GridPane.columnIndex="0"/><HBoxspacing="8"alignment="CENTER_LEFT"GridPane.rowIndex="1"GridPane.columnIndex="1"><TextFieldfx:id="audioPathField"prefWidth="400"promptText="支持 wav、mp3 等(会自动转 16k 单声道)"/><Buttontext="浏览..."onAction="#chooseAudioFile"/></HBox><!-- 语言 --><Labeltext="语言:"GridPane.rowIndex="2"GridPane.columnIndex="0"/><ComboBoxfx:id="languageCombo"prefWidth="200"GridPane.rowIndex="2"GridPane.columnIndex="1"><items><FXCollectionsfx:factory="observableArrayList"><Stringfx:value="auto"/><Stringfx:value="en"/><Stringfx:value="zh"/><Stringfx:value="ja"/><Stringfx:value="ko"/><Stringfx:value="fr"/><Stringfx:value="de"/><Stringfx:value="es"/><!-- 可继续添加 --></FXCollections></items><value><Stringfx:value="auto"/></value></ComboBox><!-- 是否翻译 --><CheckBoxfx:id="translateCheck"text="翻译成英语"GridPane.rowIndex="3"GridPane.columnIndex="1"/><!-- 是否时间戳 --><CheckBoxfx:id="timestampsCheck"text="输出带时间戳(字幕格式)"selected="true"GridPane.rowIndex="4"GridPane.columnIndex="1"/><!-- 输出格式 --><Labeltext="输出格式:"GridPane.rowIndex="5"GridPane.columnIndex="0"/><ComboBoxfx:id="outputFormatCombo"prefWidth="200"GridPane.rowIndex="5"GridPane.columnIndex="1"><items><FXCollectionsfx:factory="observableArrayList"><Stringfx:value="txt"/><Stringfx:value="srt"/><Stringfx:value="vtt"/></FXCollections></items><value><Stringfx:value="txt"/></value></ComboBox><!-- 保存地址 --><Labeltext="保存位置:"GridPane.rowIndex="6"GridPane.columnIndex="0"/><HBoxspacing="8"alignment="CENTER_LEFT"GridPane.rowIndex="6"GridPane.columnIndex="1"><TextFieldfx:id="savePathField"prefWidth="400"promptText="选择保存文件或文件夹"/><Buttontext="浏览..."onAction="#chooseSavePath"/></HBox></GridPane><HBoxspacing="20"alignment="CENTER"><Buttontext="开始转录"fx:id="startButton"style="-fx-font-size: 16px;-fx-padding: 10 20;"onAction="#startTranscription"/><ProgressBarfx:id="progressBar"prefWidth="300"visible="false"/></HBox><Separator/><Labeltext="转录结果:"style="-fx-font-size: 16px;"/><TextAreafx:id="resultArea"prefHeight="300"wrapText="true"editable="false"/></VBox>
packagecom.flower.controller;importio.github.givimad.whisperjni.WhisperContext;importio.github.givimad.whisperjni.WhisperFullParams;importio.github.givimad.whisperjni.WhisperJNI;importjavafx.application.Platform;importjavafx.concurrent.Task;importjavafx.fxml.FXML;importjavafx.scene.control.*;importjavafx.stage.FileChooser;importjavafx.stage.Stage;importorg.springframework.stereotype.Component;importjavax.sound.sampled.AudioFormat;importjavax.sound.sampled.AudioInputStream;importjavax.sound.sampled.AudioSystem;importjavax.sound.sampled.UnsupportedAudioFileException;importjava.io.BufferedWriter;importjava.io.File;importjava.io.FileWriter;importjava.io.IOException;importjava.net.URL;importjava.nio.ByteBuffer;importjava.nio.ByteOrder;importjava.nio.file.Path;importjava.nio.file.Paths;importjava.util.ResourceBundle;@ComponentpublicclassWhisperTranscribeControllerextendsBaseFormController{@FXMLprivateTextField modelPathField;@FXMLprivateTextField audioPathField;@FXMLprivateTextField savePathField;@FXMLprivateComboBox<String> languageCombo;@FXMLprivateCheckBox translateCheck;@FXMLprivateCheckBox timestampsCheck;@FXMLprivateComboBox<String> outputFormatCombo;@FXMLprivateButton startButton;@FXMLprivateProgressBar progressBar;@FXMLprivateTextArea resultArea;privateStage stage;privateWhisperJNI whisperJNI;@Overridepublicvoidinitialize(URL url,ResourceBundle resourceBundle){// 初始化语言默认值 languageCombo.getSelectionModel().select("auto"); outputFormatCombo.getSelectionModel().select("txt");// 初始化 whisper-jnitry{WhisperJNI.loadLibrary(); whisperJNI =newWhisperJNI(); resultArea.appendText("Whisper JNI 加载成功\n"); resultArea.appendText("系统信息:\n"+ whisperJNI.getSystemInfo()+"\n");}catch(Exception e){showAlert("错误","无法加载 Whisper JNI 库: "+ e.getMessage()); e.printStackTrace();}// 默认模型路径(可根据您的实际路径修改) modelPathField.setText("E:/My_Dream/whisper.cpp/models/ggml-small.bin");}publicvoidsetStage(Stage stage){this.stage = stage;}@FXMLprivatevoidchooseModelFile(){File file =showFileChooser("选择 Whisper 模型文件","Whisper 模型 (*.bin)","*.bin");if(file !=null){ modelPathField.setText(file.getAbsolutePath());}}/** * Java Sound API(javax.sound.sampled) 默认只支持有限的音频格式 */@FXMLprivatevoidchooseAudioFile(){File file =showFileChooser("选择音频文件","音频文件","*.wav");if(file !=null){ audioPathField.setText(file.getAbsolutePath());}}@FXMLprivatevoidchooseSavePath(){FileChooser fileChooser =newFileChooser(); fileChooser.setTitle("保存转录结果");String format = outputFormatCombo.getValue();String ext = format !=null? format :"txt"; fileChooser.getExtensionFilters().add(newFileChooser.ExtensionFilter("转录文件 (*."+ ext +")","*."+ ext)); fileChooser.setInitialFileName("transcription."+ ext);File file = fileChooser.showSaveDialog(stage);if(file !=null){ savePathField.setText(file.getAbsolutePath());}}privateFileshowFileChooser(String title,String filterName,String... extensions){FileChooser fileChooser =newFileChooser(); fileChooser.setTitle(title); fileChooser.getExtensionFilters().add(newFileChooser.ExtensionFilter(filterName, extensions));return fileChooser.showOpenDialog(stage);}@FXMLprivatevoidstartTranscription(){String modelPath = modelPathField.getText().trim();String audioPath = audioPathField.getText().trim();String savePath = savePathField.getText().trim();if(modelPath.isEmpty()|| audioPath.isEmpty()){showAlert("错误","请先选择模型文件和音频文件!");return;} startButton.setDisable(true); progressBar.setVisible(true); progressBar.setProgress(ProgressBar.INDETERMINATE_PROGRESS); resultArea.clear(); resultArea.appendText("正在加载模型和处理音频,请稍等...\n");Task<String> task =newTask<>(){@OverrideprotectedStringcall()throwsException{Path model =Paths.get(modelPath);try(WhisperContext ctx = whisperJNI.init(model)){float[] samples =loadAudioSamples(audioPath);WhisperFullParams params =newWhisperFullParams();String lang = languageCombo.getValue(); params.language ="auto".equals(lang)?null: lang; params.translate = translateCheck.isSelected(); params.printTimestamps = timestampsCheck.isSelected();int result = whisperJNI.full(ctx, params, samples, samples.length);if(result !=0){thrownewRuntimeException("Whisper 转录失败,错误码: "+ result);}int segments = whisperJNI.fullNSegments(ctx);StringBuilder transcription =newStringBuilder();for(int i =0; i < segments; i++){String text = whisperJNI.fullGetSegmentText(ctx, i).trim();long t0 = whisperJNI.fullGetSegmentTimestamp0(ctx, i);long t1 = whisperJNI.fullGetSegmentTimestamp1(ctx, i);if(timestampsCheck.isSelected()){ transcription.append(formatTimestamp(t0)).append(" --> ").append(formatTimestamp(t1)).append("\n");} transcription.append(text).append("\n\n");}// 保存文件if(!savePath.isEmpty()){String format = outputFormatCombo.getValue();String content =switch(format){case"srt"->toSrt(transcription.toString(), segments, ctx);case"vtt"->toVtt(transcription.toString());default-> transcription.toString();};saveToFile(savePath, content);}return transcription.toString();}}@Overrideprotectedvoidsucceeded(){Platform.runLater(()->{ resultArea.appendText("\n转录完成!\n\n"); resultArea.appendText(getValue());if(!savePath.isEmpty()){ resultArea.appendText("\n文件已保存至:\n"+ savePath +"\n");}finishTask();});}@Overrideprotectedvoidfailed(){Platform.runLater(()->{ resultArea.appendText("\n转录失败:\n"+getException().getMessage()+"\n");getException().printStackTrace();finishTask();});}};newThread(task).start();}privatevoidfinishTask(){ progressBar.setVisible(false); startButton.setDisable(false);}// 与 WhisperTest.java 中相同的音频加载方法privatefloat[]loadAudioSamples(String audioPathStr)throwsIOException,UnsupportedAudioFileException{File audioFile =newFile(audioPathStr);try(AudioInputStream audioStream =AudioSystem.getAudioInputStream(audioFile)){AudioFormat baseFormat = audioStream.getFormat();AudioFormat targetFormat =newAudioFormat(AudioFormat.Encoding.PCM_FLOAT,16000.0f,32,1,4,16000.0f, baseFormat.isBigEndian());try(AudioInputStream convertedStream =AudioSystem.getAudioInputStream(targetFormat, audioStream)){byte[] bytes = convertedStream.readAllBytes();ByteBuffer buffer =ByteBuffer.wrap(bytes); buffer.order(baseFormat.isBigEndian()?ByteOrder.BIG_ENDIAN :ByteOrder.LITTLE_ENDIAN);float[] samples =newfloat[bytes.length /4]; buffer.asFloatBuffer().get(samples);return samples;}catch(IllegalArgumentException e){// 回退到 PCM_SIGNED 16-bitAudioFormat pcmFormat =newAudioFormat(AudioFormat.Encoding.PCM_SIGNED,16000.0f,16,1,2,16000.0f, baseFormat.isBigEndian());try(AudioInputStream pcmStream =AudioSystem.getAudioInputStream(pcmFormat, audioStream)){byte[] pcmBytes = pcmStream.readAllBytes();float[] samples =newfloat[pcmBytes.length /2];ByteBuffer buffer =ByteBuffer.wrap(pcmBytes); buffer.order(pcmFormat.isBigEndian()?ByteOrder.BIG_ENDIAN :ByteOrder.LITTLE_ENDIAN);for(int i =0; i < samples.length; i++){ samples[i]= buffer.getShort()/32768.0f;}return samples;}}}}privateStringformatTimestamp(long ms){long hours = ms /3600000;long minutes =(ms %3600000)/60000;long seconds =(ms %60000)/1000;long millis = ms %1000;returnString.format("%02d:%02d:%02d,%03d", hours, minutes, seconds, millis);}privateStringtoSrt(String text,int segments,WhisperContext ctx){StringBuilder srt =newStringBuilder();for(int i =0; i < segments; i++){long t0 = whisperJNI.fullGetSegmentTimestamp0(ctx, i);long t1 = whisperJNI.fullGetSegmentTimestamp1(ctx, i);String segmentText = whisperJNI.fullGetSegmentText(ctx, i).trim(); srt.append(i +1).append("\n"); srt.append(formatTimestamp(t0)).append(" --> ").append(formatTimestamp(t1)).append("\n"); srt.append(segmentText).append("\n\n");}return srt.toString();}privateStringtoVtt(String text){return"WEBVTT\n\n"+ text.replaceAll("(\\d{2}:\\d{2}:\\d{2},\\d{3}) --> (\\d{2}:\\d{2}:\\d{2},\\d{3})","$1 --> $2");}privatevoidsaveToFile(String path,String content)throwsIOException{try(BufferedWriter writer =newBufferedWriter(newFileWriter(path))){ writer.write(content);}}privatevoidshowAlert(String title,String message){Alert alert =newAlert(Alert.AlertType.ERROR); alert.setTitle(title); alert.setHeaderText(null); alert.setContentText(message); alert.showAndWait();}@OverridepublicvoidresetForm(){}@OverridepublicbooleanvalidateForm(){returnfalse;}}
在这里插入图片描述

Read more

Qwen3-VL-WEBUI在线教育:作业批改自动化部署解决方案

Qwen3-VL-WEBUI在线教育:作业批改自动化部署解决方案 1. 引言:在线教育中的作业批改痛点与技术革新 在当前快速发展的在线教育生态中,教师面临海量学生作业的批改任务,尤其是涉及图像、图表、手写公式甚至视频类内容时,传统文本型大模型难以胜任。人工批改耗时耗力,而现有自动化工具在多模态理解能力、复杂逻辑推理和跨模态对齐精度上存在明显短板。 阿里云最新开源的 Qwen3-VL-WEBUI 正是为解决这一核心痛点而生。它不仅集成了迄今为止最强大的视觉-语言模型 Qwen3-VL-4B-Instruct,还通过 WebUI 界面实现了“开箱即用”的本地化部署,特别适用于教育机构实现作业自动批改系统的轻量化落地。 本文将围绕 Qwen3-VL-WEBUI 在在线教育场景下的作业批改自动化部署方案展开,涵盖其技术优势、部署流程、实际应用案例及优化建议,帮助开发者和教育科技团队快速构建高效、精准的智能批改系统。 2. 技术背景:Qwen3-VL 的核心能力解析 2.1 Qwen3-VL 模型架构升级详解 作为 Qwen 系列的最新一代视觉语言模型,Qwen3-VL 在多个

DAMO-YOLO-S WebUI无障碍适配:屏幕阅读器支持与键盘导航优化

DAMO-YOLO-S WebUI无障碍适配:屏幕阅读器支持与键盘导航优化 1. 项目背景与意义 在现代Web应用开发中,无障碍访问(Accessibility)已经成为一个不可忽视的重要议题。DAMO-YOLO-S作为一个基于先进目标检测技术的手机检测系统,其Web界面的无障碍适配对于确保所有用户都能平等使用这一技术具有重要意义。 传统的计算机视觉应用往往忽视了视障用户和行动不便用户的需求。通过为DAMO-YOLO-S WebUI添加屏幕阅读器支持和键盘导航优化,我们不仅提升了产品的包容性,也为更多用户群体打开了使用先进AI技术的大门。 这项改进工作的核心价值在于: * 平等访问:确保视障用户能够通过屏幕阅读器理解界面内容和操作流程 * 操作便利:为无法使用鼠标的用户提供完整的键盘操作支持 * 合规性:符合Web内容无障碍指南(WCAG)标准要求 * 用户体验:为所有用户提供更加友好和高效的操作体验 2. 屏幕阅读器支持实现 2.1 ARIA标签优化 为DAMO-YOLO-S WebUI中的关键元素添加适当的ARIA(Accessible Rich Int

用 ASCII 草图 + AI 快速生成前端代码

引言 从想法到代码,中间往往要经历画原型、出设计稿等环节。 用 ASCII 草图,可以跳过大量原型绘制、结构拆解和手动搭骨架的中间步骤。 这种表达方式其实一直存在,但真正让它进入工程流程的,是 AI 的能力提升。大语言模型对结构化文本具有很强的解析能力,能够识别文本中的层级、对齐关系与空间划分,并将这些结构信息稳定地映射为组件树和页面布局。 因此,ASCII 不再只是沟通草稿,而成为一种可执行的结构描述。 什么是 “ASCII 草图” 提到 ASCII,很多人的第一反应可能是那个年代久远的“字符画”。没错,ASCII 草图就是用字符来构建页面布局。 在 AI 时代,这种看似简陋的草图,其实蕴含着巨大的能量。大语言模型(LLM)对结构化文本的理解能力极强。相比于模糊的自然语言描述(“我要一个左边宽右边窄的布局”),ASCII 草图提供了一种所见即所得的结构化 Prompt。 简单来说,ASCII 草图充当了视觉蓝图的角色,AI 根据这个结构生成代码。

WebGIS + 无人机 + AI:下一代智能巡检系统?

WebGIS + 无人机 + AI:下一代智能巡检系统?

WebGIS 遇上无人机,再叠加 AI 能力,巡检不再只是“看画面”,而是变成“智能决策系统”。 一、为什么 WebGIS + 无人机 + AI 是趋势? 在传统巡检场景中: * 电力巡检 → 人工拍照 * 工地巡查 → 人工记录 * 农业监测 → 靠经验判断 * 安防巡逻 → 事后回放 问题: * 数据无法实时分析 * 缺乏空间关联 * 没有智能预警能力 * 无法形成可视化决策系统 而结合: * WebGIS(三维可视化) * 无人机(数据采集) * AI(智能识别与分析) 我们可以构建: 一个真正的“空天地一体化智能巡检系统” 二、整体技术架构设计 1、系统分层架构 ┌──────────────────────────────┐ │ 前端可视化层 │ │ Cesium + Three.js + WebGL │ └──────────────┬───────────────┘ │ ┌──────────────▼───────────────┐ │ 业务中台层 │ │ AI推理