微信小程序集成SenseVoice-Small:实现实时语音笔记功能

你有没有过这样的经历?开会时灵感迸发,手忙脚乱地打字记录,结果错过了重点;或者走在路上突然想到一个好点子,掏出手机打字却很不方便。语音记录,无疑是最高效的方式之一。但录下来的音频,事后整理起来又是个麻烦。

今天,我们就来聊聊怎么给你的微信小程序加上一个“聪明”的耳朵——集成语音识别能力,让用户说完话,文字稿就自动生成好了。我们将使用一个名为SenseVoice-Small的轻量级语音识别模型,它部署在星图GPU平台上,识别准确率高,响应速度快,非常适合小程序这种轻量级应用场景。

整个流程其实很清晰:用户在小程序里按下录音键,说完后松开,音频文件被上传到你的服务器;服务器调用部署好的SenseVoice-Small模型进行语音转文字;最后,识别出的文本结果返回给小程序,用户可以立即查看、编辑和保存。下面,我们就一步步拆解这个全链路是如何实现的。

1. 为什么选择SenseVoice-Small与小程序结合?

在做技术选型时,我们主要考虑几个因素:准确性、速度、成本以及易集成性。SenseVoice-Small模型在这些方面表现比较均衡。

首先,它是专门为中文场景优化的轻量级模型,对日常对话、会议记录这类内容的识别效果很好,错误率比较低。你不用担心用户带点口音或者说得快一点它就听不懂了。

其次,它的“身材”比较小巧,推理速度快。对于小程序来说,用户录完音等待转写的这段时间体验非常关键。如果等个十几秒,用户可能就失去耐心了。SenseVoice-Small通常能在几秒内完成一句话的转写,这个速度是用户可以接受的。

最后,也是很重要的一点,我们可以把它部署在星图GPU云服务上。这意味着你不需要自己购买和维护昂贵的GPU服务器,按需使用,成本可控。对于中小型项目或者想快速验证想法的团队来说,这种模式非常友好。

而微信小程序,拥有庞大的用户基础和完善的生态。它的录音、文件上传等API成熟稳定,开发门槛相对较低。把强大的云端语音识别能力和便捷的小程序前端结合起来,就能打造出一个随时随地可用的语音笔记工具。

2. 搭建后端转写服务

小程序本身不能直接运行复杂的AI模型,所以我们需要一个“中间人”——后端服务。它的工作就是接收小程序上传的音频,调用SenseVoice-Small模型进行识别,然后把文字结果送回小程序。

2.1 在星图平台部署SenseVoice-Small模型

第一步,我们需要让模型“跑起来”。这里假设你已经有了星图平台的账号。

  1. 创建GPU实例:在星图平台的控制台,选择创建一个GPU实例。对于SenseVoice-Small这种轻量模型,选择一款中等算力的GPU(比如NVIDIA T4)就足够了,性价比很高。
  2. 选择镜像与环境:在创建实例时,平台通常会提供一些预置的深度学习环境镜像(比如PyTorch)。选择一个你熟悉的、版本合适的镜像。
  3. 部署模型:实例启动后,通过SSH连接上去。你需要将SenseVoice-Small的模型文件下载到服务器上。模型文件可能包含权重(.pth.bin文件)和相关的配置文件。按照模型提供的官方文档或示例代码,编写一个简单的推理脚本。这个脚本的核心功能就是加载模型,接收音频文件路径,输出识别文本。

一个极其简化的Python脚本示例可能是这样的:

# inference.py
import torch
from sensevoice import SenseVoiceSmall  # 假设有这样一个封装好的类
import soundfile as sf

# 初始化模型(实际路径需要修改)
model = SenseVoiceSmall(model_path="./sensevoice-small-model")
model.eval()

def transcribe_audio(audio_path):
    # 读取音频文件
    audio, sr = sf.read(audio_path)
    # 进行预处理,如重采样到模型要求的采样率
    # processed_audio = preprocess(audio, sr)
    # 调用模型推理
    with torch.no_grad():
        text = model.transcribe(processed_audio)
    return text

if __name__ == "__main__":
    # 测试用
    result = transcribe_audio("test.wav")
    print(result)
  1. 封装为API服务:光有脚本还不够,我们需要一个Web服务来调用它。这里用最常用的Flask框架来快速实现。
# app.py
from flask import Flask, request, jsonify
import os
from werkzeug.utils import secure_filename
from inference import transcribe_audio  # 导入上面写的转写函数

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 限制16MB

# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

