vue中浏览器录音,通过audio播放,blob音频流转换。

  • Post author:
  • Post category:vue




html部分

对于后端返回的 blob格式的语音流,前端如何转化成 src路径,使用audio标签进行播放。

HTML代码部分很简单,就是audio标签即可(我这里是基于vue)

所以就是要解决 srcUrl即可,即把Blob语音流转化。



JS部分

首先来认识一个类 —FileReader 。

FileReader对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

其中一个方法readAsArrayBuffer():

FileReader.readAsArrayBuffer() – – 开始读取指定的 Blob中的内容, 一旦完成, result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象.

FileReader.onload:

处理load事件。该事件在读取操作完成时触发。

结合onload事件,可以在 onload 事件中console.log(e),会发现e.srcElement.result是readAsArrayBuffer转化的结果。

// Blobdata 就是后端返回给你的Blob数据

const reader = new FileReader();
reader.readAsArrayBuffer(Blobdata);
reader.onload = (e) => {
   const bufer = e.srcElement.result;
   const blob = this.addWavHeader(bufer, 16000, 16, 1);
   this.srcUrl = window.URL.createObjectURL(blob);
};

this.addWavHeader 函数是给这段buffer 加上头部信息,这样最后在用window.URL.createObjectURL就可以转化成播放路径。

addWavHeader(samples, sampleRateTmp, sampleBits, channelCount) {
      const dataLength = samples.byteLength;
      /* 新的buffer类,预留44bytes的heaer空间 */
      const buffer = new ArrayBuffer(44 + dataLength);
      /* 转为 Dataview, 利用API来填充字节 */
      const view = new DataView(buffer);
      let offset = 0;
      /* ChunkID, 4 bytes,  资源交换文件标识符 */
      this.writeString(view, offset, 'RIFF'); offset += 4;
      /* ChunkSize, 4 bytes, 下个地址开始到文件尾总字节数,即文件大小-8 */
      view.setUint32(offset, /* 32 */ 36 + dataLength, true); offset += 4;
      /* Format, 4 bytes, WAV文件标志 */
      this.writeString(view, offset, 'WAVE'); offset += 4;
      /* Subchunk1 ID, 4 bytes, 波形格式标志 */
      this.writeString(view, offset, 'fmt '); offset += 4;
      /* Subchunk1 Size, 4 bytes, 过滤字节,一般为 0x10 = 16 */
      view.setUint32(offset, 16, true); offset += 4;
      /* Audio Format, 2 bytes, 格式类别 (PCM形式采样数据) */
      view.setUint16(offset, 1, true); offset += 2;
      /* Num Channels, 2 bytes,  通道数 */
      view.setUint16(offset, channelCount, true); offset += 2;
      /* SampleRate, 4 bytes, 采样率,每秒样本数,表示每个通道的播放速度 */
      view.setUint32(offset, sampleRateTmp, true); offset += 4;
      /* ByteRate, 4 bytes, 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */
      view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset += 4;
      /* BlockAlign, 2 bytes, 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */
      view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
      /* BitsPerSample, 2 bytes, 每样本数据位数 */
      view.setUint16(offset, sampleBits, true); offset += 2;
      /* Subchunk2 ID, 4 bytes, 数据标识符 */
      this.writeString(view, offset, 'data'); offset += 4;
      /* Subchunk2 Size, 4 bytes, 采样数据总数,即数据总大小-44 */
      view.setUint32(offset, dataLength, true); offset += 4;
      if (sampleBits === 16) {
        this.floatTo16BitPCM(view, samples);
      } else if (sampleBits === 8) {
        this.floatTo8BitPCM(view, samples);
      } else {
        this.floatTo32BitPCM(view, samples);
      }
      return new Blob([view], { type: 'audio/wav' });
    },

DataView

有关 new dataView对象的全部详解,请看官方文档:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/DataView

以下是我的具体代码:

writeString(view, offset, string) {
      for (let i = 0; i < string.length; i += 1) {
        view.setUint8(offset + i, string.charCodeAt(i));
      }
    },
floatTo32BitPCM(output, input) {
      const oinput = new Int32Array(input);
      let newoffset = 44;
      for (let i = 0; i < oinput.length; i += 1, newoffset += 4) {
        output.setInt32(newoffset, oinput[i], true);
      }
    },
floatTo16BitPCM(output, input) {
      const oinput = new Int16Array(input);
      let newoffset = 44;
      for (let i = 0; i < oinput.length; i += 1, newoffset += 2) {
        output.setInt16(newoffset, oinput[i], true);
      }
    },
floatTo8BitPCM(output, input) {
      const oinput = new Int8Array(input);
      let newoffset = 44;
      for (let i = 0; i < oinput.length; i += 1, newoffset += 1) {
        output.setInt8(newoffset, oinput[i], true);
      }
    },



前端播放后端传的语音流

前端播放处理方式:

1 . 直接使用audio标签,src输入后端接口路径

2. 请求失败需要添加提示,对数据处理 ,转为blob数据

// 1. 请求时添加 responseType: 'blob',这样返回数据为blob数据
const res = await axios({
  method: 'get',
  url: '/api/123',
  responseType: 'blob'
});
 
// 2. 使用window.URL.createObjectURL
let url = undefined
// 若返回数据为application/json 格式说明文件获取失败,此时需要提示用户
if (res.type == 'application/json') {
     const reader = new FileReader();
     reader.readAsText(res, 'utf-8');
     reader.onload = () => {
         const { msg } = JSON.parse(reader?.result as string);
         message.error(msg)
     }
} else {
    url = window.URL.createObjectURL(res)
}
 
// 3. 组件卸载
if(url){
    window.URL.revokeObjectURL(url)
}



版权声明:本文为tsqjxy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。