我正在构建一个电子应用程序,需要录制音频并将其保存在我的 main.js 目录中。我将录音作为 blob 获取,然后将其转换为数组缓冲区,以便将其发送到我的后端。
在startRecording函数中我使用mediarecorder保存音频然后将其发送到main
const startRecording = async () => {
console.log('Recording started');
chunks = [];
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = event => {
chunks.push(event.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(chunks, { type: 'audio/wav' });
// Convert Blob to buffer
blob.arrayBuffer().then(buffer => {
// Sending audio data to the main process using the function exposed in the preload script
window.sendAudioToMain.send(buffer);
}).catch(error => {
console.error('Error converting Blob to buffer:', error);
});
const audioUrl = URL.createObjectURL(blob);
const audioElement = document.getElementById('audioElement');
audioElement.src = audioUrl;
};
mediaRecorder.start();
};
ipcMain.on('save-audio', async (event, buffer) => {
try {
// Convert the buffer back into a Blob
const audioBlob = new Blob([buffer], { type: 'audio/wav' });
// Print the MIME type of the blob
console.log('MIME Type:', audioBlob.type);
} catch (error) {
console.error('Error:', error);
}
});
在我的代码块中,我将数组缓冲区转回 blob,但我真正想知道的是如何将其转换为 wav 文件
我上次检查
MediaRecorder
的浏览器实现没有提供 "audio/wav"
选项。
单独更改 MIME 类型不会重新编码媒体文件。
我已经通过多种方式将浏览器中录制的媒体写入WAV格式,主要是为了自己的目的使用和调整我在野外找到的代码。
这是我编写的最新WAV编码器代码https://github.com/guest271314/WebCodecsOpusRecorder/blob/main/WebCodecsOpusRecorder.js#L256-L341主要基于https://github.com/higuma/ wav-音频-编码器-js.
初始化通道数和采样率
const wav = new WavAudioEncoder({
numberOfChannels: this.config.decoderConfig.numberOfChannels,
sampleRate: this.config.decoderConfig.sampleRate,
});
这里我正在编写一个从 WebCodecs
ArrayBuffer
复制而来的 AudioFrame
,其格式为 "f32-planar"
,我们稍后必须针对 2 个或更多通道的情况进行整理,此处 https://github.com/ guest271314/WebCodecsOpusRecorder/blob/main/WebCodecsOpusRecorder.js#L274C1-L286C6
write(buffer) {
const floats = new Float32Array(buffer);
let channels;
// Deinterleave
if (this.numberOfChannels > 1) {
channels = [[], []];
for (let i = 0, j = 0, n = 1; i < floats.length; i++) {
channels[(n = ++n % 2)][!n ? j++ : j - 1] = floats[i];
}
channels = channels.map((f) => new Float32Array(f));
} else {
channels = [floats];
}
const size = frame.allocationSize({
planeIndex: 0,
});
const chunk = new ArrayBuffer(size);
frame.copyTo(chunk, {
planeIndex: 0,
});
wav.write(chunk);
当我们完成后
const data = await wav.encode();
异步返回具有
Blob
MIME 类型的 "audio/wav"
,其中包含编码的 WAV 文件。
// https://github.com/higuma/wav-audio-encoder-js
class WavAudioEncoder {
constructor({ sampleRate, numberOfChannels }) {
let controller;
let readable = new ReadableStream({
start(c) {
return (controller = c);
},
});
Object.assign(this, {
sampleRate,
numberOfChannels,
numberOfSamples: 0,
dataViews: [],
controller,
readable,
});
}
write(buffer) {
const floats = new Float32Array(buffer);
let channels;
// Deinterleave
if (this.numberOfChannels > 1) {
channels = [[], []];
for (let i = 0, j = 0, n = 1; i < floats.length; i++) {
channels[(n = ++n % 2)][!n ? j++ : j - 1] = floats[i];
}
channels = channels.map((f) => new Float32Array(f));
} else {
channels = [floats];
}
const [{ length }] = channels;
const ab = new ArrayBuffer(length * this.numberOfChannels * 2);
const data = new DataView(ab);
let offset = 0;
for (let i = 0; i < length; i++) {
for (let ch = 0; ch < this.numberOfChannels; ch++) {
let x = channels[ch][i] * 0x7fff;
data.setInt16(
offset,
x < 0 ? Math.max(x, -0x8000) : Math.min(x, 0x7fff),
true
);
offset += 2;
}
}
this.controller.enqueue(new Uint8Array(ab));
this.numberOfSamples += length;
}
setString(view, offset, str) {
const len = str.length;
for (let i = 0; i < len; i++) {
view.setUint8(offset + i, str.charCodeAt(i));
}
}
async encode() {
const dataSize = this.numberOfChannels * this.numberOfSamples * 2;
const buffer = new ArrayBuffer(44);
const view = new DataView(buffer);
this.setString(view, 0, 'RIFF');
view.setUint32(4, 36 + dataSize, true);
this.setString(view, 8, 'WAVE');
this.setString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, this.numberOfChannels, true);
view.setUint32(24, this.sampleRate, true);
view.setUint32(28, this.sampleRate * 4, true);
view.setUint16(32, this.numberOfChannels * 2, true);
view.setUint16(34, 16, true);
this.setString(view, 36, 'data');
view.setUint32(40, dataSize, true);
this.controller.close();
return new Blob(
[
buffer,
await new Response(this.readable, {
cache: 'no-store',
}).arrayBuffer(),
],
{
type: 'audio/wav',
}
);
}
}