@app.route('/transcribe', methods=['POST'])
def transcribe():
    if 'audio' not in request.files:
        return jsonify({'error': 'No audio file provided'}), 400
    
    file = request.files['audio']
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    # 保存上传的音频文件
    filename = secure_filename(file.filename)
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    file.save(filepath)
    
    try:
        # 调用语音识别函数
        text_result = transcribe_audio(filepath)
        # 识别完成后,可以删除临时文件
        os.remove(filepath)
        return jsonify({'text': text_result})
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=False)

这样,一个简单的转写API就完成了。当你访问 http://你的服务器IP:5000/transcribe 并上传音频文件时,它就会返回识别出的文字。

2.2 API设计要点与安全考虑

上面的例子是最基础的,在实际项目中你还需要考虑更多:

  • 音频格式处理:小程序默认录制的音频格式可能是.aac.mp3,而你的模型可能要求.wav.pcm。你需要在后端添加一个音频格式转换的步骤(比如用ffmpegpydub库)。
  • 采样率与声道:模型通常对音频的采样率(如16kHz)和声道数(单声道)有要求。上传后需要统一转换。
  • 身份验证:你的API不能谁都能调用。最简单的办法是增加一个API密钥验证。小程序端在上传时携带一个密钥,后端进行校验。
  • 错误处理与日志:添加更完善的错误捕获和日志记录,方便出了问题快速排查。
  • 性能与并发:如果你的用户量增长,可能需要用Gunicorn等WSGI服务器来运行Flask应用,并考虑使用任务队列(如Celery)来处理转写任务,避免请求阻塞。

3. 微信小程序前端开发实战

后端准备好了,接下来就是小程序端。我们要实现录音、上传、显示结果这一套流程。

3.1 录音功能实现

微信小程序提供了 wx.getRecorderManager() API来管理录音。它的功能比较强大,可以实时监听录音状态。

我们通常在页面的onLoad生命周期里初始化录音管理器,并设置一些参数:

// pages/voice-note/voice-note.js
Page({
  data: {
    recording: false, // 是否正在录音
    recordTime: 0, // 录音时长
    tempFilePath: '', // 录音临时文件路径
    resultText: '', // 识别结果
    isTranscribing: false, // 是否正在转写
  },

  onLoad: function () {
    // 初始化录音管理器
    this.recorderManager = wx.getRecorderManager();

    // 监听录音开始事件
    this.recorderManager.onStart(() => {
      console.log('录音开始');
      this.setData({ recording: true });
      // 可以开始计时
      this.startRecordTimer();
    });

    // 监听录音结束事件,这里会拿到临时文件路径
    this.recorderManager.onStop((res) => {
      console.log('录音结束', res);
      this.setData({ 
        recording: false, 
        tempFilePath: res.tempFilePath,
        recordTime: 0 
      });
      clearInterval(this.timer);
      // 录音结束,自动上传
      this.uploadAudioFile(res.tempFilePath);
    });

    // 监听录音错误事件
    this.recorderManager.onError((res) => {
      console.error('录音失败', res);
      wx.showToast({ title: '录音失败', icon: 'none' });
      this.setData({ recording: false });
      clearInterval(this.timer);
    });
  },

  startRecordTimer() {
    this.timer = setInterval(() => {
      this.setData({
        recordTime: this.data.recordTime + 1
      });
    }, 1000);
  },

  // 开始录音
  startRecording() {
    // 先检查用户是否授权
    wx.authorize({
      scope: 'scope.record',
      success: () => {
        // 用户已经授权过,直接开始
        this.recorderManager.start({
          duration: 60000, // 最长60秒,0为无限制
          sampleRate: 16000, // 采样率,与后端模型匹配
          numberOfChannels: 1, // 单声道
          encodeBitRate: 48000, // 编码码率
          format: 'aac' // 音频格式,也可以是mp3
        });
      },
      fail: (err) => {
        // 用户未授权,引导去设置页开启
        console.log('未授权录音', err);
        wx.showModal({
          title: '提示',
          content: '需要您授权使用麦克风',
          success(res) {
            if (res.confirm) {
              wx.openSetting(); // 打开设置页面
            }
          }
        });
      }
    });
  },

  // 停止录音
  stopRecording() {
    if (this.data.recording) {
      this.recorderManager.stop();
    }
  },
  // ... 其他方法
})

对应的WXML页面,就是两个简单的按钮和状态显示:

