Как передать поток MediaRecorder в кодировке h264 в MediaSource в Chrome? - PullRequest
2 голосов
/ 27 мая 2020

Наше расширение записи экрана chrome позволяет пользователю записывать свой экран с помощью getDisplayMedia API, который возвращает поток, который подается в MediaRecorder API.

Обычно мы записываем этот поток с помощью видео контейнера webm с новым кодом vp9 c вот так:

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

Однако Safari не поддерживает контейнер webm и не поддерживает декодирование код vp9 c. Поскольку MediaRecorder API в Chrome поддерживает только запись в контейнере webm, но поддерживает кодировку h264 (которую может декодировать Safari), вместо этого мы записываем код h264 c в контейнер webm:

const mediaRecorder = new MediaRecorder(mediaStream, {
    mimeType: "video/webm; codecs=h264"
  });

Это хорошо работает по двум причинам:

  1. поскольку наше приложение для записи является расширением chrome, мы не возражаем, что оно может записывать только в Chrome

  2. поскольку видеоданные закодированы как h264, теперь мы можем почти мгновенно переместить видеоданные в контейнер .mp4, позволяя зрителям Safari просматривать эти записанные видео, не дожидаясь дорогостоящего процесса перекодирования ( обратите внимание, что вы можете просматривать видео без расширения chrome в обычном веб-приложении)

Однако, поскольку API устройства записи мультимедиа не имеет метода для получения продолжительности видеопотока записано до сих пор, и измерение вручную с помощью performance.now оказалось неточным (с ошибкой от 25 мс до 150 мс), нам пришлось перейти на подачу данных регистратора int oa MediaSource, чтобы мы могли использовать mediaSourceBuffer.buffered.end(sourceBuffer.buffered.length - 1) * 1000 API для получения 100% точного чтения продолжительности записанного видеопотока (в миллисекундах).

Проблема в том, что по какой-то причине MediaSource не может создать экземпляр когда мы используем наш "video / webm; codecs = h264 "MIME-тип.

Выполнение этого:

mediaSourceBuffer = mediaSource.addSourceBuffer("video/webm; codecs=h264");

Результат:

Failed to execute 'addSourceBuffer' on 'MediaSource': The type provided ('video/webm; codecs=h264') is unsupported.

Почему тип MIME поддерживается MediaRecorder, но не MediaSource? Поскольку они принадлежат к одному семейству API, разве они не должны поддерживать одни и те же типы mime? Как мы можем записывать с кодом h264 c при передаче данных в MediaSource с помощью addSourceBuffer?

Единственное решение, которое мы На данный момент можно придумать 2 устройства записи мультимедиа, одну запись в vp9, чтобы мы могли считывать точную продолжительность видео, записанного на данный момент с помощью buffered.end API, и одну запись в формате h264, чтобы мы могли немедленно переместить видеоданные в контейнер mp4 без необходимости перекодировать код c из vp9 в h264 для пользователей Safari. Однако это было бы очень неэффективно, так как эффективно удерживало бы вдвое больше данных в ОЗУ.

Случаи воспроизведения / codeandbox examples

  1. vp9 example (оба работают)
  2. h264 example (медиа-рекордер работает, медиа исходник нет)

1 Ответ

1 голос
/ 27 мая 2020

Декодеры и кодировщики это вообще зверюга. Например, Webkit (Safari) может декодировать несколько форматов, но не может ничего кодировать.

Кроме того, MediaSource API требует, чтобы мультимедийные данные, передаваемые ему, могли быть фрагментированы и, таким образом, не могли читать все мультимедийные данные, которые браузер может декодировать, например, если один браузер когда-нибудь поддерживал генерирующий стандарт (нефрагментированный ) mp4, то они все равно не смогут передать их в MediaSource API.

Я не могу точно сказать, могут ли они поддерживать этот конкретный код c (я думаю да), но вам может вообще не понадобиться все это обходное решение.

Если ваше расширение способно генерировать элементы DOM, вы можете просто использовать элемент <video>, чтобы сообщить вам продолжительность записанного видео, используя трюк, описанный в этом ответе :

Установите currentTime видео на очень большое число, дождитесь события seeked, и вы получите правильный duration.

const canvas_stream = getCanvasStream();
const rec = new MediaRecorder( canvas_stream.stream );
const chunks = [];
rec.ondataavailable = (evt) => chunks.push( evt.data );
rec.onstop = async (evt) => {
  canvas_stream.stop();
  console.log( "duration:", await measureDuration( chunks ) );
};
rec.start();
setTimeout( () => rec.stop(), 5000 );
console.log( 'Recording 5s' );

function measureDuration( chunks ) {
  const blob = new Blob( chunks, { type: "video/webm" } );
  const vid = document.createElement( 'video' );
  return new Promise( (res, rej) => {
    vid.onerror = rej;
    vid.onseeked = (evt) => res( vid.duration );
    vid.onloadedmetadata = (evt) => {
      URL.revokeObjectURL( vid.src );
      // for demo only, to show it's Infinity in Chrome
      console.log( 'before seek', vid.duration );
    };
    vid.src = URL.createObjectURL( blob );
    vid.currentTime = 1e10;
  } );
}


// just so we can have a MediaStream in StackSnippet
function getCanvasStream() {
  const canvas = document.createElement( 'canvas' );
  const ctx = canvas.getContext( '2d' );
  let stopped = false;
  function draw() {
    ctx.fillRect( 0,0,1,1 );
    if( !stopped ) {
      requestAnimationFrame( draw );
    }
  }
  draw();
  return {
    stream: canvas.captureStream(),
    stop: () => stopped = true
  };
}
...