项目:对话系统。TTS和ASR流前端工程问题总结


前言

demo模拟线上客户通话,实现tts播报和asr语音转文本,最终将tts和asr播放的文本进行存储或下载。

相关知识:

        (1)网页媒体权限授权

        (2)ArrayBuffer、TypeBuffer 截断、拼接

        (3)音频流式文件播放方式

        (4)ASR、TTS

工程相关:vue2

一、网页授权媒体

这个之前有写过

//  方法就一个
    openMedia() {
      const that = this;
      const gotMediaStream = () => {
        that.getMedia = true;
        //这里是开始websocket的方法
        that.useWebSocket();
      };
      const handleError = (err) => {
        // console.log("navigator catch error", err);
        that.$message.error("未获录音权限");
      };
      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
        // console.log("getusermedia is not supported!");
        that.$message.error("无录音权限");
      } else {
        const constrants = {
          audio: {
            volume: { min: 0.0, max: 1.0 },
            noiseSuppression: false,
            echoCancellation: false,
          },
        };
        navigator.mediaDevices
          .getUserMedia(constrants)
          .then(gotMediaStream)
          .catch(handleError);
      }
    },
    // 初始化的websocket
    useWebSocket() {
      const that = this;
      asrWs = new WebSocket(process.env.VUE_APP_WS_URL);
      asrWs.binaryType = "arraybuffer"; // 传输的是 ArrayBuffer 类型的数据
      asrWs.onopen = function () {
        if (asrWs.readyState === 1) {
          // 开启体验和心跳 但是未开始录音
          // that.experienceStatus = true;
          that.heartStart();
        }
      };
      asrWs.onmessage = function (msg) {
       //这里就是相关的业务逻辑了
        that.heartStart();
      };
      asrWs.onclose = function (err) {
        console.log(err);
      };

      asrWs.onerror = function (err) {
        that.heartStart();
      };
    },

 修改了一些配置,这个配置解决录音音量较小的问题。

        navigator.mediaDevices
          .getUserMedia({audio:true })
          .then(gotMediaStream)
          .catch(handleError);

二、ArrayBuffer、TypeBuffer 截断、拼接

首先,这个 ArrayBuffer 类型化数组,类型化数组是JavaScript操作二进制数据的一个接口。最初为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式的背景下诞生的。

ArrayBuffer

var bf = new ArrayBuffer(40); // 生成了字节长度为40的内存区域
//通过提供的 byteLength 属性返回分配字节的长度
console.log(bf.byteLength);  // 40
/*
    值得注意的是如果要分配的内存区域很大,有可能分配失败(因为没有那么多的连续空余内存),所以有必要检查是否分配成功。
*/

ArrayBuffer对象有一个slice方法,允许将内存区域的一部分,拷贝生成一个新的ArrayBuffer对象。 

const bf = new ArrayBuffer(40);
const newBf = bf.slice(0, 10); // 从0 - 9 不包括 10

上面代码拷贝buffer对象的前10个字节,生成一个新的ArrayBuffer对象。slice方法其实包含两步,第一步是先分配一段新内存,第二步是将原来那个ArrayBuffer对象拷贝过去。
slice方法接受两个参数,第一个参数表示拷贝开始的字节序号,第二个参数表示拷贝截止的字节序号。如果省略第二个参数,则默认到原ArrayBuffer对象的结尾。
除了slice方法,ArrayBuffer对象不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。

TypeBuffer  

Int8Array:8位有符号整数,长度1个字节。
Uint8Array:8位无符号整数,长度1个字节。
Int16Array:16位有符号整数,长度2个字节。
Uint16Array:16位无符号整数,长度2个字节。
Int32Array:32位有符号整数,长度4个字节。
Uint32Array:32位无符号整数,长度4个字节。
Float32Array:32位浮点数,长度4个字节。
Float64Array:64位浮点数,长度8个字节。
 

下面这个文本有相关方法 

JavaScript 之 ArrayBuffer_短暂又灿烂的的博客-CSDN博客

我的需求是将多个ArrayBuffer完成拼接

二、ASR/TTS是什么?

ASR:语音转文本,将语音转成对应的文本,然后做其他处理。

有http方式和websocket方式。

http方式

传送还是file,file属于blob 使用FormData

