动态

详情 返回 返回

前端H5錄音實時幀回調-波形配置- Recorder.js - 动态 详情


在H5端我們必須使用canvas作為容器來顯示波紋效果,這在官網上有詳細説明

這裏我定義了一個waveConfig變量用作波形的配置,具體的參數下文寫的很詳細,需要注意一點,compatibleCanvas參數綁定的是canvas容器,而且得是在canvas掛載後才能綁定。

// 創建音頻可視化圖形繪製對象
waveConfig.compatibleCanvas = recwave.value;
wave = Recorder.FrequencyHistogramView(waveConfig);


open後給waveConfig綁定canvas容器,然後通過Recorder.FrequencyHistogramView方法繪製波形。
波形的繪製就3
1、引入波形配置

// 加載frequency.histogram.view的波形配置
import 'recorder-core/src/extensions/frequency.histogram.view';
// frequency.histogram.view的波形配置的必要文件lib.fft
import 'recorder-core/src/extensions/lib.fft';

2、在創建錄音對象的實時回調onProcess種添加

  // 創建錄音對象
  rec = Recorder({
    type: 'unknown', //這裏特意使用unknown格式,方便清理內存
    onProcess: (buffers: any, powerLevel: any, _: any, bufferSampleRate: any) => {
      // 這一步實時更新波形
      if (wave) {
        wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
      }
    }
  });

3、掛載波形

  // 打開錄音,獲得權限
  rec.open(
    () => {
      if (recwave.value) {
        // 創建音頻可視化圖形繪製對象
        // canvas的dom
        // 掛載
        waveConfig.compatibleCanvas = recwave.value;
        wave = Recorder.FrequencyHistogramView(waveConfig);
      }
    }
  );

其它的邏輯基本跟PC端的一致,文檔也有説明,下面是H5拿到PCM實時回調幀的完整代碼,安裝依賴後複製到項目就可以運行了

<template>
  <canvas ref="recwave"></canvas>
</template>

<script setup lang="ts">
import Recorder from 'recorder-core';
// 加載pcm編碼器
import 'recorder-core/src/engine/pcm';
// 加載frequency.histogram.view的波形配置
import 'recorder-core/src/extensions/frequency.histogram.view';
// frequency.histogram.view的波形配置的必要文件lib.fft
import 'recorder-core/src/extensions/lib.fft';

// PCM格式流式上傳,無固定幀
let wave: any; // 用於繪製波形
const recwave = ref(null); // 綁定到dom元素上
let rec: any; // Recorder實例
let send_chunk: any; // 上次分割點數據
let testSampleRate = 16000; // 採樣率

let waveConfig = {
  compatibleCanvas: '', //提供一個兼容H5的canvas對象,需支持getContext("2d"),支持設置width、height,支持drawImage(canvas,...)
  width: 100, //canvas顯示寬度
  height: 20, //canvas顯示高度
  scale: 2, //縮放係數,應為正整數,使用2(3? no!)倍寬高進行繪製,避免移動端繪製模糊

  fps: 20, //繪製幀率,不可過高

  lineCount: 12, //直方圖柱子數量,數量的多少對性能影響不大,密集運算集中在FFT算法中
  widthRatio: 0.5, //柱子線條寬度佔比,為所有柱子佔用整個視圖寬度的比例,剩下的空白區域均勻插入柱子中間;默認值也基本相當於一根柱子佔0.6,一根空白佔0.4;設為1不留空白,當視圖不足容下所有柱子時也不留空白
  spaceWidth: 0, //柱子間空白固定基礎寬度,柱子寬度自適應,當不為0時widthRatio無效,當視圖不足容下所有柱子時將不會留空白,允許為負數,讓柱子發生重疊
  minHeight: 1, //柱子保留基礎高度,position不為±1時應該保留點高度
  position: 0, //繪製位置,取值-1到1,-1為最底下,0為中間,1為最頂上,小數為百分比
  mirrorEnable: true, //是否啓用鏡像,如果啓用,視圖寬度會分成左右兩塊,右邊這塊進行繪製,左邊這塊進行鏡像(以中間這根柱子的中心進行鏡像)

  stripeEnable: false, //是否啓用柱子頂上的峯值小橫條,position不是-1時應當關閉,否則會很醜
  stripeHeight: 3, //峯值小橫條基礎高度
  stripeMargin: 6, //峯值小橫條和柱子保持的基礎距離

  fallDuration: 1000, //柱子從最頂上下降到最底部最長時間ms
  stripeFallDuration: 3500, //峯值小橫條從最頂上下降到底部最長時間ms

  //柱子顏色配置:[位置,css顏色,...] 位置: 取值0.0-1.0之間
  linear: [0, 'rgba(255,255,255,1)', 0.5, 'rgba(255,255,255,1)', 1, 'rgba(255,255,255,1)'],
  //峯值小橫條漸變顏色配置,取值格式和linear一致,留空為柱子的漸變顏色
  stripeLinear: null,

  shadowBlur: 0, //柱子陰影基礎大小,設為0不顯示陰影,如果柱子數量太多時請勿開啓,非常影響性能
  shadowColor: '#bbb', //柱子陰影顏色
  stripeShadowBlur: 0, //峯值小橫條陰影基礎大小,設為0不顯示陰影,-1為柱子的大小,如果柱子數量太多時請勿開啓,非常影響性能
  stripeShadowColor: '', //峯值小橫條陰影顏色,留空為柱子的陰影顏色

  fullFreq: false //是否要繪製所有頻率;默認false主要繪製5khz以下的頻率,高頻部分佔比很少,此時不同的採樣率對頻譜顯示幾乎沒有影響;設為true後不同採樣率下顯示的頻譜是不一樣的,因為 最大頻率=採樣率/2 會有差異
};

