onFrameRecord 获取实时pcm 音频流,实现音频播放和上传

背景:

        实现移动端录制音频,音频上传与播放,使用原生app 对外提供的录音api (原生的h5 getUserMedia,受app 的内核浏览器限制 不支持 getUserMedia方法,同时流行的插件 js-audio-record 与 record-core pc端调试支持,移动端不支持。

app 提供的api 接口类似于 字节跳动对外提供的接口 但是具体获取数据格式是否一致未知

字节跳动录音 onfframeRecord 相关api 调用链接icon-default.png?t=N7T8https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/api/media/record/recorder-manager/recorder-manager-on-frame-recorded

遇到问题:与常规插件直接获取MP3,WAV 的音频文件不同,onframeRecord 获取的是二进制流式pcm 音频,与getUserMedia 获取的音频还又区别

问题一:不可以像MP3 或wav 文件一样直接使用audio 进行播放

问题二:实时获取的数据流无法直接像非流式文件一样 使用blob 对象上传。

尝试解决思路:

1.网络现有的将pcm 转换成wav格式 的方法 ,使用方法转换文件类型,方便上传,与播放

        最终结果:尝试失败;

        原因:app 封装的的api onframeRecord 获取的音频流与原生getUserMedia获取的音频流有出入,直接添加wav文件头,播放后是白噪音。

2.引入pcmPlayer 播放器 播放实时音频流

        最终结果:成功播放实时音频流,但是要将数据上传后 在获取音频流再次播放还无法解决

3.通过将实时音频流使用jsZip 压缩后作为zip 文件上传给后端,之后在获取该文件进行解压 播放

        最终结果:失败,解压失败

4.不进行后台上传直接将实时音频流push 到一个变量中然后作为参数传给接口,再次获取时从接口中获取

        最终结果:失败

        原因:不可行:1音频文件过大,2,文件流存入数组中传给后台,后台无法识别,保存失败返回值为空。

5.使用unit8Array将arrayBuffer 类型的音频流可视化,在转为字符串按照方式4的方法作为参数传给后台。

        最终结果:失败

        原因:从后台获取字符串后要转换为unit8array 再从unit8Array转换为buffer 整个过程繁琐,转化存在乱码,播放失败。同时也存在参数内存过大问题

6.通过各种尝试后,想到通过变量数据接收所有的音频流然后将他转换为txt的文件 通过blob 上传给后台,再通过下载接口,通过获取的url 获取该txt文件,读取该文件返回的数据流,然后再次使用pcmPlayer 播放。

        最终结果:成功,既解决了大文件上传问题,也解决了上传后播放的问题。

部分开发代码:

<button  
    @touchstart.prevent="handelLongPress"
    @touchend.prevent="touchend"
></button>

//长按录音,松开停止

 

// 长按录音开始
const handelLongPress = () => {
  clearTimeout(loop.value);
  longTouch.value = false;
  loop.value = setTimeout(() => {
    longTouch.value = true;
    handRecordAudio();
  }, 300);
};

// 录音
const handRecordAudio = () => {
  //录音监听
  eventInfo.value = toongine.recorder.onFrameRecorded({
    callback: (res) => {
      if (res.data.isLastFrame) {
        console.log(
          "toongine::recorder::onFrameRecorded::ended",
          "回调获取数据结束",
        );
        if (recordedChunks.value.length) {
          //作为txt 文件上传
          const txtBlob = new Blob(recordedChunks.value, {
            type: "text/plain",
          });
          let fileOfBlob = new File(
            [txtBlob],
            `录音${new Date().getTime()}` + ".txt"
          );
          const formData = new FormData();
          formData.append("multipartFiles", fileOfBlob);

           //上传接口
          uploadFile(formData).then((res) => {
            if (res.code == 200) {
              recordedChunks.value = [];
              audioFileList.value.push(res.data[0]);
            } else {
              showToast("语音上传失败");
            }
          });
          return;
        }
      }
      // 操作数据需要使用typed array view 或 DataView
      // var pcm = new Int16Array(res.data.frameBuffer);

    
      let pcmData = res.data.frameBuffer;
      let list = [...recordedChunks.value];
      list.push(pcmData);
      recordedChunks.value = list;
    },
  });

//录音开始
  toongine.recorder.start({
    params: {
      sampleRate: 32000,
      numberOfChannels: 2,
      frameSize: 1,
      bitsPerChannel: 16,
      format: "PCM",
    },
    callback: (res) => {
      console.log("录音1开始");
    },
  });
};



// 长按录音松开
const touchend = () => {
  clearTimeout(loop.value); // 清空定时器,防止重复注册定时器

  if (!longTouch.value) {
    //如果不是长按,执行点击事件
    console.log("点击");
  } else {
    toongine.recorder.stop({
      callback: (res) => {
        //移除录音事件监听
        toongine.removeEventListener(eventInfo.value);
        console.log("recordedChunks.value", recordedChunks.value);
        if (!recordedChunks.value.length) {
          showToast("录音失败,为获取到音频文件");
        }
      },
    });
  }


};


//录音播放
const playRecord = (item) => {
  console.log("播放", item);
  if (item.fileUrl) {
    axios
      .get("/api/disposal/oss/file/download", {
        params: {
          fileUrl: item.fileUrl,
        },
        responseType: "blob", //定义返回数据格式为Blob
      })
      .then((res) => {
        console.log("返回值blob", res, res.data);
        let reader = new FileReader();
        reader.onload = function (e) { //读取获取到的 txt 文件
          // let zipFile = e.target.result[0];
          console.log("e", e.target.result);
          pcmPlay.value.feed(e.target.result);
        };

        reader.readAsArrayBuffer(res.data);
      });
  } else {
    showToast("播放失败");
  }
};

注意点:

1.引入pcm-player播放器

2.获取txt 流文件时要设置相应类型为blob ,然后读取文件流进行播放

3.pcmPlayer 播放器对应得音频 采样数,声道数等信息得设置最好与 调用录音start方法得参数保持一致,以防止声音播放异常

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值