export function updataBlob(data) {
  let formData = new FormData();
  formData.append("file", data);
  formData.append("chatId", store.getters.chatId);
  return request({
    url: "model/audio/upload",
    method: "post",
    data: formData,
    headers: {
        "Content-Type": "multipart/form-data",
      },
  });
}

 这种方式需要考虑是否为PCM和还是WAV格式的问题,后端解析需不需要加头

websocket这种方式

这种方式实时传送音频,对音频进行了切片。之前文章中也有代码。
https://mp.csdn.net/mp_blog/creation/editor/124618257

TTS是什么

tts是将文本转成语音。
涉及语音就涉及播放。

(1)如果是一次性返回pcm格式文件,需要播放就需要加上头,wav格式。播放TTS返回的文件

添加依赖

    "wav-headers": "^1.0.1"

 var getFileHeaders = require("wav-headers");

 添加wav请求头

    generateWav(buffer) {
      var options = {
        channels: 1,
        sampleRate: 16000,
        bitDepth: 16,
        dataLength: buffer.length,
      };
      var headersBuffer = getFileHeaders(options);

      var temp = new Uint8Array(buffer.byteLength + headersBuffer.byteLength);
      temp.set(new Uint8Array(headersBuffer), 0);
      temp.set(new Uint8Array(buffer), headersBuffer.byteLength);
      return temp;
    },

 上传文件 new Blob([this.PCMList], { type: "audio/wav" });  记住有个[ ]  本来this.PCMList 就是个TypeArray。

下面的生成wav文件下载。

    upAudioOne() {
      store.commit("audio/SET_AUDIO_CUT_SIZE", -1);
      this.PCMList = this.generateWav(this.PCMList);
      const blob = new Blob([this.PCMList], { type: "audio/wav" });
      updataBlob(blob);

      // let blobUrl = window.URL.createObjectURL(blob);
      // let link = document.createElement('a')
      // link.style.display = 'none'
      // link.href = blobUrl
      // link.download = 'test' + '.wav'
      // document.body.appendChild(link)
      // link.click()

    },

 这个是生成wav文件 直接下载或播放

      if (Object.prototype.toString.call(data) == "[object Object]") {
        this.$message({
          message: data.message,
          type: "warning",
          duration: 3 * 1000,
        });
      } else {
        if (type) {
          this.cunrentAudioUrl = URL.createObjectURL(data);
          this.$refs.audio.volume = 0.1;
        } else {
          const reader = new FileReader();
          reader.readAsDataURL(data);
          reader.onload = (e) => {
            const a = document.createElement("a");
            a.download = item.id + ".wav";
            a.href = e.target.result;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
          };
        }
      }

 (2) 如果返回的是流式文件就需要将文件合成拼接在一起 记录TTS返回音频

    concatenate(resultConstructor, ...arrays) {
      let totalLength = 0;
      let startIndx = 0;
      if (!arrays[0].length) {
        this.PCMListAllIndex = [];
      } else {
        startIndx = arrays[0].length;
      }
      for (let arr of arrays) {
        totalLength += arr.length;
      }
      let result = new resultConstructor(totalLength);
      let offset = 0;
      for (let arr of arrays) {
        result.set(arr, offset);
        offset += arr.length;
      }
      this.PCMListAllIndex.push({ start: startIndx, end: totalLength });
      return result;
    },

 使用方法合成

     var dataAudio = new Uint8Array(msg.data);

          if (that.ttsSart) {

            that.PCMList = that.concatenate(

              Uint8Array,

              that.PCMList,

              dataAudio

            );

  }

 TTS流式播放

返回是ArrayBuffer ,这个写了看了一个第三方的做了修改

var bufferSource = null;

