uniapp中接入科大讯飞语音听写(app端)

问题

由于uniapp录音上下文(uni.getRecorderManager())的帧功能(recordManager.onFrameRecorded)仅支持小程序,app端只会获取到一个临时的录音文件地址,因此需要把录音文件转成base64,在切片传输给讯飞的接口.

一、申请一个讯飞的apiKey和秘钥 控制台-讯飞开放平台

二、页面展示一个录音按钮,控制语音输入输出.

<template>
    <div class="asr">
        <up-button class="btn" shape="circle" type="info" @touchstart="openMedia" @touchend="stopMedia">按住
            说话</up-button>
        <!-- @click="handleShow" -->
        <!-- <iatPopup v-model:moduleValue="show" /> -->
        <view v-if="show" class="iating">
            <up-image src="/static/images/main/iat.png" width="300rpx" height="120rpx" class="img"></up-image>
            <up-text text="正在说话中...." color="#49ABFE" size="27rpx" line-height="60rpx" align="center"></up-text>
        </view>

    </div>
</template>

二、对接讯飞鉴权接口

   import CryptoJS from "crypto-js";
    // 科大讯飞接口配置
    const config = {
        hostUrl: "wss://iat-api.xfyun.cn/v2/iat",
        host: "iat-api.xfyun.cn",
        appid: "你的appid",
        apiSecret: "你的秘钥",
        apiKey: "你的apikey",
        uri: "/v2/iat",
        highWaterMark: 1280,
        // file: "./16k_10.pcm",
    };
     // 鉴权签名
    function getAuthStr(date) {
        let signatureOrigin = `host: ${config.host}\ndate: ${date}\nGET ${config.uri} HTTP/1.1`;
        let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, config.apiSecret);
        let signature = CryptoJS.enc.Base64.stringify(signatureSha);
        let authorizationOrigin =
            `api_key="${config.apiKey}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`;
        let authStr = CryptoJS.enc.Base64.stringify(
            CryptoJS.enc.Utf8.parse(authorizationOrigin)
        );
        return authStr;
    }

    function getUrl() {
        // 获取当前时间 RFC1123格式
        let date = new Date().toUTCString();
        //科大讯飞远程连接地址
        let wssUrl =
            config.hostUrl +
            "?authorization=" +
            getAuthStr(date) +
            "&date=" +
            encodeURIComponent(date) +
            "&host=" +
            config.host;
        console.log("websocke科大讯飞的地址为", wssUrl);
        return wssUrl;
    }

三、监听录音点击开始事件,录音开始的时候连接讯飞的websokect,url是鉴权返回的url.

 //打开麦克风
    function openMedia() {
        connectSocket(); //创建websocket连接
    }
   
     //创建连接并返回数据
    function connectSocket() {
        //创建socketTask实例
        if (uniSocketTask === null) {
            uniSocketTask = uni.connectSocket({
                url: getUrl(),
                success() {},
            });
            //监听连接成功的事件
            uniSocketTask.onOpen(() => {
                console.log("监听到开启连接成功");
                startRecord(); //打开录音
            });
            //监听连接关闭的事件
            uniSocketTask.onClose(() => {
                console.log("监听到关闭连接成功");
                uniSocketTask = null;
            });
            uniSocketTask.onError(() => {
                console.log("监听到连接发生错误");
            });
            //监听科大讯飞消息返回
            uniSocketTask.onMessage((res) => {
                //收到消息
                const message = JSON.parse(res.data);
                //判断是否存在数据
                if (res.data) {
                    console.log("收到服务器消息,并开始渲染", message);
                    renderResult(message);

                    if (message.code === 0 && message.data.status === 2) {
                        //该函数为当前页唯一的关闭连接函数
                        console.log('最后一条', renderText.value)
                        closeSocket();
                        emit('renderText', renderText.value)
                    }
                    //收到不正常服务器消息,返回错误到控制台
                    if (message.code !== 0) {
                        closeSocket();
                        console.error(message);
                    }
                } else {
                    console.log("未监听到消息:原因:", JSON.stringify(res));
                }
            });
        } else {
            console.log("socketTask实例已存在");
        }
    }
    
    //发送给科大讯飞的第一帧的模板数据格式
    let frame = {
        common: {
            app_id: config.appid,
        },
        business: {
            language: "zh_cn",
            domain: "iat",
            accent: "mandarin",
            dwa: "wpgs", // 可选参数,动态修正
            vad_eos: 5000,
        },
        data: {
            status: 0,
            format: "audio/L16;rate=16000",
            encoding: "lame"
        },
    };
