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

embedded/2024/11/17 8:32:27/

在之前文章《腾讯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 = truerecordManager = 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同步给resultTextresultText = 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>

http://www.ppmy.cn/embedded/138206.html

相关文章

计算机视觉在自动驾驶汽车中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机视觉在自动驾驶汽车中的应用 计算机视觉在自动驾驶汽车中的应用 计算机视觉在自动驾驶汽车中的应用 引言 计算机视觉在自动…

15-1.Java 网络编程之 InetAddress(InetAddress 常用静态方法、InetAddress 常用方法)

InetAddress 概述 InetAddress 用于表示一个 IP 地址&#xff08;IPv4 / IPv6&#xff09; InetAddress 提供了获取主机名、获取 IP 地址等一系列方法 其中 Inet 是 Internet 的缩写&#xff0c;代表因特网 一、InetAddress 常用静态方法 1、基本介绍 方法说明InetAddress…

分页查询我的课表

文章目录 概要整体架构流程技术细节小结 概要 需求分析以及接口设计 技术细节 1.Controller层: private final ILearningLessonService iLearningLessonService;/*** 分页查询我的课表* param pageQuery* return*/ApiOperation("分页查询我的课表")GetMapping(&quo…

git没有识别出大写字母改成小写重命名的文件目录

Git 默认不会跟踪大写字母和小写字母的区别&#xff0c;因为在大多数文件系统中&#xff0c;大写字母和小写字母被认为是相同的文件&#xff0c;只有在区分大小写的文件系统中&#xff08;如 macOS 的 HFS 或 Windows 的 NTFS&#xff09;&#xff0c;这才是一个问题。 如果重命…

LabVIEW前面板最大化显示与像素偏差分析 有源程序附件

LabVIEW前面板最大化显示与像素偏差分析 有源程序附件 LabVIEW前面板最大化显示与像素偏差分析 有源程序附件 - 北京瀚文网星科技有限公司 这个VI用于将LabVIEW程序的前面板最大化地显示在指定显示器上&#xff0c;实现步骤如下&#xff1a; 1. 获取所有显示器的信息 首先&…

‘v-scale-screen‘使用(Vue框架的大屏幕自适应组件)

v-scale-screen 是一个用于 Vue 框架的大屏幕自适应组件。它可以帮助开发者在开发大屏幕项目时实现屏幕的自适应&#xff0c;支持根据宽度、高度以及宽高比进行自适应调整&#xff0c;并且支持全屏自适应。这个组件适用于 Vue 2.7 及以上版本和 Vue 3 版本。 使用方法 1.安装…

MySql--增删改查表设计总结

一、客户端和数据库操作 1.登录 mysql -uroot -p 2.查看当前的数据库版本 show version(); 3.显示所有的数据库 show databases; 4.创建数据库 create [if not exists] databases 数据库名 character set 字符编码集 collate 排序规则&#xff1b; 5.选择数据库 use 数据库…

从二维到一维:动态规划矩阵问题的优化之道

动态规划中的矩阵问题是非常经典的应用场景&#xff0c;比如最小路径和问题。这类问题很自然地可以想到使用二维 dp 数组来求解。 我们定义&#xff1a; dp[i][j] 表示从矩阵的第 i行第 j列到右下角的最小路径和。 基本解法 求解过程从右下角开始&#xff0c;向左上角遍历&am…