class PCMPlayer {
  constructor(option) {
    this.init(option);
  }
  // 初始化
  init(option) {
    // 默认参数
    const defaultOption = {
      inputCodec: "Int16", // 传入的数据是采用多少位编码,默认16位
      channels: 1, // 声道数
      sampleRate: 8000, // 采样率 单位Hz
      flushTime: 3000, // 缓存时间 单位 ms
    };
    this.option = Object.assign({}, defaultOption, option); // 实例最终配置参数
    this.samples = new Float32Array(); // 样本存放区域
    this.audioCtx = null;
    this.convertValue = this.getConvertValue();
    // 获取类型
    this.typedArray = this.getTypedArray();
    // 初始下音频上线文本
    this.initAudioContext();
    // 需要播放资源统计
    this.sourceCount = 0;
    // pcm二进制文件播放次数
    this.bufferSourceEndCount = 0;
    // 一次文本二进制文件全部返回会  与tts ws中的 speech_end 相关 设置成false
    this.bufferFeed = false;
  }
  // 设置 tts 传入状态, true: 传送中, false :未开始|传送结束
  setBufferFeedState(type = false) {
    this.bufferFeed = type;
  }
  // 创建浮点型数据数组
  // 初始某人值
  sourceLenInit() {
    this.samples = new Float32Array();
    // console.log("初始化=》被调用");
    this.sourceCount = 0;
    this.setBufferFeedState(true);
    this.bufferSourceEndCount = 0;
  }
  // 播放暂停 初始化参数值
  ttsClose() {
    this.sourceCount = 0;
    this.setBufferFeedState();
    this.bufferSourceEndCount = 0;
    if (bufferSource) {
      bufferSource.stop(0); //立即停止
    }
    this.destroy();
  }
  // 获取不同TypeArray类型的转化
  getConvertValue() {
    const inputCodecs = {
      Int8: 128,
      Int16: 32768,
      Int32: 2147483648,
      Float32: 1,
    };
    if (!inputCodecs[this.option.inputCodec])
      throw new Error(
        "wrong codec.please input one of these codecs:Int8,Int16,Int32,Float32"
      );
    return inputCodecs[this.option.inputCodec];
  }
  //获取TypeArray
  getTypedArray() {
    // 根据传入的目标编码位数
    // 选定前端的所需要的保存的二进制数据格式
    // 完整TypedArray请看文档
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
    const typedArrays = {
      Int8: Int8Array,
      Int16: Int16Array,
      Int32: Int32Array,
      Float32: Float32Array,
    };
    if (!typedArrays[this.option.inputCodec])
      throw new Error(
        "wrong codec.please input one of these codecs:Int8,Int16,Int32,Float32"
      );
    return typedArrays[this.option.inputCodec];
  }
  //初始化音频上下文
  initAudioContext() {
    this.sourceCount = 0;
    this.bufferSourceEndCount = 0;
    this.setBufferFeedState();
    // 初始化音频上下文的东西
    this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    // 控制音量的 GainNode
    // https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createGain
    //创建音量控制节点
    this.gainNode = this.audioCtx.createGain();
    this.gainNode.gain.value = 0.1;
    this.gainNode.connect(this.audioCtx.destination);
    this.startTime = this.audioCtx.currentTime;
  }
  //  检测 是否为TypeArray
  static isTypedArray(data) {
    // 检测输入的数据是否为 TypedArray 类型或 ArrayBuffer 类型
    return (
      (data.byteLength &&
        data.buffer &&
        data.buffer.constructor == ArrayBuffer) ||
      data.constructor == ArrayBuffer
    );
  }
  // 是否为 ArrayBuffer 或者 TypedArray
  isSupported(data) {
    // 数据类型是否支持
    // 目前支持 ArrayBuffer 或者 TypedArray
    if (!PCMPlayer.isTypedArray(data))
      throw new Error("请传入ArrayBuffer或者任意TypedArray");
    return true;
  }
  // pcm二进制文件输入(一句话多个二进制文件输入)
  feed(data) {
    if (!this.audioCtx) {
      return;
    }
    this.isSupported(data);

    // 获取格式化后的buffer
    data = this.getFormatedValue(data);
    // 开始拷贝buffer数据
    // 新建一个Float32Array的空间
    const tmp = new Float32Array(this.samples.length + data.length);
    // console.log(data, this.samples, this.samples.length)
    // 复制当前的实例的buffer值(历史buff)
    // 从头(0)开始复制
    tmp.set(this.samples, 0);

    // 复制传入的新数据
    // 从历史buff位置开始
    tmp.set(data, this.samples.length);

    // 将新的完整buff数据赋值给samples
    // interval定时器也会从samples里面播放数据
    this.samples = tmp;

    // 播放和回调的相关操作
    this.flush();
  }
  // 获取 对应类型数据的方法
  getFormatedValue(data) {
    if (data.constructor == ArrayBuffer) {
      data = new this.typedArray(data);
    } else {
      data = new this.typedArray(data.buffer);
    }

    let float32 = new Float32Array(data.length);

    for (let i = 0; i < data.length; i++) {
      // buffer 缓冲区的数据,需要是IEEE754 里32位的线性PCM,范围从-1到+1
      // 所以对数据进行除法
      // 除以对应的位数范围,得到-1到+1的数据
      // float32[i] = data[i] / 0x8000;
      float32[i] = data[i] / this.convertValue;
    }
    return float32;
  }
  // 音量控制
  volume(volume) {
    this.gainNode.gain.value = volume;
  }
  // 关闭上线文
  destroy() {
    this.samples = null;
    this.audioCtx.close();
    this.audioCtx = null;
  }

