【问题标题】:Record 5 seconds segments of audio using MediaRecorder and then upload to the server使用 MediaRecorder 录制 5 秒的音频片段,然后上传到服务器
【发布时间】:2018-12-21 20:03:21
【问题描述】:

我想录制用户的麦克风 5 秒长的片段并将每个片段上传到服务器。我尝试使用 MediaRecorder 并以 5 秒的时间间隔调用 start() 和 stop() 方法,但是当我连接这些录音时,它们之间会发出“滴”的声音。所以我尝试使用 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 秒)。

谢谢!

【问题讨论】:

    标签: javascript blob mediarecorder web-mediarecorder


    【解决方案1】:

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

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

    这些元数据对于随后读取文件的软件来说是必需的,以了解它应该如何解析文件中包含的实际数据。

    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 之类的工具。

    【讨论】:

    • 非常感谢您的回答!是的,我需要每个音频片段都可以播放。我使用了第一种方法,但是这些片段不能单独播放。如果我使用第二种方法,并且我启动和停止录音机,每个片段都是可播放的,所以很好,但是当我需要加入它们时,每个片段之间都会出现“滴,咔哒,啪”的声音。嗯...是否有任何选项可以使用第一种方法,并将元数据(或其他任何内容)从第一个文件复制并粘贴到所有其他段?非常感谢!
    • @MMPP 不是真的没有。我没有包含在答案中的第三种方法实际上是同时进行这两种操作:启动一个循环来记录短序列,并启动一个更大的循环来生成完整的记录。 (仅将完整的块发送到您的服务器)
    • 如果我没有收到答案(因为它很紧急),我之前在考虑创建一个赏金。所以,当你在我开始赏金之前回答了这个问题时,我想为你提供我的 50 名声望(大约 23 小时内)。谢谢!
    • 第三种方式确实是个不错的选择!真是个好主意!非常感谢!
    • @MMPP 你可能想看看stackoverflow.com/questions/52817966/…。虽然这里给出的解决方案已被弃用,但有一个新的 AudioWorklet API 可以使用。
    【解决方案2】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多