SenseVoice-Small语音模型Java集成实战:SpringBoot服务端调用ONNX Runtime 📅 发布时间:2026/7/7 12:26:14 👁️ 浏览次数: SenseVoice-Small语音模型Java集成实战SpringBoot服务端调用ONNX Runtime语音识别正在从消费级应用快速渗透到企业级服务中。无论是客服中心的通话质检、在线会议的实时字幕还是智能硬件中的语音指令都需要一个稳定、高效且易于集成的后端方案。对于Java技术栈的团队来说直接在熟悉的SpringBoot环境中集成AI模型往往比引入一套全新的Python服务更实际。SenseVoice-Small作为一个轻量级、高性能的语音识别模型其ONNX格式为跨语言部署提供了便利。今天我们就来聊聊如何在一个标准的SpringBoot微服务里把它跑起来并封装成一套能扛住压力的RESTful API。整个过程我会尽量避开那些让人头疼的配置用最直白的方式讲清楚。1. 项目准备与环境搭建在开始写代码之前我们需要把“舞台”搭好。这里没有复杂的Python虚拟环境核心就是让Java能通过ONNX Runtime去调用那个模型文件。首先创建一个最普通的SpringBoot项目用Maven或者Gradle都可以。我习惯用Maven所以在pom.xml里关键是要引入ONNX Runtime的Java依赖。dependencies !-- SpringBoot基础依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- ONNX Runtime for Java -- dependency groupIdcom.microsoft.onnxruntime/groupId artifactIdonnxruntime/artifactId version1.16.3/version !-- 请使用最新稳定版 -- /dependency !-- 数据库相关用于结果持久化 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 音频处理用于解码MP3/WAV等 -- dependency groupIdorg.apache.tika/groupId artifactIdtika-core/artifactId version2.9.1/version /dependency /dependencies这里有个小坑需要注意onnxruntime这个JAR包本身不包含本地库Native Library。所以你需要根据你的部署环境Linux/Windows/macOS去ONNX Runtime的GitHub Release页面下载对应版本的预构建包把里面的动态链接库比如Linux的libonnxruntime.so放到Java库路径下或者通过启动参数-Djava.library.path指定其位置。这是Java通过JNI调用C库的标准操作。接下来去SenseVoice的官方仓库找到SenseVoice-Small模型的ONNX格式文件通常以.onnx结尾把它放到项目的资源目录里比如src/main/resources/models/sensevoice-small.onnx。这样打包后模型文件就在Jar包内部了。2. 核心推理服务封装环境准备好后我们来创建最核心的类——语音识别服务。这个服务要干几件事加载模型、预处理音频、执行推理、后处理文本。2.1 模型加载与会话管理我们先创建一个VoiceRecognitionService在Spring容器启动时就把模型加载好。import ai.onnxruntime.*; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.nio.file.Path; import java.nio.file.Paths; Service Slf4j public class VoiceRecognitionService { private OrtEnvironment environment; private OrtSession session; PostConstruct public void init() throws OrtException { log.info(正在初始化ONNX Runtime环境...); environment OrtEnvironment.getEnvironment(); // 从classpath加载模型文件 ClassPathResource modelResource new ClassPathResource(models/sensevoice-small.onnx); try { Path modelPath Paths.get(modelResource.getURI()); OrtSession.SessionOptions sessionOptions new OrtSession.SessionOptions(); // 根据需求设置选项比如使用CPU还是GPU // sessionOptions.addCUDA(0); // 如果启用GPU session environment.createSession(modelPath.toString(), sessionOptions); log.info(SenseVoice-Small ONNX模型加载成功。输入信息: {}, session.getInputInfo()); } catch (Exception e) { log.error(模型加载失败, e); throw new RuntimeException(初始化语音识别模型失败, e); } } // 提供一个简单的识别方法后续会完善 public String recognize(byte[] audioData) { // 这里先留空后面填充具体逻辑 return 识别结果待实现; } PreDestroy public void cleanup() { try { if (session ! null) { session.close(); } if (environment ! null) { environment.close(); } log.info(ONNX Runtime资源已释放。); } catch (Exception e) { log.error(资源释放异常, e); } } }这段代码在服务启动时自动加载模型关闭时清理资源符合Spring Bean的生命周期管理。OrtSession就是模型在内存中的代表后续的推理都通过它进行。2.2 音频预处理与推理执行SenseVoice模型对输入有特定要求通常是特定采样率如16kHz的单声道浮点数组。我们需要将上传的音频文件如MP3、WAV转换成模型期望的格式。import javax.sound.sampled.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; public class AudioPreprocessor { /** * 将音频字节数据转换为模型需要的浮点数组。 * 这是一个简化示例实际处理可能需要更复杂的重采样和归一化。 */ public static float[] preprocessAudio(byte[] audioBytes, String contentType) throws IOException, UnsupportedAudioFileException { // 使用Java Sound API或第三方库如Tika解码音频 AudioInputStream audioStream AudioSystem.getAudioInputStream(new ByteArrayInputStream(audioBytes)); AudioFormat format audioStream.getFormat(); // 检查并转换为目标格式16kHz, 单声道, PCM_SIGNED AudioFormat targetFormat new AudioFormat(16000, 16, 1, true, false); if (!format.matches(targetFormat)) { audioStream AudioSystem.getAudioInputStream(targetFormat, audioStream); } // 读取音频数据到字节数组 byte[] rawData audioStream.readAllBytes(); // 将16位PCM字节转换为浮点数组-1.0 到 1.0 float[] floatSamples new float[rawData.length / 2]; for (int i 0; i floatSamples.length; i) { short sample (short) ((rawData[2*i1] 8) | (rawData[2*i] 0xFF)); floatSamples[i] sample / 32768.0f; } audioStream.close(); return floatSamples; } }有了预处理后的浮点数组就可以填充到VoiceRecognitionService的recognize方法中了。// 在VoiceRecognitionService类中完善recognize方法 public String recognize(float[] audioSamples) throws OrtException { // 1. 将浮点数组包装成Tensor long[] shape {1, audioSamples.length}; // 假设模型输入shape为 [batch, sequence] OnnxTensor audioTensor OnnxTensor.createTensor(environment, FloatBuffer.wrap(audioSamples), shape); // 2. 准备输入Map键名需要与模型输入节点名称匹配 MapString, OnnxTensor inputs new HashMap(); inputs.put(audio_input, audioTensor); // audio_input需根据实际模型修改 // 3. 执行推理 try (OrtSession.Result results session.run(inputs)) { // 4. 获取输出输出节点名称也需根据模型修改 OnnxTensor outputTensor (OnnxTensor) results.get(text_output); float[][] outputArray (float[][]) outputTensor.getValue(); // 5. 后处理将模型输出的logits或token ids转换为字符串 // 这里需要根据SenseVoice的具体输出格式编写解码逻辑 String recognizedText decodeOutput(outputArray[0]); // 假设batch1 return recognizedText; } finally { audioTensor.close(); } } // 一个简单的占位解码函数实际需要词汇表映射 private String decodeOutput(float[] tokens) { // 简化处理这里应该是将token ids转换为汉字/单词的过程 // 实际项目中你需要加载模型的词汇表文件来实现 StringBuilder sb new StringBuilder(); // ... 解码逻辑 ... return sb.toString(); }音频预处理和模型推理的流程就走通了。当然实际的解码decodeOutput部分需要结合SenseVoice模型的词表vocab.txt来实现将模型输出的ID序列转换成可读的文本。3. 构建高并发RESTful API单个请求处理没问题了但企业级应用面临的是并发请求。我们不能让每个HTTP请求都同步等待漫长的语音识别过程那样会很快耗尽Tomcat线程。3.1 异步处理与线程池设计我们的策略是API接口快速接收请求将耗时的识别任务提交给后台线程池处理并通过异步机制返回结果。import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; EnableAsync Component public class AsyncRecognitionHandler { private final VoiceRecognitionService recognitionService; private final Executor taskExecutor; // 自定义线程池 public AsyncRecognitionHandler(VoiceRecognitionService recognitionService) { this.recognitionService recognitionService; // 配置一个专用于语音识别的线程池 this.taskExecutor new ThreadPoolTaskExecutor(); // 实际使用需配置参数 } Async(taskExecutor) public CompletableFutureString handleRecognitionAsync(float[] audioSamples) { try { String text recognitionService.recognize(audioSamples); return CompletableFuture.completedFuture(text); } catch (Exception e) { CompletableFutureString future new CompletableFuture(); future.completeExceptionally(e); return future; } } }然后我们创建一个ThreadPoolTaskExecutor的配置Bean隔离语音识别任务避免影响Web容器的其他业务。import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; Configuration public class AsyncConfig { Bean(name recognitionTaskExecutor) public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); // 核心线程数根据CPU核心数调整 executor.setMaxPoolSize(10); // 最大线程数 executor.setQueueCapacity(100); // 队列容量 executor.setThreadNamePrefix(voice-recog-); executor.initialize(); return executor; } }3.2 控制器与文件上传现在创建REST控制器来暴露API。我们设计两个端点一个用于同步测试一个用于异步提交任务并返回任务ID。import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; RestController RequestMapping(/api/v1/voice) public class VoiceRecognitionController { private final AsyncRecognitionHandler asyncHandler; private final VoiceRecognitionService syncService; // 用于简单同步测试 private final ConcurrentHashMapString, CompletableFutureString taskMap new ConcurrentHashMap(); public VoiceRecognitionController(AsyncRecognitionHandler asyncHandler, VoiceRecognitionService syncService) { this.asyncHandler asyncHandler; this.syncService syncService; } /** * 同步识别接口仅用于测试或小文件 */ PostMapping(/recognize/sync) public ApiResponseString recognizeSync(RequestParam(file) MultipartFile file) { try { float[] samples AudioPreprocessor.preprocessAudio(file.getBytes(), file.getContentType()); String text syncService.recognize(samples); return ApiResponse.success(text); } catch (Exception e) { return ApiResponse.error(识别失败: e.getMessage()); } } /** * 异步识别接口生产环境推荐 */ PostMapping(/recognize/async) public ApiResponseString recognizeAsync(RequestParam(file) MultipartFile file) { try { float[] samples AudioPreprocessor.preprocessAudio(file.getBytes(), file.getContentType()); String taskId UUID.randomUUID().toString(); CompletableFutureString future asyncHandler.handleRecognitionAsync(samples); taskMap.put(taskId, future); // 立即返回任务ID客户端可凭此查询结果 return ApiResponse.success(taskId); } catch (Exception e) { return ApiResponse.error(任务提交失败: e.getMessage()); } } /** * 查询异步任务结果 */ GetMapping(/task/{taskId}) public ApiResponseString getTaskResult(PathVariable String taskId) { CompletableFutureString future taskMap.get(taskId); if (future null) { return ApiResponse.error(任务不存在或已过期); } if (future.isDone()) { try { String result future.get(); taskMap.remove(taskId); // 清理已完成任务 return ApiResponse.success(result); } catch (Exception e) { return ApiResponse.error(任务执行出错: e.getMessage()); } } else { return ApiResponse.success(null, 任务处理中); } } } // 简单的统一响应封装 class ApiResponseT { private int code; private String msg; private T data; // 构造方法、getter/setter、success/error静态方法省略... }这样一个支持文件上传、异步处理、结果查询的语音识别API骨架就完成了。前端可以先调用/api/v1/voice/recognize/async提交音频拿到taskId然后轮询/api/v1/voice/task/{taskId}获取最终结果。4. 识别结果持久化与业务集成识别出来的文本通常需要存下来供后续分析、检索或展示。我们结合Spring Data JPA将结果存入MySQL。4.1 数据实体与存储定义一个实体类记录每次识别的元数据和结果。import javax.persistence.*; import java.time.LocalDateTime; Entity Table(name voice_recognition_task) public class RecognitionTask { Id private String taskId; Lob Column(columnDefinition TEXT) private String originalFileName; Lob Column(columnDefinition TEXT) private String recognizedText; private String status; // PENDING, PROCESSING, SUCCESS, FAILED private LocalDateTime submitTime; private LocalDateTime finishTime; private Long durationMs; // 处理耗时 // 省略构造方法、getter/setter... }创建对应的Repository接口。import org.springframework.data.jpa.repository.JpaRepository; public interface RecognitionTaskRepository extends JpaRepositoryRecognitionTask, String { }然后修改我们的异步处理器在任务开始、成功、失败时更新数据库状态。// 在AsyncRecognitionHandler中注入Repository private final RecognitionTaskRepository taskRepository; Async(recognitionTaskExecutor) public CompletableFutureString handleRecognitionAsync(String taskId, float[] audioSamples) { // 1. 更新任务状态为PROCESSING RecognitionTask task taskRepository.findById(taskId).orElseThrow(); task.setStatus(PROCESSING); taskRepository.save(task); long startTime System.currentTimeMillis(); try { // 2. 执行识别 String text recognitionService.recognize(audioSamples); long endTime System.currentTimeMillis(); // 3. 更新任务为SUCCESS保存结果 task.setStatus(SUCCESS); task.setRecognizedText(text); task.setFinishTime(LocalDateTime.now()); task.setDurationMs(endTime - startTime); taskRepository.save(task); return CompletableFuture.completedFuture(text); } catch (Exception e) { // 4. 更新任务为FAILED task.setStatus(FAILED); taskRepository.save(task); CompletableFutureString future new CompletableFuture(); future.completeExceptionally(e); return future; } }控制器在提交异步任务时需要先在数据库创建一条PENDING状态的记录再将taskId返回给客户端。4.2 扩展思考性能与优化在实际高并发场景下还有几个点可以考虑音频文件存储上传的音频文件本身可能很大不应直接进入数据库。可以存到对象存储如MinIO、S3或文件服务器数据库中只存URL。模型批处理ONNX Runtime支持批量推理。可以设计一个队列累积一定数量的音频样本后一次性送入模型能显著提升GPU利用率如果使用GPU。结果缓存对于相同的音频文件可通过MD5判断可以直接返回缓存中的识别结果避免重复计算。监控与告警集成Micrometer等监控工具收集识别耗时、成功率、线程池状态等指标。5. 总结走完这一趟你会发现在SpringBoot里集成一个ONNX格式的AI模型并没有想象中那么复杂。核心就是三件事用正确的姿势加载模型和本地库、把业务数据音频转换成模型能吃的格式、设计好异步流程别让服务被堵死。这套方案的好处是整个技术栈是统一的Java生态你的团队不需要额外维护Python服务调试、部署、监控都走现有的成熟流程。SenseVoice-Small模型本身比较轻量在CPU上也能跑出不错的速度对于很多企业级并发量来说已经够用。当然每个实际项目都有自己的特殊需求。你可能需要处理更复杂的音频流、集成更专业的解码器、或者对识别结果进行二次加工。但有了这个基础框架后续的扩展就都有了落脚点。建议你先跑通这个最小可行版本感受一下从上传音频到看到文字结果的完整链路然后再根据实际情况一步步添砖加瓦。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
OneAPI效果展示:Gemini与Claude在复杂推理任务中的表现对比 OneAPI效果展示:Gemini与Claude在复杂推理任务中的表现对比 1. 引言:统一API访问的便利性 在人工智能快速发展的今天,各种大语言模型如雨后春笋般涌现。每个模型都有自己的API接口、调用方式和认证机制,这让开发者在实际应用中面… 2026/7/5 15:18:48
PP-DocLayoutV3可部署方案:无需Python环境,Docker镜像开箱即用 PP-DocLayoutV3可部署方案:无需Python环境,Docker镜像开箱即用 你是不是经常需要处理扫描的合同、论文或者发票?面对一堆图片文档,想快速把里面的文字、表格、图片位置都找出来,是不是觉得特别麻烦?手动去… 2026/7/6 3:55:50
CPU友好型AI:DeepSeek-R1 (1.5B) 极速推理体验分享 CPU友好型AI:DeepSeek-R1 (1.5B) 极速推理体验分享 在AI模型动辄需要高端GPU才能流畅运行的时代,一个能在普通电脑CPU上跑得飞快的智能模型,听起来是不是有点不可思议?最近我深度体验了DeepSeek-R1-Distill-Qwen-1.5B这个“小身材… 2026/7/3 15:29:31
uboot启动内核学习笔记 图:笔记内容结构概览,包含多个技术主题的层次化要点整理。 一、Flash 知识点补充 1. Flash 定义 是断电不会丢失数据的存储芯片,用来存放系统、程序、配置信息。 2. Flash 分类 NOR Flash:可在芯片上直接执行代码,… 2026/7/8 8:03:31
2026适合一人创业的AI工具盘点:快速做应用原型、零代码AI开发平台推荐 一人创业的时代,正在加速到来。据鸿鹄汇发布的《2026一人公司洞察报告》,一人公司每投入1元AI成本,即可等效替代约72元的开发人力支出。工信部《2026小微主体生存白皮书》也指出,92%的高盈利“一人公司”深度应用AI工具。超过57%的… 2026/7/8 8:03:31
养鸡行业尼卡巴嗪残留风险防控科普指南 在规模化养鸡行业,尼卡巴嗪是应用广泛的经典抗球虫药物,药性稳定、耐药性产生慢,在肉鸡、土鸡、火鸡的球虫病防控中被普遍使用。但不少养殖从业者未意识到,该药物目前已是生鲜鸡肉国家抽检的高频不合格项目,很多从业者… 2026/7/8 8:03:31
暗黑破坏神2存档编辑器:3分钟学会自定义你的游戏体验 暗黑破坏神2存档编辑器:3分钟学会自定义你的游戏体验 【免费下载链接】diablo_edit Diablo II Character editor. 项目地址: https://gitcode.com/gh_mirrors/di/diablo_edit 你是否曾经在暗黑破坏神2的世界里花费数小时只为寻找一件特定装备?或者… 2026/7/8 8:01:31
人类动作到机器人执行的语义对齐方法 1. 这不是“让机器人看懂视频”,而是重建人类动作与机器执行之间的语义桥梁“人类-机器人视频配对与跨模态动作对齐”——这个标题乍看像论文摘要,但实际落地时,它直指当前具身智能最硬的那块骨头:我们能轻松看懂别人抬手、转身、… 2026/7/8 8:01:31
8大主流网盘直链解析神器:告别龟速下载的终极方案 8大主流网盘直链解析神器:告别龟速下载的终极方案 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 ,支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天翼云盘… 2026/7/8 7:59:29
BetterNCM安装器:高效管理网易云插件的最佳选择 BetterNCM安装器:高效管理网易云插件的最佳选择 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer 还在为网易云音乐插件的繁琐安装流程而烦恼吗?BetterNCM安装器是… 2026/7/8 0:02:48
运动控制系统安全设置对比:ECI3808的3种限位保护与急停逻辑实现 运动控制系统安全机制深度解析:限位保护与急停逻辑的设计哲学在精密制造与自动化领域,运动控制系统的安全设计绝非简单的功能堆砌,而是一套融合了机械工程、电气原理和软件算法的防御体系。当一台数控机床以每分钟数万转的速度运转࿰… 2026/7/8 0:06:48
AI大模型应用开发:小白也能抓住的红利风口,收藏这篇入门指南! 文章指出,虽然微软等科技巨头在裁员,但英伟达等公司却在积极扩招AI相关人才,尤其是具身智能、仿真等领域。AI行业正在经历结构性调整,传统岗位被淘汰,而大模型应用开发等新岗位需求旺盛。对于想转行或学习AI的普通人来… 2026/7/8 0:10:49
6个月转型AI工程师:实战路径与核心技能 1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公… 2026/7/7 11:26:57
TPAFE0808与PIC18F87K22的多通道信号采集方案 1. 项目背景与核心需求在工业自动化、医疗设备和科研仪器等领域,多通道信号采集与系统监测是基础且关键的技术需求。传统方案往往面临通道数量不足、信号调理复杂、系统集成度低等问题。TPAFE0808作为一款8通道模拟前端芯片,与PIC18F87K22微控制器的组合… 2026/7/7 11:26:57
STC3115与PIC18LF26K80构建高精度电池管理系统 1. STC3115与PIC18LF26K80在电池管理系统中的核心价值在现代电子设备中,电池管理系统(BMS)的重要性不亚于设备的核心处理器。STC3115作为一款高精度电池电量监测IC,与PIC18LF26K80微控制器的组合,构成了一个既能精确监控又能智能管理的完整解… 2026/7/7 11:26:58