  // 每个音频文件
  flush() {
    const self = this;

    if (this.samples.length) {
      this.sourceCount += 1;
    }

    if (!this.samples.length) {
      return;
    }
    if (!this.audioCtx) {
      return;
    }
    bufferSource = this.audioCtx.createBufferSource();

    // 两个方法的回调
    if (
      typeof this.option.onended === "function" &&
      typeof this.option.allPlayed === "function"
    ) {
      bufferSource.onended = function (event) {
        self.bufferSourceEndCount += 1;
        self.option.allPlayed(
          self,
          self.bufferSourceEndCount == self.sourceCount && !self.bufferFeed
        );
        self.option.onended(this, event);
      };
    }

    const length = this.samples.length / this.option.channels;
    const audioBuffer = this.audioCtx.createBuffer(
      // 声道数
      this.option.channels,
      // 长度
      length,
      // 采样率
      this.option.sampleRate
    );
    // 多声道处理
    for (let channel = 0; channel < this.option.channels; channel++) {
      const audioData = audioBuffer.getChannelData(channel);
      let offset = channel;
      let decrement = 50;
      for (let i = 0; i < length; i++) {
        audioData[i] = this.samples[offset];
        /* fadein */
        if (i < 50) {
          audioData[i] = (audioData[i] * i) / 50;
        }
        /* fadeout*/
        if (i >= length - 51) {
          audioData[i] = (audioData[i] * decrement--) / 50;
        }
        offset += this.option.channels;
      }
    }

    if (this.startTime < this.audioCtx.currentTime) {
      this.startTime = this.audioCtx.currentTime;
    }
    // console.log(
    //   "start vs current " +
    //     this.startTime +
    //     " vs " +
    //     this.audioCtx.currentTime +
    //     " duration: " +
    //     audioBuffer.duration
    // );
    bufferSource.buffer = audioBuffer;
    bufferSource.connect(this.gainNode);
    bufferSource.start(this.startTime);
    // 添加开始播放的回调函数
    if (this.sourceCount == 1) {
      if (typeof this.option.firstPlay === "function") {
        self.option.firstPlay(this, Date.now());
      }
    }
    this.startTime += audioBuffer.duration;
    this.samples = new Float32Array();
  }
  // 暂停
  async pause() {
    await this.audioCtx.suspend();
  }
  // 继续播放
  async continue() {
    await this.audioCtx.resume();
  }
  // 绑定音频上线文的的状态 - 未使用
  bindAudioContextEvent() {
    const self = this;
    if (typeof self.option.onstatechange === "function") {
      this.audioCtx.onstatechange = function (event) {
        self.option.onstatechange(this, event, self.audioCtx.state);
      };
    }
  }
  // 获取音频上下文的状态
  getSate() {
    const self = this;
    return self.audioCtx.state;
  }
}

export default PCMPlayer;

使用页面

typeBuffer 加个wav 头用于播放 和上产后台

npm install wav-headers --save

 

