SenseVoice-Small语音识别Java集成实战:SpringBoot微服务调用案例

张开发
2026/4/13 11:27:22 15 分钟阅读

分享文章

SenseVoice-Small语音识别Java集成实战:SpringBoot微服务调用案例
SenseVoice-Small语音识别Java集成实战SpringBoot微服务调用案例语音识别技术正在从云端走向边缘从通用走向专用。对于很多Java开发者来说虽然Python在AI领域风头正劲但在企业级微服务架构中Java依然是核心。今天我们就来聊聊如何把一个轻量级但能力不俗的语音识别模型——SenseVoice-Small无缝集成到你的SpringBoot应用里。想象一下这样的场景你的智能客服系统需要实时处理用户上传的语音消息或者你的在线会议工具要自动生成会议纪要。如果每次都要把音频文件传到遥远的云端服务不仅延迟高成本也不可控。把模型部署在本地或私有环境自己掌控流程和数据是不是更踏实这篇文章我就带你走一遍从零到一的集成过程用Java把这件事搞定。1. 项目准备与环境搭建首先我们得把“舞台”搭好。这个项目本质上是一个标准的SpringBoot Web应用但需要一些额外的“零件”来处理AI模型。1.1 核心依赖配置打开你的pom.xml文件除了SpringBoot的基础依赖我们重点需要关注这几个库dependencies !-- SpringBoot Web 基础 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 异步处理提升并发能力 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency !-- 本地缓存用于存储识别结果 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-cache/artifactId /dependency dependency groupIdcom.github.ben-manes.caffeine/groupId artifactIdcaffeine/artifactId /dependency !-- 音频处理工具 -- dependency groupIdorg.apache.tika/groupId artifactIdtika-core/artifactId version2.9.1/version /dependency !-- ONNX Runtime 的 Java API -- !-- 这是最关键的一环负责加载和运行模型 -- dependency groupIdcom.microsoft.onnxruntime/groupId artifactIdonnxruntime/artifactId version1.17.0/version /dependency !-- 工具类方便处理JSON和日志 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里最核心的是onnxruntime依赖。SenseVoice-Small模型通常以ONNX格式提供这是一个开放的模型格式跨平台性能很好。ONNX Runtime就是它的“发动机”。1.2 模型文件准备接下来你需要获取SenseVoice-Small的模型文件通常是一个.onnx文件以及对应的词汇表文件.txt。你可以从模型的官方发布渠道获取。拿到文件后我建议在项目的resources目录下创建一个models文件夹把它们放进去。这样打包成JAR时模型也会被打包进去部署起来更方便。你的资源目录结构看起来应该是这样的src/main/resources/ ├── application.yml └── models/ ├── sensevoice-small.onnx └── vocab.txt2. 核心服务层构建环境搭好了我们来造“引擎”。这个引擎的核心是一个Spring管理的Bean它负责加载模型、管理会话、执行推理。2.1 模型加载与初始化我们创建一个VoiceRecognitionService类。它的初始化工作很关键主要是加载ONNX模型并创建推理会话。import ai.onnxruntime.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; Service Slf4j public class VoiceRecognitionService { private OrtEnvironment environment; private OrtSession session; private ListString vocabulary; Value(${voice.model.path:models/sensevoice-small.onnx}) private String modelPath; Value(${voice.vocab.path:models/vocab.txt}) private String vocabPath; PostConstruct public void init() throws OrtException, IOException { log.info(开始初始化语音识别模型...); // 1. 创建ONNX Runtime环境 environment OrtEnvironment.getEnvironment(); // 2. 从类路径加载模型文件 ClassPathResource modelResource new ClassPathResource(modelPath); Path modelFilePath Paths.get(modelResource.getURI()); OrtSession.SessionOptions sessionOptions new OrtSession.SessionOptions(); // 可以根据硬件情况设置优化选项例如使用CPU或GPU // sessionOptions.addCPU(false); // 使用CPU // 如果支持GPU可以启用CUDA // sessionOptions.addCUDA(0); session environment.createSession(modelFilePath.toString(), sessionOptions); // 3. 加载词汇表 loadVocabulary(); log.info(语音识别模型初始化完成。输入名称: {}, 输出名称: {}, session.getInputInfo().keySet(), session.getOutputInfo().keySet()); } private void loadVocabulary() throws IOException { ClassPathResource vocabResource new ClassPathResource(vocabPath); vocabulary new ArrayList(); try (Scanner scanner new Scanner(vocabResource.getInputStream())) { while (scanner.hasNextLine()) { vocabulary.add(scanner.nextLine().trim()); } } log.info(词汇表加载完成共 {} 个词元, vocabulary.size()); } }这段代码在Spring容器启动时PostConstruct会自动执行。它创建了与ONNX模型交互的会话OrtSession后续所有的识别请求都会通过这个会话来处理。2.2 音频预处理与推理模型加载好了但音频数据不能直接扔给模型。我们需要一个预处理步骤把常见的音频格式如MP3、WAV转换成模型能理解的数字矩阵通常是梅尔频谱图。这里我们简化处理假设输入已经是预处理好的特征数据例如通过其他服务处理后的float数组。在实际项目中你可能需要集成一个像javax.sound.sampled或FFmpeg包装库来完成音频解码和特征提取。public class VoiceRecognitionService { // ... 接上面的初始化代码 /** * 执行语音识别 * param audioFeatures 预处理后的音频特征数据例如梅尔频谱图 (float[][]) * return 识别出的文本 */ public String recognize(float[][] audioFeatures) throws OrtException { // 1. 将Java数组转换为ONNX Runtime期待的Tensor long[] shape {1, audioFeatures.length, audioFeatures[0].length}; // [batch, time, feature] OnnxTensor inputTensor OnnxTensor.createTensor(environment, audioFeatures, shape); // 2. 准备输入需要根据模型实际的输入名称来定 // 通常可以通过 session.getInputInfo() 查看 MapString, OnnxTensor inputs new HashMap(); inputs.put(input, inputTensor); // input 是示例需替换为实际名称 // 3. 执行模型推理 try (OrtSession.Result results session.run(inputs)) { // 4. 获取输出输出名称也需要根据模型确定 OnnxTensor outputTensor (OnnxTensor) results.get(logits); // 示例输出名 // 5. 后处理将模型输出的数字序列转换为文本 long[] outputArray (long[]) outputTensor.getValue(); return decodeText(outputArray); } finally { inputTensor.close(); } } private String decodeText(long[] tokenIds) { StringBuilder sb new StringBuilder(); for (long id : tokenIds) { if (id vocabulary.size()) { sb.append(vocabulary.get((int) id)).append( ); } } return sb.toString().trim(); } }这里的recognize方法是核心。它接收预处理好的特征转换成Tensor喂给模型拿到输出的token ID序列最后通过词汇表翻译成人类可读的文本。你需要根据SenseVoice-Small模型具体的输入输出张量名称来调整inputs.put和results.get中的键名。3. 构建RESTful API与控制层引擎准备好了现在要给它装上一个“控制面板”让外部能通过HTTP请求来使用它。我们将创建一个控制器提供文件上传和异步查询的接口。3.1 文件上传与异步处理接口考虑到语音识别可能比较耗时我们设计成异步模式用户上传音频接口立即返回一个任务ID用户再用这个ID来查询识别结果。import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; RestController RequestMapping(/api/voice) Slf4j public class VoiceRecognitionController { private final VoiceRecognitionService recognitionService; private final TaskCacheService taskCacheService; // 一个缓存任务结果的服务 // 用于模拟音频特征提取真实项目需替换 private float[][] extractFeatures(MultipartFile file) { // 此处应实现从音频文件提取特征如MFCC的逻辑 // 为简化示例返回模拟数据 log.info(处理音频文件: {}, 大小: {} bytes, file.getOriginalFilename(), file.getSize()); return new float[][]{/* 模拟特征数据 */}; } PostMapping(/recognize) public ApiResponseString uploadAndRecognize(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return ApiResponse.error(音频文件不能为空); } try { // 1. 生成唯一任务ID String taskId task_ System.currentTimeMillis() _ file.getOriginalFilename(); // 2. 提交异步任务 CompletableFuture.supplyAsync(() - { try { // 2.1 特征提取 (实际项目需完善) float[][] features extractFeatures(file); // 2.2 调用识别服务 return recognitionService.recognize(features); } catch (Exception e) { log.error(语音识别任务失败: {}, taskId, e); return 识别失败: e.getMessage(); } }).thenAccept(result - { // 3. 将结果存入缓存 taskCacheService.putResult(taskId, result); }); // 4. 立即返回任务ID return ApiResponse.success(任务已提交, taskId); } catch (Exception e) { log.error(处理上传请求失败, e); return ApiResponse.error(处理请求时发生错误); } } GetMapping(/result/{taskId}) public ApiResponseString getRecognitionResult(PathVariable String taskId) { String result taskCacheService.getResult(taskId); if (result null) { return ApiResponse.success(任务处理中或不存在, null); } return ApiResponse.success(识别成功, result); } // 简单的响应封装类 Data AllArgsConstructor NoArgsConstructor public static class ApiResponseT { private int code; private String message; private T data; public static T ApiResponseT success(String msg, T data) { return new ApiResponse(200, msg, data); } public static T ApiResponseT error(String msg) { return new ApiResponse(500, msg, null); } } }这个控制器提供了两个端点POST /api/voice/recognize: 上传音频文件返回任务ID。GET /api/voice/result/{taskId}: 根据任务ID查询识别结果。3.2 实现简单的任务缓存服务为了存储异步任务的结果我们需要一个简单的缓存服务。这里用Spring的Cacheable注解配合Caffeine实现。import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; Service CacheConfig(cacheNames voiceTaskCache) public class TaskCacheService { CachePut(key #taskId) public String putResult(String taskId, String result) { // 直接由缓存管理器存储这里只需返回结果 return result; } Cacheable(key #taskId, unless #result null) public String getResult(String taskId) { // 如果缓存中没有返回null return null; } }然后在application.yml里配置一下Caffeine缓存spring: cache: type: caffeine caffeine: spec: maximumSize1000, expireAfterWrite10m # 最多缓存1000个任务10分钟过期4. 性能优化与生产建议代码跑起来只是第一步要真正用到生产环境还得考虑性能和稳定性。这里分享几个我觉得比较重要的点。模型推理优化ONNX Runtime提供了很多会话选项。如果你有GPU一定要启用CUDA支持速度提升是数量级的。即使只用CPU也可以尝试设置线程数sessionOptions.setInterOpNumThreads()和sessionOptions.setIntraOpNumThreads()来充分利用多核。音频预处理流水线特征提取比如计算MFCC可能比模型推理本身还慢。考虑将这部分也异步化或者使用更高效的本地库比如通过JNI调用用C优化过的代码。对于固定采样率的音频可以预先计算好一些参数。连接池与异步化如果你的服务调用量很大确保HTTP客户端如你服务内部调用其他服务使用了连接池。整个处理链路从文件接收到特征提取再到模型推理都可以用CompletableFuture链式调用避免阻塞Web容器线程。监控与降级给识别服务加上监控比如记录每次识别的耗时、成功率。当服务压力过大时可以考虑降级策略例如对过长的音频只识别前N秒或者返回一个“服务繁忙请稍后再试”的提示。关于模型本身SenseVoice-Small作为一个轻量级模型在通用场景下效果不错但对于强口音、专业术语或嘈杂环境识别率可能会下降。如果是关键业务可以结合规则后处理比如纠错词表或者设计一个“人工审核”的流程作为备份。5. 总结走完这一趟你会发现用Java在SpringBoot里集成一个ONNX语音识别模型并没有想象中那么复杂。核心就是三件事用ONNX Runtime加载模型、写好预处理和后处理的逻辑、提供一个稳定可靠的API。这种方案把AI能力变成了你微服务集群里的一个普通服务数据不出私域延迟可控扩容也方便。实际集成时你可能需要花些时间调试音频预处理环节确保输入模型的张量形状和数据类型完全匹配。另一个重点是错误处理网络IO、文件解析、模型推理每一步都可能出错做好日志和异常捕获服务才能健壮。代码里我留了一些待完善的地方比如真正的音频特征提取。你可以根据SenseVoice-Small模型要求的输入格式去找一个合适的Java音频处理库来补全它。这条路跑通之后不仅可以做语音识别其他ONNX格式的视觉、NLP模型都可以用类似的模式集成进来让你用Java技术栈也能轻松玩转AI应用。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章