Невозможно перейти в поток с медиа-рекордера, используя медиа-источник с socket.io

Следующий код работает, когда сначала загружается клиент наблюдения за видео, а затем загружается клиент веб-камеры, он работает безупречно, однако, если порядок изменяется или каким-либо образом поток прерывается, например, путем обновления любого клиента, поток завершится ошибкой и Источник мультимедиа изменит свое состояние готовности на закрытое.

Я предполагаю, что видео, получаемое при запуске, требует заголовков инициализации для запуска, и, поскольку поток читается в середине, он никогда не получает указанные заголовки инициализации. Я не знаю, как добавить такой заголовок в файл webm.

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

Клиент камеры

main();
function main() {
    if (hasGetUserMedia()) {
        const constraints = {
            video: {
                facingMode: 'environment',
                frameRate: {
                    ideal: 10,
                    max: 15
                }
            },
            audio: true
        };

        navigator.mediaDevices.getUserMedia(constraints).
        then(stream => {
            setupRecorder(stream);
        });
    }
}

function setupRecorder(stream) {
    let mediaRecorder = new MediaRecorder(stream, {
        mimeType: 'video/webm; codecs="opus, vp9"'
    });

    mediaRecorder.ondataavailable = e => {
        var blob = e.data;
        socket.emit('video', blob);
    }

    mediaRecorder.start(500);
}

Сервер просто транслирует все, что получено

Наблюдающий за клиентом

var sourceBuffer;
var queue = [];
var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', sourceOpen, false);
main();

socket.on('stream', data => {
    if (mediaSource.readyState == "open") {
        if (sourceBuffer.updating || queue.length > 0) {
            queue.push(data.video);
        } else {
            sourceBuffer.appendBuffer(data.video);
        }
    }
});

function main() {
    videoElement = document.querySelector('#video');
    videoElement.src = URL.createObjectURL(mediaSource);
}

function sourceOpen(e) {
    console.log('open');
    sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="opus, vp9"');
    sourceBuffer.addEventListener('updateend', () => {
        console.log(sourceBuffer.updating, mediaSource.readyState);

        if (queue.length > 0 && !sourceBuffer.updating) {
            sourceBuffer.appendBuffer(queue.shift());
        }
    });
}

Таким образом, код на самом деле работает некорректно, поэтому с сервером отправки сокета все в порядке. Это связано либо с MediaRecorder, либо с MediaSource.


person Justin Fernald    schedule 09.05.2019    source источник


Ответы (1)


Я предполагаю, что видео, получаемое при запуске, требует заголовков инициализации для запуска, и, поскольку поток читается в середине, он никогда не получает указанные заголовки инициализации.

Правильный!

Чтобы решить эту проблему, вам нужно немного узнать о формате WebM. WebM - это лишь часть Matroska (MKV). Matroska - это спецификация схемы для хранения мультимедиа в EBML. EBML - это двоичный формат файла, который может содержать произвольные блоки. Думайте об этом как о двоичном XML.

Это означает, что вы можете использовать такие инструменты, как EBML Viewer для проверки WebM и обратитесь к спецификациям Matroska, чтобы понять, что происходит. Например:

Пример EBMLViewer

Это проверка предварительно записанного файла WebM. Он отлично работает в браузерах. Вы заметите, что есть вложенные элементы.

В каждом файле WebM есть два элемента верхнего уровня. EBML, который определяет этот двоичный файл, и Segment, который содержит все, что находится после.

Внутри Segment есть пара важных для вас элементов. Один из которых Tracks. Вы заметите, что в этом файле есть две дорожки: одна для звука в Opus, а другая для видео в VP9. Другой важный блок - это Info, который содержит информацию о шкале времени и некоторые метаданные о мультиплексоре.

После всех этих метаданных вы найдете Cluster, Cluster, Cluster и т. Д. Это места, в которых вы можете вырезать поток WebM, при условии, что каждый Cluster начинается с ключевого кадра.

Другими словами, ваш код должен делать следующее:

  • Сохраните все данные перед первым Cluster как «данные инициализации».
  • После этого разделитесь на Cluster.

При воспроизведении:

  • Используйте ранее сохраненные «данные инициализации» как первое, что вы загружаете.
  • Начните загрузку через Clusters после этого, начиная с любого места в потоке.

