腾讯IM uniapp微信小程序版本实现迅飞语音听写(流式版)

在之前文章《腾讯IM web版本实现迅飞语音听写(流式版)》实现了腾讯IM web版本实现迅飞语音听写,本文将基于uniapp vue2/vue3(cli 脚手架)的Demo项目集成迅飞语音听写(流式版):

主要代码:

// \src\TUIKit\components\TUIChat\message-input\index.vue

<template>
	<!-- 录音按钮 -->
	<VoiceToText @change="changeVoiceToText"/>
	<!-- 输入框 -->
	<MessageInputEditor  ref="editor"/>
</template>

// voice-to-text/index.vue

<template>
  <div v-if="!openShow" class="message-input-asr" @click="openMedia" />
  <div v-if="openShow" class="message-input-asr" :class="{active: openShow}" @click="stopMedia" />
</template>

<script setup>
import { onMounted, ref } from "vue";
import ChatApi from '@/api/chat.js'

const emits = defineEmits(['change'])
const openShow = ref(false)

// 发给科大讯飞的每一帧的定义
//参考接口调用流程: https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B
const FRAME = {
  STATUS_FIRST_FRAME: 0, //第一帧音频
  STATUS_CONTINUE_FRAME: 1, //中间的音频
  STATUS_LAST_FRAME: 2, //最后一帧音频,最后一帧必须要发送
};
let status = FRAME.STATUS_FIRST_FRAME;

//心跳定时器
let heartTimer = null;
// 心跳数据(发给科大讯飞的数据)发送队列
let sendDataStack = [];

//websocket实例化
let uniSocketTask = null;

//打开麦克风
function openMedia() {
  status = FRAME.STATUS_FIRST_FRAME; //初始化音频状态(设置当前录音状态为首帧)
	sendDataStack = []
	stopMedia()
  connectSocket(); //创建websocket连接
  startRecord(); //打开录音
}
//关闭麦克风
function stopMedia() {
  //关闭录音
  stopRecord();
  //为什么关闭录音的时候不直接关闭websocket连接,这是因为:
  //关闭麦克风的时候会回调出录音最后一帧,并且会触发监听录音结束事件(也不是在这个地方结束)。
  //发送最后一帧后,得到最后一帧返回的消息后,此时此刻才应该关闭websocket连接
  //但是可以关闭心跳定时器
  clearInterval(heartTimer);
  //初始化已经渲染了的数据
  renderText.value = "";
  resultText = "";
  resultTextTemp = "";
}
/**
 * 获取麦克风权限并开始录音(步骤一)
 */
// 录音配置项
const recordOption = {
  duration: 60000, //指定录音的时长,单位 ms,微信可设置的最大值为 600000(10 分钟),科大讯飞最高支持时间为 60000 (1分钟)
  sampleRate: 16000, // 采样率(pc不支持)
  numberOfChannels: 1.25, // 录音通道数
  // encodeBitRate: 48000, // 编码码率(默认就是48000)
  // frameSize 为指定帧大小,单位 KB。传入 frameSize 后,每录制指定帧大小的内容后,会回调录制的文件内容,不指定则不会回调。
  // 暂仅支持 mp3、pcm 格式。科大讯飞建议:每40ms发送一个节数为1280B的为压缩PCM格式
  frameSize: 1,
  format: "pcm", // 音频格式,默认是 aac
};
let recordManager = null;
//开启录音
const startRecord = () => {
  openShow.value = true
	recordManager = uni.getRecorderManager();
	
  recordManager.onStart(() => {
    console.log("开始录音");
    // ...
  });
  // recordManager.onPause(() => {
  //   console.log("录音暂停");
  // });
  recordManager.onStop((res) => {
    // tempFilePath	String	录音文件的临时路径
    console.log("录音停止,文件路径为:", res.tempFilePath);
  });
  recordManager.onError((err) => {
    // errMsg	String	错误信息
    console.log("录音出现错误", err);
  });
  //获得录音结果的回调函数
  recordManager.onFrameRecorded((res) => {
    // console.log("开始产生录音结果:");
    // frameBuffer:	type:[ArrayBuffer]	录音分片结果数据
    // isLastFrame:	type:[Boolean]	当前帧是否正常录音结束前的最后一帧
    const { frameBuffer, isLastFrame } = res;
    // console.log("录音分片结果:", toBase64(frameBuffer));
    // console.log("开始判断当前帧为第几帧");
    // 判断当前录音数据是否为最后一帧
    if (isLastFrame) {
      console.log("当前帧为录音最后一帧");
      status = FRAME.STATUS_LAST_FRAME;
    }
    //存入心跳数据栈中,再合适的时候发送给科大讯飞(第四步)
    sendDataStack.push(packFrame(frameBuffer)); //入栈
  });
  recordManager.start(recordOption);
};
//关闭录音
const stopRecord = () => {
  recordManager?.stop();
  openShow.value = false
};
//音频转码(步骤二)[把音频片段转换成base64]
function toBase64(buffer) {
  return uni.arrayBufferToBase64(buffer);
}