var ws = null; // 实现WebSocket
import PCMPlayer from "./pcm-player.js";
var getFileHeaders = require("wav-headers");
export default {
  data() {
    return {
      timeoutTTs: 6000, // 6s发一次心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
      ttsTimeoutObj: null, // ws定时器
      ttsSart: false,
      ttsPlayStatus: false,
      player: null,
      PCMList: new Uint8Array(0),
    };
  },
  created() {
    // console.log('获取摄像头和麦克风权限')
  },

  mounted() {
    //初始化 PCMPlayer
    this.play();
  },
  beforeDestroy() {
    this.closeServe();
    this.ttsStop();
  },

  methods: {
    // 设置wav 头
    generateWav(buffer) {
      var options = {
        channels: 1,
        sampleRate: 16000,
        bitDepth: 16,
        dataLength: buffer.length,
      };
      var headersBuffer = getFileHeaders(options);

      var temp = new Uint8Array(buffer.byteLength + headersBuffer.byteLength);
      temp.set(new Uint8Array(headersBuffer), 0);
      temp.set(new Uint8Array(buffer), headersBuffer.byteLength);
      return temp;
    },
    // 拼接
    concatenate(resultConstructor, ...arrays) {
      let totalLength = 0;
      for (let arr of arrays) {
        totalLength += arr.length;
      }
      let result = new resultConstructor(totalLength);
      let offset = 0;
      for (let arr of arrays) {
        result.set(arr, offset);
        offset += arr.length;
      }
      return result;
    },

    // 初始化 PCMPlayer
    play() {
      const that = this;
      if (this.player) return;
      this.player = new PCMPlayer({
        encoding: "16bitInt",
        channels: 1,
        sampleRate: 16000,
        // 所有cpm 播放结束
        allPlayed: function (r, data) {
          console.log("allPlayed", data);
        },
        // 每个pcm 播放结束
        onended: function (r) {
          
        },
        // 音频上线状态
        onstatechange: function (r) {
    
        },
      });
      if (!ws) this.useTTSSocket();
    },

    // 播放暂停 用于打断使用
    ttsPlayEnd() {
      this.player.ttsClose();
      this.ttsPlayStatus = false;
    },
    // 摧毁状态
    ttsStop() {
      this.player.destroy();
      this.ttsPlayStatus = false;
      this.player = null;

    },

    // 关闭tts服务
    closeServe() {
      if (ws) {
        ws.close(); // 离开路由之后断开websocket连接
        ws = null;
      }
      // 2、通过关闭计时器和倒计时关闭心跳监测
      this.heartTTSReset();
    },

    // 心跳初重置
    heartTTSReset: function () {
      clearInterval(this.ttsTimeoutObj);
    },

    // 心跳初始化
    heartTTSStart() {
      const that = this;
      this.ttsTimeoutObj && clearInterval(this.ttsTimeoutObj);
      this.ttsTimeoutObj = setInterval(function () {
        if (ws.readyState === 1) {
          // console.log('连接状态,发送消息保持连接' + new Date())
          ws.send(JSON.stringify({ signal: "heart_beat" }));
          that.heartTTSStart();
        } else {
          that.useTTSSocket();
        }
      }, that.timeoutTTs);
    },
    // 初始化的websocket
    useTTSSocket() {
      const that = this;
      ws = new WebSocket("ws:/xx.xx.xx.xx:8000");
      // 传输的是 ArrayBuffer 类型的数据
      ws.binaryType = "arraybuffer"; 
      ws.onopen = function () {
        if (ws.readyState === 1) {
          that.heartTTSStart();
        }
      };
      ws.onmessage = function (msg) {
  
        if (!that.isArrayBuffer(msg.data)) {
          const backMsg = JSON.parse(msg.data);
          if (backMsg.message === "speech_start") {
            that.PCMList = [];
            that.player && that.player.sourceLenInit();
            that.ttsSart = true;
          }
          if (backMsg.message === "timestamp") {
            if (that.ttsSart) {
              that.setTxtProgress(backMsg.syllable_times);
            }
            // console.log("播放中");
          }

          if (backMsg.message === "speech_end") {
            that.ttsSart = false;
            that.player.bufferFeed = true;
            that.player && that.player.setBufferFeedState(false);
          }
        } else {
          const dataAudio = new Uint8Array(msg.data);
          if (that.ttsSart) {
            that.ttsPlayStatus = true;
            that.PCMList = that.concatenate(
              Uint8Array,
              that.PCMList,
              dataAudio
            );
            that.player.feed(dataAudio);
          }
        }
        that.heartTTSStart();
      };
      ws.onerror = function (err) {
        that.heartTTSStart();
      };
    },
    // 判断返回是否为 ArrayBuffer
    isArrayBuffer(data) {
      const strType = Object.prototype.toString.call(data);
      return strType.includes("ArrayBuffer");
    },
    // 开始发送文本
    startTTS(text) {
      const that = this;
      this.player.initAudioContext();
      setTimeout(() => {
        ws.send(
          JSON.stringify({
            text: text,
            speaker: "xiaoqian",
            pitch: 70,
            volume: 80,
          })
        );
      }, 0);
    },
  },
};

audioBuffer转wav格式的方式

npm install audiobuffer-to-wav --save

