前端语音数据处理
1. 初始化录音设备
async function initVoiceRecorder() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const constrains = {
audio: {
sampleRate: 16000,
sampleSize: 16,
channelCount: 1
}
}
const mediaStream = await navigator.mediaDevices.getUserMedia(constrains)
mediaRecorder = new MediaRecorder(mediaStream, {mimeType: 'audio/webm;codec=pcm'})
mediaRecorder.audioChannels = 1; // 单声道
mediaRecorderReady.value = true
} else {
console.error('getUserMedia is not supported')
}
}
2. 重采样
async function webmToPcm(webmData) {
const bitDepth = 16
let audioContext = new AudioContext({sampleRate: 16000})
let audioBuffer = await audioContext.decodeAudioData(webmData)
let channelData = audioBuffer.getChannelData(0)
let byteLength = bitDepth / 8 * channelData.length
let pcmData = new ArrayBuffer(byteLength)
let pcmDataView = new DataView(pcmData)
for (let i = 0; i < channelData.length; i++) {
let sample = Math.max(-1, Math.min(1, channelData[i]))
let intSample = sample * ((1 << (bitDepth - 1)) - 1)
for (let j = 0; j < bitDepth / 8; j++) {
pcmDataView.setUint8(i * bitDepth / 8 + j, (intSample >> (8 * j)) & 0xff)
}
}
return pcmData
}
3. 数据分块
function splitAudio(audioBlob, chunkSize) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async () => {
let webmData = reader.result
let pcmData = await webmToPcm(webmData)
let audioFrameChunks = []
for (let i = 0; i < pcmData.byteLength; i += chunkSize) {
const chunk = pcmData.slice(i, Math.min(i + chunkSize, pcmData.byteLength))
audioFrameChunks.push(chunk)
}
resolve(audioFrameChunks)
}
reader.onerror = reject
reader.readAsArrayBuffer(audioBlob)
})
}
4. 声音录制
function startRecording() {
let recordedChunks = []
mediaRecorder.ondataavailable = event => {
recordedChunks.push(event.data)
}
mediaRecorder.onstop = async () => {
//连接服务器
await connectToServer()
//构造音频文件
const audioBlob = new Blob(recordedChunks, {type: 'audio/webm;codec=pcm'})
//注入播放器
voiceUrl.value = URL.createObjectURL(audioBlob)
//拆分数据块
const frameChunks = await splitAudio(audioBlob, 1280)
//发送数据
for (let i = 0; i < frameChunks.length; i++) {
const params = {
data: {
status: i === 0 ? 0 : 1,
format: "audio/L16;rate=16000",
encoding: "raw",
audio: window.btoa(String.fromCharCode(...new Uint8Array(frameChunks[i]))),
}
}
websocket.send(JSON.stringify(params))
await delay(40)
}
const params = {
data: {
status: 2
}
}
websocket.send(JSON.stringify(params))
}
mediaRecorder.start();
}
License:
CC BY 4.0