//发送消息
    function sendMessage(sendData) {

        console.log('发送', JSON.stringify(sendData))

        uniSocketTask.send({
            data: JSON.stringify(sendData),
            success() {
                // console.log("发送成功");
            },
            fail() {
                console.log("发送失败");
            },
        });
    }
    //关闭连接
    function closeSocket() {
        console.log("开始尝试关闭连接");
        // 关闭连接
        uniSocketTask.close();
    }
   

四、录音结束的时候,先传输第一帧信息,返回文件地址,由于uniapp小程序可以直接监听录音帧,但是app不能监听到帧所以需要将录音文件转成base64,在切片传输.

// 录音配置项
    const recordOption = {
        sampleRate: 16000, // 采样率(pc不支持)
        format: "mp3", // 音频格式,默认是 aac
    };
    const recordManager = uni.getRecorderManager();
    
    //开启录音
    const startRecord = () => {
        recordManager.onStart(() => {
            console.log("开始录音");
            show.value = true
            // ...
        });
        recordManager.onStop((res) => {
            // tempFilePath	String	录音文件的临时路径
            console.log("录音停止,文件路径为:", res.tempFilePath);

            sendMessage(frame)

            pathToBase64(res.tempFilePath).then(base64 => {
                console.log('base64--', base64);
                // 这里一定要注意:当需要将base64字符串转换为Buffer时,通常会去掉前面部分的数据URL标识(如"data:image/png;base64,"),因为这部分内容不是实际的base64编码数据。
                let buff = base64.split(",")[1]
                const arrayBuffer = uni.base64ToArrayBuffer(buff)
                const audioString = toString(arrayBuffer)
                console.log("文件读取成功", audioString.length);
                let offset = 0;
                while (offset < audioString.length) {
                    const subString = audioString.substring(offset, offset + 1280)
                    offset += 1280
                    const isEnd = offset >= audioString.length

                    let params = {
                        data: {
                            status: isEnd ? 2 : 1,
                            format: "audio/L16;rate=16000",
                            encoding: "lame",
                            audio: btoa(subString)
                        }
                    }

                    sendMessage(params);
                }
            }).catch(error => {
                console.error(error)
            })

        });
        recordManager.onError((err) => {
            // errMsg	String	错误信息
            console.log("录音出现错误", err);
        });

        recordManager.start(recordOption);
    };
    //关闭录音
    const stopRecord = () => {
        recordManager.stop();
    };

    const toString = (buffer) => {
        var binary = '';
        var bytes = new Uint8Array(buffer);
        var len = bytes.byteLength;
        for (var i = 0; i < len; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return binary
    }
    //录音文件路径转base64
    function pathToBase64(path) {
        return new Promise(function(resolve, reject) {
            // app
            if (typeof plus === 'object') {
                plus.io.resolveLocalFileSystemURL(path, function(entry) {
                    entry.file(function(file) {
                        var fileReader = new plus.io.FileReader()
                        fileReader.onload = function(evt) {
                            resolve(evt.target.result)
                        }
                        fileReader.onerror = function(error) {
                            reject(error)
                        }
                        fileReader.readAsDataURL(file)
                    }, function(error) {
                        reject(error)
                    })
                }, function(error) {
                    reject(error)
                })

                return
            }

            reject(new Error('not support'))
        })
    }

五、处理讯飞返回的数据

 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);
    }

总结

在项目过程中遇到讯飞返回给我的一直是空数据问题,感觉是因为传过去的数据有问题,因此要确定第一帧的配置信息是否正确,跟每一帧发送的base64是不是不一样的数据,这样返回的数据就不用有问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值