//创建连接并返回数据
async function connectSocket() {
 const res = await ChatApi.getXFVoiceAuthUrl()
 uniSocketTask = uni.connectSocket({
   url: res.url,
   success() {},
 });
 //监听连接成功的事件
 uniSocketTask.onOpen(() => {
   console.log("监听到开启连接成功");
   //启动心跳定时器
   onHeartBeat();
 });
 //监听连接关闭的事件
 uniSocketTask.onClose(() => {
   console.log("监听到关闭连接成功");
   uniSocketTask = null;
 });
 uniSocketTask.onError(() => {
   console.log("监听到连接发生错误");
 });
 //监听科大讯飞消息返回
 uniSocketTask.onMessage((res) => {
   //收到消息
   const message = JSON.parse(res.data);
   //判断是否存在数据
   if (res.data) {
     console.log("收到服务器消息,并开始渲染");
     renderResult(message);
     if (message.code === 0 && message.data.status === 2) {
       //此时此刻我们可以得知当前科大讯飞返回的数据为最后一帧可以关闭连接
       //该函数为当前页唯一的关闭连接函数
       closeSocket();
     }
     //收到不正常服务器消息,返回错误到控制台
     if (message.code !== 0) {
       closeSocket();
       console.error(message);
     }
   } else {
     console.log("未监听到消息:原因:", JSON.stringify(res));
   }
 });
}
//关闭连接
function closeSocket() {
  console.log("开始尝试关闭连接");
  // 关闭心跳
  if (heartTimer) {
    clearInterval(heartTimer);
  }
  // 关闭连接
  uniSocketTask.close();
}

//发送给科大讯飞的每一帧的模板数据格式
let frame = {
  common: {
    app_id: '自己的appid',
  },
  business: {
    language: "zh_cn",
    domain: "iat",
    accent: "mandarin",
    dwa: "wpgs", // 可选参数,动态修正
    vad_eos: 5000,
  },
  data: {
    status: 0,
    format: "audio/L16;rate=16000",
    encoding: "raw",
    audio: "", //音频内容
  },
};
//封装录音后的每一"帧"数据(步骤三)
function packFrame(audioData) {
  //转载音频数据
  frame.data.audio = toBase64(audioData);
  switch (status) {
    //首条空白消息(第一帧)
    case FRAME.STATUS_FIRST_FRAME:
      // console.log("当前帧为第一帧");
      frame.data.status = 0;
      status = FRAME.STATUS_CONTINUE_FRAME;
      break;
    //正文(中间帧)
    case FRAME.STATUS_CONTINUE_FRAME:
      // console.log("当前帧为中间帧");
      frame.data.status = 1;
      break;
    //传输结束(最后一帧)
    case FRAME.STATUS_LAST_FRAME:
      // console.log("当前帧为最后一帧");
      frame.data.status = 2;
      break;
  }
  return JSON.stringify(frame);
}

//启动心跳连接定时器,每40ms发送一次数据(第五步发送数据)
function onHeartBeat() {
  let sendData = null; //发送给科大讯飞的每一帧数据
  heartTimer = setInterval(() => {
    //发送队列中有数据的时候执行下述逻辑发送数据,否则不执行下述函数
    console.log("当前发送队列中数据的长度为", sendDataStack.length);
    if (sendDataStack.length !== 0) {
      sendData = sendDataStack.shift();
      uniSocketTask.send({
        data: sendData,
        success() {
          // console.log("发送成功");
        },
        fail() {
          console.log("发送失败");
        },
      });
    }
  }, 40);
}

//根据科大讯飞语音听写识别结果进行渲染(最后一步,根据onMessage()方法返回的数据进行渲染)
let resultText = "";
let resultTextTemp = "";
let renderText = ref("");
function renderResult(jsonData) {
  // console.log("开始执行渲染函数", jsonData);
  if (jsonData.data && jsonData.data.result) {
    let data = jsonData.data.result;
    let str = ""; // 初始化一个字符串变量用于存储拼接后的识别结果
    let ws = data.ws;
    for (let i = 0; i < ws.length; i++) {
      str = str + ws[i].cw[0].w;
    }
    // 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
    // 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
    if (data.pgs) {
      if (data.pgs === "apd") {
        // 将resultTextTemp同步给resultText
        resultText = resultTextTemp;
      }
      // 将结果存储在resultTextTemp中
      resultTextTemp = resultText + str;
    } else {
      resultText = resultText + str;
    }
    renderText.value = resultTextTemp || resultText || "";
  }
  console.log("渲染后的数据为");
  console.log(renderText.value);

	emits.$emit('change', {
		content: renderText.value,
  })
}
</script>
相关推荐
Kika写代码9 小时前
【微信小程序】页面跳转基础 | 我的咖啡店-综合实训
服务器·微信小程序·小程序
源码哥_博纳软云11 小时前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
洗发水很好用12 小时前
uniApp打包H5发布到服务器(docker)
uni-app
YUJIAN。12 小时前
使用uniapp开发微信小程序-框架搭建
微信小程序·小程序·uni-app
V+zmm1013421 小时前
基于微信小程序的乡村政务服务系统springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
还这么多错误?!21 小时前
uniapp微信小程序,使用fastadmin完成一个一键获取微信手机号的功能
微信小程序·小程序·uni-app
_院长大人_21 小时前
微信小程序用户信息解密 AES/CBC/NoPadding 解密失败问题
微信小程序·小程序
IT 前端 张21 小时前
Uniapp 手机基座调试App 打包成Apk文件,并上传到应用商店
uni-app
User_undefined21 小时前
uniapp Native.js原生arr插件服务发送广播到uniapp页面中
android·javascript·uni-app
web1350858863521 小时前
uniapp小程序使用webview 嵌套 vue 项目
vue.js·小程序·uni-app