截断获取

 
  watch: {
    //播放暂停状态
    ttsPlayStatus: {
      handler(val) {
        if (val) {
          this.playLongTime = Date.now();
        } else {
        //获取播放时间
          this.playLongTime = Date.now() - this.playLongTime;
          //这里很重要 decodeAudioData 需要的arrayBuffer是需要 有头的wav MP3都是可以
          //上一个 调用实现方法中 有PCMList  获取方式
          this.PCMList = this.generateWav(this.PCMList);
          // 调用截断的方法
          this.audioSilce(this.PCMList, this.playLongTime / 1000);
        }
      },
    },
  },
  
    //音频截断
    audioSilce(arrBuffer, time) {
       //兼容写法 
      // window.AudioContext =
      //   window.AudioContext ||
      //   window.webkitAudioContext ||
      //   window.mozAudioContext ||
      //   window.msAudioContext;
       //创建音频上下文
      var audioCtx = new window.AudioContext();

      // var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      //异步解析 ARRaybuffer
      audioCtx.decodeAudioData(arrBuffer.buffer, function (audioBuffer) {
        const channels = audioBuffer.numberOfChannels;
        // 声道数量和采样率
        // var channels = audioBuffer.numberOfChannels;
        const rate = audioBuffer.sampleRate;

        // 获取截断的音频
        const startOffset = 0;
        const endOffset = rate * time;
        // 对应的帧数
        const frameCount = endOffset - startOffset;

        // 创建同样采用率、同样声道数量,长度是前3秒的空的AudioBuffer
        const newAudioBuffer = new AudioContext().createBuffer(
          channels,
          endOffset - startOffset,
          rate
        );
        // 创建临时的Array存放复制的buffer数据
        const anotherArray = new Float32Array(frameCount);
        // 声道的数据的复制和写入
        const offset = 0;
        for (let channel = 0; channel < channels; channel++) {
          audioBuffer.copyFromChannel(anotherArray, channel, startOffset);
          newAudioBuffer.copyToChannel(anotherArray, channel, offset);
        }

       //播放的方法 方便验证
        // setTimeout(() => {
        //   var source = audioCtx.createBufferSource();
        //   source.buffer = newAudioBuffer;
        //   source.connect(gainNode);
        //   source.start(0);
        //   source.onended = function (event) {
        //     console.log("播放结束");
        //   };
        // }, 1000);
        
        const wav = toWav(newAudioBuffer);
        const blob = new Blob([wav], { type: "audio/wav" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.download = "测试.wav";
        a.href = url;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
      });
    },

 

引入js

import PCMPlayer from "./pcm-player.js";

 方法中调用

this.player.sourcePlayLen 和 playLen  是所有 流式的ArrayBuffer播放完成的判断。

原理:

    使用音频上下文及createBufferSource() 实时播放二进制流

    this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();

    bufferSource = this.audioCtx.createBufferSource();   见pcm-player.js源码

      

        每个ArrayBuffer都会播放,每次播放播放会调用 onended方法,记录 每次feed方法的调用的次数(就是sourcePlayLen)和个文件播放完成  onended 次数做个判断。相等就播放完成。

        firstPlay这个回调是 sourcePlayLen= 1的的回调,判断音频播放中状态的回调方法。这些和业务相关,自己可以补充回调方法。

          var dataAudio = new Uint8Array(msg.data);
          if (that.ttsSart) {
            that.PCMList = that.concatenate(
              Uint8Array,
              that.PCMList,
              dataAudio
            );

            that.txtProgreeArr.push(that.sorcePushConut);
            // that.ttsPlayStatus = true;
            that.player && that.player.feed(dataAudio);
            that.sorcePushConut += 1;
          }
    play() {
      const that = this;
      if (this.player) return;
      this.player = new PCMPlayer({
        encoding: "16bitInt",
        channels: 1,
        sampleRate: 16000,
        flushTime: 1000,
        firstPlay: function (r) {
          that.ttsPlayStatus = true;
        },
        onended: function (r) {
          that.playLen += 1;
          if (that.playLen === that.player.sourcePlayLen) {
            that.playLen = 0;
            that.player.sourceLenInit();
            // 正常结束
            that.ttsIsInterrupt = true;
            that.ttsPlayStatus = false;
            that.ttsOnend();
            if (that.chatSate == 4) {
              that.ttsStop();
            }
          }
        },
      })
    },

 主要的几个方法

创建:this.player = new PCMPlayer({})

暂停:this.player.ttsClose();

摧毁:this.player.destroy();

总结

 以上是在项目中,是使用到TTS、ASR不同协议对音频的上传、下载、播放、等功能,用得到请点赞谢谢。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值