有没有办法将 MIME 类型“音频/wav”的 blob 转换为 WAV 文件?

问题描述 投票:0回答:1

我正在构建一个电子应用程序,需要录制音频并将其保存在我的 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 文件

javascript audio electron html5-audio web-audio-api
1个回答
0
投票

我上次检查

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',
      }
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.