uniapp开发app实现百度语音识别(vue2)

1,143 阅读3分钟

一、使用uniapp的Speech模块(使用 plus.speech.startRecognize api)

1.在Speech.js文件中存放语音识别监听的逻辑。

let nowText = '';

class Speech {
    setSpeech() {
        plus.speech.addEventListener('start', this.onStart, false);
        plus.speech.addEventListener('recognition', this.onRecognition, false);
        plus.speech.addEventListener('end', this.onEnd, false);
    }
    onStart() {
        nowText = '';
    }
    onRecognition(e) {
        // console.log('识别完成');
        nowText = e.result;
        uni.$emit('SPEECH', {
            result: nowText
        })
    }
    onEnd() {
        console.log('Event: end', '结束录制');
        console.log('结束语音录制....');
        if(!nowText) {
            plus.nativeUI.toast('没有识别到内容');
        }
		
        plus.speech.stopRecognize();
    }
    checkMicrophone() {
        // 检查麦克风权限
        const info = uni.getAppAuthorizeSetting();
        if(info.microphoneAuthorized !== 'authorized') {
            plus.nativeUI.toast('请打开麦克风权限,再重启应用');
            return false;
        }
		
        return true;
    }
}

export default new Speech();

2.在App.vue中引入Speech.js文件。

<script>
    import Speech from '@/utils/Speech.js';
    
    export default {
        onLaunch: () {
            // #ifdef APP-PLUS
                // 初始化语音识别功能
                Speech.setSpeech();
            // #endif
        }
    }
</script>

3.现在就可以在你想要的地方使用了,例如:

// 在自己的语音识别组件中加入下面这段
let options = {
    engine: 'baidu',
    timeout: 10 * 1000, //超时时间
    punctuation: true, //是否需要标点符号
    continue: true, //语音识别是否采用持续模式
    userInterface: true, //安卓手机为true时会影响@touchend触摸结束事件,暂未发现好的解决方法
};

// console.log('开始语音识别:');
plus.speech.startRecognize(options, () => {}, (errMsg) => {
    console.log('语音识别失败:' + errMsg);
});

uni.$on('SPEECH', (e: any) => {
    console.log('识别结果:', e.result);
    tips.value = e.result;
    emits('micInput', e.result);
})



// 在组件的beforeDestroy生命周期或语音识别组件的关闭方法中加入移除全局监听
uni.$off('SPEECH');

这种方式有个问题,配置options中的userInterface必须是true(为false会识别不了语音内容),为true时又无法自定义识别样式,只能使用其自带的样式,就导致无法实现长按语音识别效果,因此更推荐使用第二种方式。

二、使用百度短语音识别的接口上传录音文件,获取返回的语音识别信息

1.百度语音识别接口需要携带百度的access_token参数,因此建议在登录的时候调用该接口获取access_token,鉴权相关文档: ai.baidu.com/ai-doc/REFE…

uni.request({
    url: 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=你的百度API Key&client_secret=你的百度Secret Key&',
    method: 'POST',
    header: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    },
    success: (res) => {
        console.log('百度token:', res.data.access_token);
        uni.setStorageSync('BAIDU_ACCESS_TOKEN', res.data.access_token);
    },
    fail: (err) => {
        resolve('fail:', err);
    }
})

2.然后在你的语音识别组件中录音,录音结束后调用百度短语音识别接口携带base64格式的录音文件参数等信息,就可以获取语音识别结果了,这里是用的是json方式上传音频,接口参数的格式请自行前往百度短语音识别开发文档查看( cloud.baidu.com/doc/SPEECH/… )。

<script>
import { pathToBase64 } from '@/utils/pathToBase64';
import dayjs from 'dayjs';