<!-- pages/voice-note/voice-note.wxml -->
<view class="container">
  <view class="status">
    <text wx:if="{{recording}}">录音中... {{recordTime}}秒</text>
    <text wx:elif="{{isTranscribing}}">正在转写文字,请稍候...</text>
    <text wx:else>点击下方按钮开始录音</text>
  </view>

  <view class="button-area">
    <button 
      wx:if="{{!recording}}" 
      type="primary" 
      size="large" 
      bindtap="startRecording"
      disabled="{{isTranscribing}}"
    >
      按住录音
    </button>
    <button 
      wx:else 
      type="warn" 
      size="large" 
      bindtap="stopRecording"
    >
      松开结束
    </button>
  </view>

  <view class="result" wx:if="{{resultText}}">
    <text class="title">识别结果:</text>
    <textarea 
      value="{{resultText}}" 
      placeholder="识别出的文字将显示在这里" 
      auto-height 
      bindinput="onTextChange"
    />
    <button type="default" size="mini" bindtap="saveNote">保存笔记</button>
  </view>
</view>

3.2 上传音频与获取结果

录音结束后,我们拿到了临时文件路径 tempFilePath,接下来就是把它上传到我们刚刚搭建的后端API。

// pages/voice-note/voice-note.js (续)
Page({
  // ... 之前的 data 和 onLoad 等方法

  // 上传音频文件到后端服务器
  uploadAudioFile(tempFilePath) {
    this.setData({ isTranscribing: true });
    
    wx.uploadFile({
      url: 'https://你的服务器域名/transcribe', // 你的后端API地址
      filePath: tempFilePath,
      name: 'audio', // 与后端接收的字段名一致
      formData: {
        'token': 'your_api_token_here' // 简单的API密钥验证
      },
      success: (res) => {
        const data = JSON.parse(res.data); // 注意:uploadFile返回的data是字符串
        if (data.text) {
          // 转写成功
          this.setData({ 
            resultText: data.text,
            isTranscribing: false 
          });
          wx.showToast({ title: '转写完成', icon: 'success' });
        } else {
          // 后端返回了错误
          wx.showToast({ title: `转写失败: ${data.error}`, icon: 'none' });
          this.setData({ isTranscribing: false });
        }
      },
      fail: (err) => {
        console.error('上传失败', err);
        wx.showToast({ title: '网络错误,上传失败', icon: 'none' });
        this.setData({ isTranscribing: false });
      }
    });
  },

  // 文本区域内容变化时触发
  onTextChange(e) {
    this.setData({
      resultText: e.detail.value
    });
  },

  // 保存笔记到本地或上传到云
  saveNote() {
    if (!this.data.resultText.trim()) {
      wx.showToast({ title: '内容为空', icon: 'none' });
      return;
    }
    // 这里可以实现保存逻辑,例如存入小程序本地存储或自己的数据库
    wx.setStorageSync('last_note', this.data.resultText);
    wx.showToast({ title: '保存成功', icon: 'success' });
  }
})

这样,一个完整的“录音-上传-转写-显示”闭环就实现了。用户按下按钮说话,松开后稍等几秒,文字就出现在屏幕上,还可以进行编辑和保存。

4. 优化体验与扩展思路

基础功能跑通后,我们可以从细节上打磨,让体验更好。

  • 实时反馈:录音时,可以增加一个波形动画,让用户感知到声音正在被采集。
  • 取消与重录:在录音过程中或转写等待时,提供取消按钮。转写结果不理想时,提供一键重录功能。
  • 音频播放:允许用户点击播放刚才的录音,对照文字进行检查和修改。
  • 标点与分段:SenseVoice-Small等现代模型通常能自动添加标点和进行说话人分段。你可以将后端返回的带有时间戳和分段信息的结果,在小程序端进行更美观的渲染。
  • 多语言支持:如果你的模型支持,可以增加一个语言选择开关,满足不同场景需求。
  • 离线能力探索:虽然主要依赖云端,但也可以研究微信小程序本地的语音识别API(wx.startRecord旧API或Web Speech API的兼容方案)作为网络不佳时的降级方案,虽然精度可能不如专用模型。

5. 总结

走完这一趟,你会发现,给微信小程序加上一个高质量的语音识别功能,并没有想象中那么复杂。关键是把链路拆解清楚:前端负责友好的交互和音频采集,后端负责可靠的模型服务和业务逻辑,而星图这样的GPU平台则提供了强大且省心的算力支撑。

SenseVoice-Small模型在中文场景下的表现,足以支撑起一个语音笔记应用的核心体验。在实际开发中,你可能会遇到音频格式兼容、网络波动、错误处理等具体问题,但解决问题的路径是清晰的。最重要的是,你拥有了一个可以快速验证想法、服务用户的工具。不妨就从今天开始,动手试试看,把你的下一个想法,用声音变成现实吧。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