
SenseVoice-Small模型微信小程序开发实战实现录音即时转文字功能不知道你有没有遇到过这样的场景开会时想快速记录要点但打字跟不上说话的速度或者灵感突然涌现想用语音记录下来回头再整理成文字。这时候一个能实时把语音转成文字的小工具就显得特别方便。今天我们就来聊聊怎么在微信小程序里自己动手实现一个这样的功能。你不用依赖那些收费的第三方服务而是用开源的SenseVoice-Small模型搭建一个属于自己的录音转文字工具。整个过程就像搭积木前端用小程序自带的录音能力后端部署一个轻量级的语音识别模型再把它们连接起来。我会把每一步都拆开讲清楚包括怎么处理音频格式、怎么把大段录音切成小块上传、怎么让用户看到实时的转写进度最终做出一个体验流畅的小程序。1. 为什么选择SenseVoice-Small与微信小程序在开始动手之前你可能会有疑问语音识别的方案那么多为什么偏偏是SenseVoice-Small和微信小程序这个组合首先说说SenseVoice-Small这个模型。它最大的特点就是“小”这里的“小”指的是模型参数量相对较少对计算资源的要求不高。这意味着你可以在普通的云服务器甚至性能好一点的个人电脑上把它跑起来部署成本很低。别看它“小”它在中文普通话的识别准确率上表现相当不错对于日常对话、会议记录这类场景完全够用。它就像一个专注做好一件事的能手不追求大而全但在特定任务上效率很高。然后是微信小程序。它的优势在于触达用户极其方便。用户不用下载安装新的App在微信里搜索打开就能用用完即走。小程序提供了完善的录音API可以很方便地获取到用户的语音输入。把这两者结合起来你就能快速做出一个轻量、实用、且完全可控的语音转文字工具。你可以根据自己的需求定制功能比如只转写特定时长、保存历史记录、或者导出为特定格式而不用受限于公共平台的各种规则和限制。2. 搭建你的语音识别后端服务后端是我们的“大脑”负责接收前端送来的音频调用模型进行识别再把文字结果返回去。我们分几步来搭建它。2.1 环境准备与模型部署首先你需要一台有公网IP的服务器配置不用太高2核4G的云服务器就足够跑起SenseVoice-Small了。操作系统推荐使用Ubuntu 20.04或22.04。登录服务器后我们先把必要的环境装好。主要是Python和深度学习框架。这里我推荐使用Conda来管理环境可以避免版本冲突。# 更新系统包 sudo apt update sudo apt upgrade -y # 安装Miniconda如果尚未安装 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh # 按照提示安装安装完成后重启终端或执行 source ~/.bashrc # 创建并激活一个专门的Python环境 conda create -n sensevoice python3.9 conda activate sensevoice # 安装PyTorch请根据你的CUDA版本选择对应命令若无GPU则安装CPU版本 # 例如对于CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # CPU版本 # pip install torch torchvision torchaudio # 安装其他依赖如 transformers, soundfile, flask用于创建Web API pip install transformers soundfile flask flask-cors环境准备好后我们来获取并加载SenseVoice-Small模型。你可以从Hugging Face Model Hub上找到它。我们写一个简单的Python脚本来测试模型是否能正常工作。# test_model.py from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor import torch # 指定模型名称 model_id fun-audio/SenseVoice-Small # 加载模型和处理器 print(正在加载模型和处理器首次运行需要下载请耐心等待...) model AutoModelForSpeechSeq2Seq.from_pretrained(model_id) processor AutoProcessor.from_pretrained(model_id) # 将模型设置为评估模式 model.eval() print(模型加载成功) # 注意这里只是加载实际推理需要配合音频输入我们下一步会做。运行这个脚本如果一切顺利模型就准备就绪了。接下来我们要创建一个Web API让小程序能够调用它。2.2 创建Flask API服务我们使用Flask这个轻量级的Web框架来创建API。主要会创建两个接口一个用于健康检查另一个用于接收音频并进行转写。# app.py from flask import Flask, request, jsonify from flask_cors import CORS import torch import soundfile as sf import io import numpy as np from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor import logging app Flask(__name__) # 允许跨域请求方便小程序调用 CORS(app) # 配置日志 logging.basicConfig(levellogging.INFO) # 全局加载模型和处理器在实际生产中应考虑懒加载或使用模型服务 model_id fun-audio/SenseVoice-Small device cuda:0 if torch.cuda.is_available() else cpu torch_dtype torch.float16 if torch.cuda.is_available() else torch.float32 model None processor None def load_model(): 加载语音识别模型 global model, processor if model is None or processor is None: logging.info(f正在加载模型到设备: {device}) model AutoModelForSpeechSeq2Seq.from_pretrained( model_id, torch_dtypetorch_dtype, low_cpu_mem_usageTrue, use_safetensorsTrue ) model.to(device) processor AutoProcessor.from_pretrained(model_id) model.eval() logging.info(模型加载完毕。) return model, processor app.route(/health, methods[GET]) def health_check(): 健康检查接口 return jsonify({status: healthy, message: SenseVoice service is running.}) app.route(/transcribe, methods[POST]) def transcribe_audio(): 音频转写接口 try: # 检查是否有文件上传 if audio not in request.files: return jsonify({error: No audio file provided}), 400 audio_file request.files[audio] # 检查文件格式小程序上传的是临时路径或buffer我们支持常见格式 if audio_file.filename : return jsonify({error: Empty filename}), 400 # 读取音频数据 audio_bytes audio_file.read() # 使用soundfile读取音频字节流假设为wav格式。小程序端需确保格式。 # 这里使用io.BytesIO将字节转换为文件类对象 audio_data, sample_rate sf.read(io.BytesIO(audio_bytes)) # 加载模型 model, processor load_model() # 预处理音频确保采样率为16kHzSenseVoice模型期望的输入 if sample_rate ! 16000: # 此处简化处理实际应用中应使用librosa或torchaudio进行重采样 import librosa audio_data librosa.resample(audio_data, orig_srsample_rate, target_sr16000) sample_rate 16000 # 准备模型输入 inputs processor(audio_data, sampling_ratesample_rate, return_tensorspt) inputs inputs.to(device, dtypetorch_dtype) # 执行推理 with torch.no_grad(): generated_ids model.generate(**inputs, max_length448) # 解码识别结果 transcription processor.batch_decode(generated_ids, skip_special_tokensTrue)[0] logging.info(f识别成功: {transcription[:50]}...) # 日志只打印前50字符 return jsonify({text: transcription}) except Exception as e: logging.error(f转写过程中发生错误: {str(e)}) return jsonify({error: str(e)}), 500 if __name__ __main__: # 在启动时预加载模型 load_model() # 运行服务host0.0.0.0允许外部访问debug模式仅用于开发 app.run(host0.0.0.0, port5000, debugFalse)将这段代码保存为app.py然后在服务器上运行python app.py。你的后端API服务就在本地的5000端口启动了。为了让小程序能访问到你还需要配置服务器的安全组开放5000端口并且可以考虑使用Nginx做反向代理用域名访问会更规范。3. 微信小程序前端开发详解后端跑起来之后我们就要来打造用户直接接触的小程序界面了。核心任务就是录音、上传、并展示结果。3.1 项目初始化与页面布局首先在微信开发者工具中创建一个新的小程序项目。在app.json中配置好必要的权限特别是录音权限。// app.json { pages: [ pages/index/index ], window: { backgroundTextStyle: light, navigationBarBackgroundColor: #fff, navigationBarTitleText: 语音速记, navigationBarTextStyle: black }, requiredPrivateInfos: [ getRecorderManager ], permission: { scope.record: { desc: 需要获取您的录音用于语音转文字 } }, style: v2, sitemapLocation: sitemap.json }接着我们来设计主页面index.wxml。界面要简洁直观一个大的按钮用于控制录音一个区域用来显示实时转写的文字再加上一个历史记录列表。!-- pages/index/index.wxml -- view classcontainer view classheader text classtitle语音速记/text text classsubtitle轻按录音松开转写/text /view !-- 录音按钮 -- view classrecorder-section button classrecord-btn {{isRecording ? recording : }} bind:touchstartstartRecording bind:touchendstopRecording hover-classbtn-hover text classbtn-icon{{isRecording ? ■ : ●}}/text text classbtn-text{{isRecording ? 录音中... : 按住录音}}/text /button text classtimer wx:if{{isRecording}}时长: {{recordDuration}}s/text /view !-- 实时转写结果 -- view classresult-section wx:if{{transcriptionText}} view classsection-title text转写结果/text button classcopy-btn sizemini bindtapcopyText复制/button /view scroll-view classresult-text scroll-y text{{transcriptionText}}/text /scroll-view /view !-- 历史记录 -- view classhistory-section wx:if{{historyList.length 0}} view classsection-title text历史记录/text button classclear-btn sizemini bindtapclearHistory清空/button /view view classhistory-list block wx:for{{historyList}} wx:keytimestamp view classhistory-item text classhistory-time{{item.time}}/text text classhistory-content{{item.text}}/text /view /block /view /view !-- 状态提示 -- view classstatus-toast wx:if{{statusMessage}} text{{statusMessage}}/text /view /view相应的样式index.wxss可以让界面看起来更舒服这里就不展开全部代码了主要是设置按钮、文本区域的样式让布局清晰美观。3.2 实现核心录音与上传逻辑前端的核心在index.js的代码里。我们需要管理录音、处理音频、并与后端通信。// pages/index/index.js const app getApp() // 请替换为你的后端API地址 const API_BASE_URL https://your-server-domain.com:5000; Page({ data: { isRecording: false, recordDuration: 0, timer: null, recorderManager: null, transcriptionText: , historyList: [], statusMessage: }, onLoad: function() { // 初始化录音管理器 const recorderManager wx.getRecorderManager(); this.setData({ recorderManager }); // 监听录音开始事件 recorderManager.onStart(() { console.log(录音开始); this.startTimer(); }); // 监听录音结束事件拿到临时文件路径 recorderManager.onStop((res) { console.log(录音结束, res); const { tempFilePath, duration } res; this.stopTimer(); // 显示“处理中”状态 this.showStatus(正在转写中...); // 上传音频文件进行转写 this.uploadAndTranscribe(tempFilePath, duration); }); // 监听录音错误 recorderManager.onError((err) { console.error(录音失败:, err); this.showStatus(录音失败请重试); this.setData({ isRecording: false }); this.stopTimer(); }); // 加载本地历史记录 this.loadHistory(); }, // 开始录音 startRecording: function(e) { this.setData({ isRecording: true, transcriptionText: }); const { recorderManager } this.data; recorderManager.start({ duration: 60000, // 最长60秒可根据需要调整 sampleRate: 16000, // 采样率与模型匹配 numberOfChannels: 1, // 单声道 encodeBitRate: 48000, // 编码码率 format: wav, // 格式后端期望wav frameSize: 50 // 指定帧大小越小实时性越高但数据量越大 }); }, // 停止录音 stopRecording: function() { if (this.data.isRecording) { this.data.recorderManager.stop(); this.setData({ isRecording: false }); } }, // 计时器 startTimer: function() { this.setData({ recordDuration: 0 }); const timer setInterval(() { this.setData({ recordDuration: this.data.recordDuration 1 }); }, 1000); this.setData({ timer }); }, stopTimer: function() { if (this.data.timer) { clearInterval(this.data.timer); this.setData({ timer: null }); } }, // 上传音频并转写 uploadAndTranscribe: function(tempFilePath, duration) { const that this; wx.uploadFile({ url: ${API_BASE_URL}/transcribe, // 你的后端转写接口 filePath: tempFilePath, name: audio, // 与后端接口定义的字段名一致 formData: { duration: duration }, success(res) { if (res.statusCode 200) { const data JSON.parse(res.data); if (data.text) { const transcription data.text; that.setData({ transcriptionText: transcription }); that.showStatus(转写完成); // 保存到历史记录 that.saveToHistory(transcription); } else { that.showStatus(转写失败未返回文本); console.error(API返回错误:, data); } } else { that.showStatus(网络请求失败); console.error(上传失败:, res); } }, fail(err) { that.showStatus(上传音频失败); console.error(上传文件失败:, err); } }); }, // 保存到历史记录 saveToHistory: function(text) { const historyList this.data.historyList; const now new Date(); const timeStr ${now.getHours().toString().padStart(2, 0)}:${now.getMinutes().toString().padStart(2, 0)}; historyList.unshift({ text: text.length 50 ? text.substring(0, 50) ... : text, // 列表显示缩略 fullText: text, time: timeStr, timestamp: now.getTime() }); // 只保留最近20条 if (historyList.length 20) { historyList.pop(); } this.setData({ historyList }); // 同步存储到本地 wx.setStorageSync(transcription_history, historyList); }, // 加载历史记录 loadHistory: function() { const history wx.getStorageSync(transcription_history) || []; this.setData({ historyList: history }); }, // 清空历史 clearHistory: function() { wx.showModal({ title: 确认清空, content: 确定要清空所有历史记录吗, success: (res) { if (res.confirm) { this.setData({ historyList: [] }); wx.removeStorageSync(transcription_history); wx.showToast({ title: 已清空, icon: success }); } } }); }, // 复制文本 copyText: function() { const text this.data.transcriptionText; wx.setClipboardData({ data: text, success: () { wx.showToast({ title: 已复制到剪贴板, icon: success }); } }); }, // 显示状态提示 showStatus: function(msg) { this.setData({ statusMessage: msg }); // 3秒后自动清除 setTimeout(() { if (this.data.statusMessage msg) { this.setData({ statusMessage: }); } }, 3000); } })3.3 优化体验分片上传与实时反馈上面的代码实现了基础功能但用户体验还有提升空间。比如录音时间长了文件会很大上传和转写等待时间也长。我们可以实现“分片上传”即一边录音一边将已录好的部分上传到后端后端也进行流式识别这样用户就能近乎实时地看到文字逐句出现。这涉及到前后端更复杂的协作前端需要定时比如每3秒获取录音文件并上传一个片段后端则需要一个支持“长语音”或“流式”识别的接口。SenseVoice模型本身支持一定长度的音频对于更长的音频可以考虑使用专门的“长语音识别”模型或在后端对接支持流式的ASR服务。此外还可以增加一些细节优化比如视觉反馈录音时按钮有呼吸灯效果显示音量波动。网络状态处理检查网络状况弱网时提示用户。音频播放允许用户点击历史记录回听当时的录音需要保存音频文件。编辑功能允许用户对转写结果进行简单的编辑和修正。这些优化会让你的小程序从“能用”变得“好用”。4. 关键问题排查与优化建议在实际开发中你可能会遇到一些典型问题。这里我列举几个并给出解决思路。问题一录音失败或权限被拒绝。检查点确保app.json中正确配置了requiredPrivateInfos和permission。真机调试时微信会弹出录音权限申请务必点击“允许”。解决方案在小程序的onLoad生命周期中可以调用wx.authorize提前向用户申请录音权限获得更稳定的体验。问题二上传音频后后端识别返回错误或乱码。检查点音频格式和采样率是否与后端期望的一致。我们前后端都约定使用16kHz采样率的WAV格式。解决方案在后端API的/transcribe接口中增加更详细的日志打印接收到的音频信息采样率、时长、大小并尝试用librosa或soundfile直接读取并检查音频数据是否正常。确保前端recorderManager.start的参数设置正确。问题三长录音转写时间过长用户体验差。检查点这是性能瓶颈。SenseVoice-Small虽然轻量但处理数十秒的音频也需要几秒时间。解决方案分片与流式识别进阶如前所述这是终极解决方案。需要修改前后端协议支持发送音频片段并返回片段识别结果。设置最长录音限制在UI上提示用户“建议每次录音不超过30秒”从产品层面规避。后端优化确保服务器有足够的CPU/GPU资源。对于GPU环境可以启用torch.compile对模型进行编译以加速推理。提供进度反馈在上传和转写期间给用户明确的进度提示比如“正在处理...(1/3)”减轻等待的焦虑感。问题四识别准确率在某些场景下不高。检查点SenseVoice-Small是一个通用模型在嘈杂环境、带口音或专业术语多的场景下效果可能会打折扣。解决方案前端预处理录音时提示用户“请在安静环境下清晰发音”。可以尝试在录音前进行简单的环境音检测需要更复杂的音频处理。后处理在后端识别结果返回后可以接入一个简单的文本后处理模块例如使用语言模型进行纠错、顺滑或者针对你的业务领域添加关键词词库进行纠偏。模型微调高级如果你有特定领域如医疗、法律的标注语音数据可以考虑对SenseVoice-Small进行微调以提升在该领域的识别率。5. 总结走完这一趟一个具备录音即时转文字功能的微信小程序就从想法变成了现实。回顾一下核心就是三步在后端服务器上把SenseVoice-Small模型跑起来提供一个简单的API在小程序前端利用微信的录音能力把声音录下来、传上去最后把识别回来的文字漂亮地展示给用户。整个过程里你会遇到一些坑比如音频格式要对得上、网络请求要处理好、用户体验要流畅。但每解决一个问题你对整个开发链条的理解就会更深一层。这个项目本身就是一个很好的学习案例它串联了AI模型部署、后端API开发、前端交互设计这几个关键环节。做出来的小程序你可以自己用来做会议纪要、灵感速记也可以分享给朋友用。如果还想做得更深入加上我说的那些优化点比如实时流式识别、历史记录云端同步、甚至多语言支持它就能变成一个更强大的生产力工具。开发的过程其实就是不断把想法实现、再把实现打磨得更好的过程希望这个实战指南能帮你踏出坚实的第一步。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。