//重置環境,每次開始錄音時必須先調用此方法,清理環境
const RealTimeSendReset = () => {
  send_chunk = null;
  wave = null;
};

// 打開錄音,state代表立即開始錄音
const onStart = async () => {
  RealTimeSendReset();
  // 創建錄音對象
  rec = Recorder({
    type: 'unknown', //這裏特意使用unknown格式,方便清理內存
    onProcess: (buffers: any, powerLevel: any, _: any, bufferSampleRate: any) => {
      // 所有的pcm數據queue,緩存採樣率,是否結束
      RealTimeSendTry(buffers, bufferSampleRate, false);
      if (wave) {
        wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
      }
    }
  });
  if (!rec) {
    alert('當前瀏覽器不支持錄音功能!');
    return;
  }
  // 打開錄音,獲得權限
  rec.open(
    () => {
      if (recwave.value) {
        // 創建音頻可視化圖形繪製對象
        waveConfig.compatibleCanvas = recwave.value;
        wave = Recorder.FrequencyHistogramView(waveConfig);
      }
      setTimeout(() => {
        recStart();
      },100)
    },
    (msg: any, isUserNotAllow: any) => {
      // 用户拒絕了錄音權限,或者瀏覽器不支持錄音
      console.log((isUserNotAllow ? 'UserNotAllow,' : '') + '無法錄音:' + msg);
    }
  );
};

const RealTimeSendTry = (buffers: any, bufferSampleRate: any, isClose: any) => {
  //提取出新的pcm數據
  let pcm = new Int16Array(0);
  if (buffers.length > 0) {
    //【關鍵代碼】借用SampleData函數進行數據的連續處理,採樣率轉換是順帶的,得到新的pcm數據
    // send_chunk為上次分割點
    let chunk = Recorder.SampleData(buffers, bufferSampleRate, testSampleRate, send_chunk);
    send_chunk = chunk; // 保存本次分割點,用於下次使用
    pcm = chunk.data; //此時的pcm就是原始的音頻16位pcm數據(小端LE),直接保存即為16位pcm文件、加個wav頭即為wav文件、丟給mp3編碼器轉一下碼即為mp3文件
  }

  //沒有指定固定的幀大小,直接把pcm發送出去即可
  TransferUpload(pcm, isClose);
  return;
};

//=====數據傳輸函數==========
const TransferUpload = (pcmFrame: any, isClose: any) => {
  if (isClose && pcmFrame.length == 0) {
    return; //如果不需要處理最後一幀數據,直接return不做任何處理
  }
  let str = '';
  let bytes = new Uint8Array(pcmFrame.buffer);
  for (let i = 0, L = bytes.length; i < L; i++) str += String.fromCharCode(bytes[i]);
  let base64 = btoa(str);
  // base64數據拿到後就可以隨便你怎麼操作了,比如發送給後端

  //最後一次調用發送,此時的pcmFrame可以認為是最後一幀
  if (isClose) {
  }
};

// 開始錄音
const recStart = () => {
  if (!rec) return console.error('開始:未打開錄音');
  rec.start();
  console.log('已開始錄音');
};
// 結束錄音
const onClose = () => {
  if (!rec) return console.error('結束:未打開錄音');
  rec.close(); // 關閉錄音,釋放錄音資源
  rec = null;
  RealTimeSendTry([], 0, true); //最後一次發送
};

defineExpose({
  onStart,
  onClose
});
</script>

參考文檔:
open_book:Extensions - 插件文檔
FrequencyHistogramView插件

user avatar lhsuo 头像 evenboy 头像
点赞 2 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.