一、Web语音API - SpeechRecognition
这 SpeechRecognition 的接口 Web 语音 API 是识别服务的控制器接口;它还处理 SpeechRecognitionEvent 从识别服务发送。
注意: 在某些浏览器(如 Chrome)上,在网页上使用语音识别会涉及基于服务器的识别引擎。音频将发送到 Web 服务进行识别处理,因此它无法脱机工作。
更多详情可以查看MDN : developer.mozilla.org/en-US/docs/…
这种方式的话需要考虑这种方式的兼容性问题,有些浏览器可以不太兼容。
这里列出他的一些常用的属性事件和方法: 属性:
SpeechRecognition.grammars:返回并设置 SpeechGrammar 表示当前将理解的语法的对象 SpeechRecognition。
SpeechRecognition.lang:返回并设置当前语言 SpeechRecognition.如果未指定,则默认为 HTML [lang] 属性值或用户代理的语言设置(如果也未设置)。
SpeechRecognition.continuous:控制是为每个识别返回连续结果,还是只返回单个结果。默认为单个 ( false.)。
事件:
[end] 在语音识别服务断开连接时触发。 也可通过 onend 财产。
[error]发生语音识别错误时触发。 也可通过 onerror 财产。
[result] 当语音识别服务返回结果时触发 - 单词或短语已被积极识别,并且已将其传达回应用。 也可通过 onresult 财产。
[soundstart] 当检测到任何声音(无论是否可识别的语音)时触发。 也可通过 onsoundstart 财产。
[soundend]当任何声音(无论是否可识别的语音)停止被检测到时触发。 也可通过 onsoundend 财产。
[start] 当语音识别服务开始侦听传入音频以识别与当前关联的语法时触发 SpeechRecognition. 也可通过 onstart 财产。
方法:
SpeechRecognition.start() : 启动侦听传入音频的语音识别服务,以识别与当前 SpeechRecognition。
SpeechRecognition.stop(): 停止语音识别服务侦听传入的音频,并尝试返回 [SpeechRecognitionResult]使用到目前为止捕获的音频。
<script setup lang="ts">
import { ref } from 'vue'
let interimText = ref()
const recognition: any = new webkitSpeechRecognition() || new SpeechRecognition()
recognition.continuous = true
recognition.interimResults = true
recognition.lang = 'en-US' //(可以改成其他的语言)
recognition.onstart = () => {
console.log('Speech recognition started')
}
recognition.onsoundstart = function (e) {
console.log('开始收听了')
console.log(e)
}
recognition.onspeechstart = (e) => {
console.log('开始讲话了')
console.log(e)
}
recognition.onspeechend = (e) => {
console.log('讲话完毕')
console.log(e)
}
//语音转化成字符串
recognition.onresult = (event) => {
console.log('文本结果')
interimText.value = event.results[0][0].transcript
console.log('interimText', interimText)
}
//监听报错
recognition.onerror = (event) => {
console.log(`Error occurred in recognition: ${event.error}`)
}
//开始
const startListening = () => {
recognition.start()
console.log('开始录音');
//监听开始
}
//停止
const stopListening = () => {
recognition.stop()
recognition.continuous = false;
console.log('结束录音');
}
</script>
<template>
<div>
<h1>Audio Recognition Test</h1>
<Button type="primary" @click="startListening"> 输入语音 </Button>
<Button type="primary" @click="stopListening"> 结束录音 </Button>
</div>
</template>
<style lang="less" scoped></style>
二、百度语音识别
废话不多说,先上代码。
/components/robot/recoder.js
let leftDataList = new Array();
let rightDataList = new Array();
//百度api地址
const url = "";
class Recorder {
constructor() {
this.mediaNode = null;
this.jsNode = null;
this.txt = "";
this.wavBuffer=null;
}
// 开始录音
beginRecorder() {
// 重置数据
this._dataInit();
// 采集声音
window.navigator.mediaDevices
.getUserMedia({
audio: {
sampleRate: 44100, // 采样率
channelCount: 2, // 声道
volume: 1.0, // 音量
},
})
.then((mediaStream) => {
this._begin(mediaStream);
})
.catch((err) => {
console.error(err);
});
}
// 结束录音 callback
stopRecorder() {
this.mediaNode.disconnect();
this.jsNode.disconnect();
// 停止录音
let leftData = this._mergeArray(leftDataList),
rightData = this._mergeArray(rightDataList);
let allData = this._interleaveLeftAndRight(leftData, rightData);
this.wavBuffer = this._createWavFile(allData);
// this._audioCheck(wavBuffer, callback);
}
// this._audioCheck(wavBuffer, callback);
// 重置数据
_dataInit() {
leftDataList = [];
rightDataList = [];
this.txt = "";
}
_begin(mediaStream) {
// AudioContext接口表示由链接在一起的音频模块构建的音频处理图,每个模块由一个AudioNode表示。音频上下文控制它包含的节点的创建和音频处理或解码的执行。
// 在做任何其他操作之前,你需要创建一个AudioContext对象,因为所有事情都是在上下文中发生的。
// 建议创建一个AudioContext对象并复用它,而不是每次初始化一个新的AudioContext对象,并且可以对多个不同的音频源和管道同时使用一个AudioContext对象。
let audioContext = new (window.AudioContext || window.webkitAudioContext)();
// 创建一个MediaStreamAudioSourceNode接口来关联可能来自本地计算机麦克风或其他来源的音频流MediaStream。
this.mediaNode = audioContext.createMediaStreamSource(mediaStream);
// 创建一个jsNode
this.jsNode = this._createJSNode(audioContext);
// 需要连到扬声器消费掉outputBuffer,process回调才能触发
// 并且由于不给outputBuffer设置内容,所以扬声器不会播放出声音
this.jsNode.connect(audioContext.destination);
this.jsNode.onaudioprocess = this._onAudioProcess;
// 把mediaNode连接到jsNode
this.mediaNode.connect(this.jsNode);
}
_createJSNode(audioContext) {
// 缓冲区大小,以样本帧为单位。具体来讲,缓冲区大小必须是下面这些值当中的某一个:256, 512, 1024, 2048, 4096, 8192, 16384.
// 如果不传,或者参数为 0,则取当前环境最合适的缓冲区大小,取值为 2 的幂次方的一个常数,在该 node 的整个生命周期中都不变。
// 该取值控制着 audioprocess 事件被分派的频率,以及每一次调用多少样本帧被处理。较低 bufferSzie 将导致一定的延迟。
// 较高的 bufferSzie 就要注意避免音频的崩溃和故障。推荐作者不要给定具体的缓冲区大小,让系统自己选一个好的值来平衡延迟和音频质量。
const BUFFER_SIZE = 4096;
// 值为整数,用于指定输入 node 的声道的数量,默认值是 2,最高能取 32.
const INPUT_CHANNEL_COUNT = 2;
// 值为整数,用于指定输出 node 的声道的数量,默认值是 2,最高能取 32.
const OUTPUT_CHANNEL_COUNT = 2;
// createJavaScriptNode已被废弃
let creator =
audioContext.createAudioWorkletNode ||
audioContext.createScriptProcessor ||
audioContext.createJavaScriptNode;
creator = creator.bind(audioContext);
return creator(BUFFER_SIZE, INPUT_CHANNEL_COUNT, OUTPUT_CHANNEL_COUNT);
}
_onAudioProcess(event) {
let audioBuffer = event.inputBuffer;
let leftChannelData = audioBuffer.getChannelData(0),
rightChannelData = audioBuffer.getChannelData(1);
// 需要克隆一下
leftDataList.push(leftChannelData.slice(0));
rightDataList.push(rightChannelData.slice(0));
}
// 合并数组
_mergeArray(list) {
let length = list.length * list[0].length;
let data = new Float32Array(length),
offset = 0;
for (let i = 0; i < list.length; i++) {
data.set(list[i], offset);
offset += list[i].length;
}
return data;
}
// 交叉合并左右声道的数据
_interleaveLeftAndRight(left, right) {
let totalLength = left.length + right.length;
let data = new Float32Array(totalLength);
for (let i = 0; i < left.length; i++) {
let k = i * 2;
data[k] = left[i];
data[k + 1] = right[i];
}
return data;
}
_createWavFile(audioData) {
const WAV_HEAD_SIZE = 44;
let buffer = new ArrayBuffer(audioData.length * 2 + WAV_HEAD_SIZE),
// 需要用一个view来操控buffer
view = new DataView(buffer);
// 写入wav头部信息
// RIFF chunk descriptor/identifier
this._writeUTFBytes(view, 0, "RIFF");
// RIFF chunk length
view.setUint32(4, 44 + audioData.length * 2, true);
// RIFF type
this._writeUTFBytes(view, 8, "WAVE");
// format chunk identifier
// FMT sub-chunk
this._writeUTFBytes(view, 12, "fmt ");
// format chunk length
view.setUint32(16, 16, true);
// sample format (raw) 格式类型
view.setUint16(20, 1, true);
// stereo (2 channels) 声道数
view.setUint16(22, 2, true);
// sample rate 采样率,每秒样本数,表示每个通道的播放速度
view.setUint32(24, 44100, true);
// byte rate (sample rate * block align)波形数据传输率 (每秒平均字节数)
view.setUint32(28, 44100 * 2, true);
// block align (channel count * bytes per sample)
view.setUint16(32, 2 * 2, true);
// bits per sample
view.setUint16(34, 16, true);
// data sub-chunk
// data chunk identifier
this._writeUTFBytes(view, 36, "data");
// data chunk length
view.setUint32(40, audioData.length * 2, true);
// 写入PCM数据
let length = audioData.length;
let index = 44;
let volume = 1;
for (let i = 0; i < length; i++) {
view.setInt16(index, audioData[i] * (0x7fff * volume), true);
index += 2;
}
return buffer;
}
// 创建一个wav文件
_writeUTFBytes(view, offset, string) {
var lng = string.length;
for (var i = 0; i < lng; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
// 上传录音到百度并检查结果data, 发送
sendAudioCheck(callback) {
let data =this.wavBuffer
let form = new FormData();
let newData = new Blob([new Uint8Array(data)], { type: "audio/wav" });
form.append("arrayBuffer", newData);
let xhr = new XMLHttpRequest();
xhr.open("post", url, true);
xhr.send(form);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
let res = xhr.responseText;
let index = res.search(/{/);
console.log(res);
res = JSON.parse(res.slice(index)).result[0];
console.log(res);
let ans = { ans: "我不清楚,请再说一遍", index: 0 };
// if (res) ans = dialog.getAnswer(res);
ans = { ans: "暂不回答", index: 666 };
this.txt = ans;
if (callback && typeof callback === "function")
callback(res, this.txt);
}
}
};
}
}
export { Recorder };
/components/robot/recorder.d.ts
declare class Recorder {
beginRecorder(): void;
stopRecorder(): void;
sendAudioCheck(callback: Function):void;
}
export { Recorder };
在vue中的使用: Recorder.vue
<script setup lang="ts">
import { ref } from 'vue'
import { Recorder } from '../robot/recorder'
// 录音
let startRecorder = ref(false)
// 语音机器人
let recorder = new Recorder()
let startTxt = ref("开始录音");
let clickLock = false // 出结果后再允许触发开始录音事件
function startRecorderFn() {
if (clickLock) return
clickLock = true
startTxt.value = '处理中...'
startRecorder.value = true
recorder.beginRecorder()
}
function stopRecorderFn() {
startRecorder.value = false
recorder.stopRecorder(recorderRes)
}
// 语音识别结果回调
/**
*
* @param que 根据语音生成的文本内容
* @param res 根据文本内容,找到答案,暂无
*/
function recorderRes(que: string, res: any) {
if (!que) que = '...'
setTimeout(() => {
console.log(que);
startTxt.value = '开始录音'
clickLock = false
}, 600)
}
</script>
<template>
<div>
<button @click="startRecorderFn">{{startTxt}}</button>
<button @click="stopRecorderFn">停止录音</button>
</div>
</template>
<style lang="less" scoped></style>
引用:
前端语音转文字实践总结
前端js实现asr(语音转文字)
MDN中的 MediaDevices.getUserMedia()
MDN中的SpeechRecognition
百度的语音识别(收费)
如果这篇文章对你有用,多多点赞收藏!!!