👣2023年了,还有人不会语音转文字吧(js)

4,259 阅读6分钟

一、Web语音API - SpeechRecognition

SpeechRecognition 的接口 Web 语音 API 是识别服务的控制器接口;它还处理 SpeechRecognitionEvent 从识别服务发送。

注意: 在某些浏览器(如 Chrome)上,在网页上使用语音识别会涉及基于服务器的识别引擎。音频将发送到 Web 服务进行识别处理,因此它无法脱机工作。

更多详情可以查看MDN : developer.mozilla.org/en-US/docs/…

这种方式的话需要考虑这种方式的兼容性问题,有些浏览器可以不太兼容。

图片.png

这里列出他的一些常用的属性事件和方法: 属性:

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

百度的语音识别(收费)

56ac935951ab4af78bf31af55b600d95.jpg

如果这篇文章对你有用,多多点赞收藏!!!