export default {
    data() {
        ...,
        voiceTextValue: '', // 记录识别结果
        recorderManager: null,
        audioContext: null,
        openTimer: 0,
        ...
    },
    mounted() {
        // 获取全局唯一的录音管理器
        this.recorderManager = uni.getRecorderManager();
        // 创建并返回内部 audio 对象
        this.audioContext = uni.createInnerAudioContext();
        this.recorderManager.onStop((res) => {
            const closeTimer = dayjs().valueOf();
            console.log('recorder stop:', closeTimer - this.openTimer);
            // 判断录音时长是否大于1s,大于1s才能识别
            if(closeTimer - this.openTimer > 1000) {
                uni.showLoading({
                    title: '正在识别中...'
                })
                
                // 获取录音文件的临时地址
                this.audioContext.src = res.tempFilePath;
            } else {
                uni.showToast({
                        title: '说话时间太短,请重新说一遍',
                        icon: 'none'
                })
            }
        });

        this.recorderManager.onError((err) => {
            console.log('错误信息', err);
        })
	
        // 当audio获取到文件地址时,会触发onCanplay事件
        this.audioContext.onCanplay(() => {
            this.getSpeechRecognition(this.audioContext.src);
        })
    },
    methods: {
        getSpeechRecognition(path) {
            // 获取录音文件的大小
            uni.getFileInfo({
                filePath: path,
                success: file => {
                    console.log('文件大小:', file.size);
                    // 根据所传地址获取base64格式的文件
                    pathToBase64(path).then(res => {
                        // 只需要文件的内容
                        const data = res.split(',')[1];
                        this.requestRecordData(data, file.size);
                    })
                },
                fail: () => {
                    uni.hideLoading();
                    uni.showToast({
                        title: '未找到文件',
                        icon: 'none'
                    })
                }
            })
        },
        // 请求百度语音识别接口,获取语音识别结果
        requestRecordData(data, size) {
            uni.request({
                url: 'http://vop.baidu.com/server_api',
                method: 'POST',
                header: {
                    'content-type': 'application/json;charset=UTF-8'
                },
                data: {
                    format: 'm4a',
                    rate: 16000,
                    channel: 1,
                    token, // 之前获取的百度access_token
                    cuid, // 设备识别码,建议使用通过 plus.device.getInfo()获取的设备uuid
                    speech: data,
                    len: size
                },
                success: speech => {
                    uni.hideLoading();
                    if(+speech.data.err_no === 0 && speech.data.result[0]) {
                        this.voiceTextValue = speech.data.result[0];
                    } else {
                        uni.showToast({
                            title: '未识别到内容',
                            icon: 'none'
                        })
                    }
                },
                fail: () => {
                    uni.hideLoading();
                    uni.showToast({
                        title: '获取语音识别结果失败',
                        icon: 'none'
                    })
                }
            })
        },
        // 开始语音识别
        startSpeek() {
            // #ifdef APP-PLUS
            // 记录开始语音识别的时间
            this.openTimer = dayjs().valueOf();
            
            this.recorderManager.start({
                    duration: 60000,
                    sampleRate: 16000,
                    format: 'mp3',
                    hideTips: true
            });
            // #endif
        },
        // 结束语音识别
        endSpeek() {
            // #ifdef APP-PLUS
            // 停止录音
            this.recorderManager.stop();
            // #endif
        }
    }
}
</script>

其中引用的pathToBase64.js文件内容:

export function pathToBase64(path) {
    return new Promise(function(resolve, reject) {
        // #ifdef H5
        if (typeof FileReader === 'function') {
            var xhr = new XMLHttpRequest()
            xhr.open('GET', path, true)
            xhr.responseType = 'blob'
            xhr.onload = function() {
                if (this.status === 200) {
                    let fileReader = new FileReader()
                    fileReader.onload = function(e) {
                        resolve(e.target.result)
                    }
                    fileReader.onerror = reject
                    fileReader.readAsDataURL(this.response)
                }
            }
            xhr.onerror = reject
            xhr.send()
            return
        }
        var canvas = document.createElement('canvas')
        var c2x = canvas.getContext('2d')
        var img = new Image
        img.onload = function() {
            canvas.width = img.width
            canvas.height = img.height
            c2x.drawImage(img, 0, 0)
            resolve(canvas.toDataURL())
            canvas.height = canvas.width = 0
        }
        img.onerror = reject
        img.src = path
        // #endif
        // #ifdef APP-PLUS
        plus.io.resolveLocalFileSystemURL(path, function(entry) {
            entry.file(function(file) {
                var fileReader = new plus.io.FileReader()
                fileReader.onload = function(data) {
                    resolve(data.target.result)
                }
                fileReader.onerror = function(error) {
                    reject(error)
                }
                fileReader.readAsDataURL(file)
            }, function(error) {
                reject(error)
            })
        }, function(error) {
            reject(error)
        })
        // #endif
    })
}