使用 MediaRecorder 录制 5 秒的音频片段,然后上传到服务器

Record 5 seconds segments of audio using MediaRecorder and then upload to the server

我想录制用户麦克风的 5 秒长片段并将每个片段上传到服务器。我尝试使用 MediaRecorder 并以 5 秒的时间间隔调用 start() 和 stop() 方法,但是当我连接这些录音时,它们之间有一个 "drop" 声音。所以我尝试使用 start() 方法的时间片参数记录 5 秒的片段:

navigator.mediaDevices.getUserMedia({ audio: { channelCount: 2, volume: 1.0, echoCancellation: false, noiseSuppression: false } }).then(function(stream) {
  const Recorder = new MediaRecorder(stream, { audioBitsPerSecond: 128000, mimeType: "audio/ogg; codecs=opus" });
  Recorder.start(5000); 
  Recorder.addEventListener("dataavailable", function(event) {
    const audioBlob = new Blob([event.data], { type: 'audio/ogg' });
    upload(audioBlob);
  });
});

但是只有第一段可以播放。我能做什么,或者我怎样才能让所有的 blob 都可以播放? 我必须记录然后上传每个片段。我不能制作一个 blob 数组(因为用户可以记录 24 小时甚至更长时间的数据,并且需要在用户记录时将数据上传到服务器 - 延迟 5 秒)。

谢谢!

你必须了解媒体文件是如何构建的。
不仅仅是一些原始数据可以直接转换为音频或视频。

这取决于所选择的格式,但基本情况是您拥有所谓的 元数据,它就像一本描述文件结构的字典。

这些 元数据 是软件所必需的,然后软件将读取文件以了解应如何解析文件中包含的实际数据。

MediaRecorder API 在这里处于一个奇怪的位置,因为它必须能够同时写入这些 元数据 ,并且还可以添加未确定的数据(它是 live 录音机)。

所以浏览器会把主要的 元数据 放在文件的开头,这样他们就可以简单地将新数据推送到文件中, 并且仍然是一个有效的文件(即使像持续时间这样的一些信息会丢失)。

现在,您在 datavailableEvent.data 中得到的只是正在生成的整个文件的一部分。
第一个通常包含 元数据 和一些其他数据,具体取决于事件何时被告知触发,但接下来的部分不一定包含任何元数据。

因此您不能将这些部分作为独立文件获取,因为唯一生成的文件是由所有这些部分组成的文件,并结合在一个 Blob 中。


因此,对于您的问题,您有不同的可能方法:

  • 您可以将您从记录器中获取的最新切片每隔一段时间发送到您的服务器,并在服务器端合并这些切片。

    const recorder = new MediaRecorder(stream);
    const chunks = [];
    recorder.ondataavailable = e => chunks.push(e.data);
    recorder.start(); // you don't need the timeslice argument
    setInterval(()=>{
      // here we both empty the 'chunks' array, and send its content to the server
      sendToServer(new Blob(chunks.splice(0,chunks.length)))
    }, 5000);
    

    而在您的服务器端,您会将新发送的数据附加到正在记录的文件中。

  • 另一种方法是生成很多小的独立文件,为此,您可以简单地在一个时间间隔内生成一个新的 MediaRecorder:

    function record_and_send(stream) {
       const recorder = new MediaRecorder(stream);
       const chunks = [];
       recorder.ondataavailable = e => chunks.push(e.data);
       recorder.onstop = e => sendToServer(new Blob(chunks));
       setTimeout(()=> recorder.stop(), 5000); // we'll have a 5s media file
       recorder.start();
    }
    // generate a new file every 5s
    setInterval(record_and_send, 5000);
    

    这样做,每个文件将是独立的,持续时间约为 5 秒,您将能够一个接一个地播放这些文件。
    现在,如果您希望只在服务器上存储一个文件,仍然使用这种方法,您也可以在服务器端很好地将这些文件合并在一起,使用例如 ffmpeg.

使用 之一的版本我得到了这个工作。它将小型独立文件发送到服务器,当它们在服务器端连接为统一文件时不会产生任何图像或声音故障:

var mediaRecorder;
var recordingRunning = false;
var chunks;

// call this function to start the process
function startRecording(stream) {
  recordingRunning = true;
  chunks = [];

  mediaRecorder = new MediaRecorder(stream, { mimeType: "video/webm; codecs=vp9" });

  mediaRecorder.ondataavailable = function (e) {
    chunks.push(e.data);
  };

  mediaRecorder.onstop = function () {
    actualChunks = chunks.splice(0, chunks.length);
    const blob = new Blob(actualChunks, { type: "video/webm; codecs=vp9" });
    uploadVideoPart(blob); // Upload to server
  };

  recordVideoChunk(stream);
};

// call this function to stop the process
function stopRecording(stream) {
  recordingRunning = false
  mediaRecorder.stop();
};

function recordVideoChunk(stream) {
  mediaRecorder.start();

  setTimeout(function() {
    if(mediaRecorder.state == "recording")
      mediaRecorder.stop();

    if(recordingRunning)
      recordVideoChunk(stream);
  }, 10000); // 10 seconds videos
}

后来在服务器上我用这个命令连接它们:

# list.txt
file 'chunk1'
file 'chunk2'
file 'chunk3'

# command
ffmpeg -avoid_negative_ts 1 -f concat -safe 0 -i list.txt -c copy output.mp4