Теперь важен тот, кому кластеру нужен бит ключевого кадра. Насколько мне известно, нет возможности настроить MediaRecorder для этого, и браузеры особенно придирчивы к этому. Как минимум, вам придется ремультиплексировать на стороне сервера ... вам может даже потребоваться перекодирование. См. Также: Кодирование FFMPEG в MPEG-DASH - или WebM с кластерами ключевых кадров - для MediaSource API

используя медиа-источник с socket.io

Должен отметить, что для этого вам даже не нужен MediaSource. Вам точно не нужен Socket.IO. Это может быть так же просто, как вывод этих данных через обычный HTTP-поток. Его можно загрузить прямо в элемент <video>. (Обязательно используйте MediaSource, если вам нужен дополнительный контроль, но в этом нет необходимости.)

person Brad    schedule 09.05.2019
comment
Спасибо за ответ, Брэд, он был очень вдохновляющим. Я попытался загрузить свое видео прямо в видеоэлемент, но это было непросто, что побудило меня использовать MediaSource, но я попробую то, что вы рекомендуете. Кроме того, возникнет вопрос, как мне вообще взять метаданные для анализа кластеров? - person Justin Fernald; 09.05.2019
comment
@JustinFernald Вам нужно будет проанализировать WebM в своем коде. Это полностью выполнимо в JavaScript. WebM довольно легко расшифровать. Думаю, в прошлый раз, когда я сделал это, я закончил тем, что написал свой собственный демультиплексор, но похоже, что на NPM есть хотя бы один пакет, который может стать для вас отправной точкой: github.com/mafintosh/webm-cluster-stream На самом деле, потратьте день на спецификацию Matroska и шестнадцатеричный редактор. Это довольно просто, как только вы это получите! - person Brad; 09.05.2019
comment
Честно говоря, я изучал FFMPEG, Dash и Matrosta, и я нигде не приблизился к своей цели. Я очень не понимаю, как бы отправить свое видео в FFMPEG, а затем получить результат и отправить его наблюдателю. Сначала я думал, что если я просто отправлю сегмент инициализации, а затем отправлю данные, он будет работать, однако после некоторого тестирования любое прерывание в потоке уничтожит поток. Я знаю, что это немного странно, но Брэд, я бы хотел иметь возможность просто поговорить с тобой немного и пощупать твой мозг, поскольку я новичок в потоковой передаче всего этого. Документация не помогает. - person Justin Fernald; 23.05.2019
comment
@JustinFernald Конечно, напишите мне на адрес [email protected] и сообщите, когда вы будете доступны. Меня можно нанять для консультации, но я также рад коротко поболтать бесплатно, чтобы посмотреть, смогу ли я указать вам в правильном направлении. - person Brad; 23.05.2019
comment
Привет, ребята, кто-нибудь из вас нашел библиотеку или код для создания заголовков инициализации? У меня есть электронное приложение, которое записывает звук с помощью MediaRecorder Web API, создает множество коротких аудиофайлов и сохраняет их как аудиофайлы WebM. Вне моего приложения я могу проигрывать каждую запись индивидуально, но у них нет заголовков инициализации, поэтому я больше ничего не могу сделать (например, без прокрутки). Средство просмотра EBML для любого из моих файлов сообщает, что целое число переменной длины 0x01FF ... FF не представляет допустимый размер записи (позиция файла # 40). Я предполагаю, что мои файлы - это просто аудиоданные без заголовка. - person Adam Marsh; 03.01.2021
comment
@AdamMarsh Я не знаю ни одного готового демультиплексора. EBML Viewer выдает это сообщение об ошибке, потому что ваш файл предназначен для потоковой передачи, а это означает, что он имеет неопределенную длину. Приведенный выше пример просто показывает, что находится в этих типах файлов. Вам все равно придется написать демультиплексор и выполнить отладку с помощью шестнадцатеричного редактора. Вам действительно нужно разделить первый кластер ... так что _1 _... когда вы это видите, у вас есть начало кластера. Все, что до этого, по сути, является данными инициализации. - person Brad; 03.01.2021
comment
Спасибо за помощь @Brad, в конце концов мне удалось использовать mkvtoolnix.download/doc/mkvmerge.html, выполняя вызовы командной строки из приложения node.js. Это сделало файлы совместимыми с webm, когда я объединил их вместе с помощью mkvmerge (или одного совместимого файла, если я объединил только один файл). Вариант пользовательского интерфейса был отличным, чтобы понять, что я пытался сделать, а затем вызовы интерфейса командной строки сделали это автоматически. - person Adam Marsh; 07.01.2021