在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>