Лучший подход к потоковой передаче http в реальном времени к видео клиенту HTML5

213

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

Мой вариант использования:

1) IP-видеокамера Поток RTSP H.264 принимается FFMPEG и повторно смешивается в контейнере mp4 с использованием следующих настроек FFMPEG в узле, выводится в STDOUT. Это выполняется только при первоначальном клиентском соединении, поэтому частичные запросы контента не пытаются снова порождать FFMPEG.

liveFFMPEG = child_process.spawn("ffmpeg", [
                "-i", "rtsp://admin:12345@192.168.1.234:554" , "-vcodec", "copy", "-f",
                "mp4", "-reset_timestamps", "1", "-movflags", "frag_keyframe+empty_moov", 
                "-"   // output to stdout
                ],  {detached: false});

2) Я использую http-сервер узла для захвата STDOUT и передачи его обратно клиенту по запросу клиента. Когда клиент впервые подключается, я создаю вышеупомянутую командную строку FFMPEG, а затем передаю поток STDOUT в ответ HTTP.

liveFFMPEG.stdout.pipe(resp);

Я также использовал потоковое событие для записи данных FFMPEG в ответ HTTP, но без разницы

xliveFFMPEG.stdout.on("data",function(data) {
        resp.write(data);
}

Я использую следующий HTTP-заголовок (который также используется и работает при потоковой передаче предварительно записанных файлов)

var total = 999999999         // fake a large file
var partialstart = 0
var partialend = total - 1

if (range !== undefined) {
    var parts = range.replace(/bytes=/, "").split("-"); 
    var partialstart = parts[0]; 
    var partialend = parts[1];
} 

var start = parseInt(partialstart, 10); 
var end = partialend ? parseInt(partialend, 10) : total;   // fake a large file if no range reques 

var chunksize = (end-start)+1; 

resp.writeHead(206, {
                  'Transfer-Encoding': 'chunked'
                 , 'Content-Type': 'video/mp4'
                 , 'Content-Length': chunksize // large size to fake a file
                 , 'Accept-Ranges': 'bytes ' + start + "-" + end + "/" + total
});

3) Клиент должен использовать теги видео HTML5.

У меня нет проблем с потоковым воспроизведением (используя fs.createReadStream с частичным содержимым HTTP 206) для клиента HTML5 видеофайл, ранее записанный с помощью указанной выше командной строки FFMPEG (но сохраненный в файл вместо STDOUT), поэтому я знаю поток FFMPEG правильно, и я даже могу правильно видеть потоковую передачу видео в VLC при подключении к серверу узла HTTP.

Однако попытка потокового вещания из FFMPEG через узел HTTP выглядит намного сложнее, так как клиент отобразит один кадр, а затем остановится. Я подозреваю, что проблема в том, что я не настраиваю соединение HTTP, чтобы быть совместимым с видео клиентом HTML5. Я пробовал разные вещи, такие как использование HTTP 206 (частичное содержимое) и 200 ответов, помещение данных в буфер, а затем потоковую передачу без удачи, поэтому мне нужно вернуться к первым принципам, чтобы убедиться, что я настроил это правильно путь.

Вот мое понимание того, как это должно работать, поправьте меня, если я ошибаюсь:

1) FFMPEG должен быть настроен для фрагментации вывода и использования пустого moov (флаги FFMPEG frag_keyframe и empty_moov mov). Это означает, что клиент не использует атом moov, который обычно находится в конце файла, который не имеет отношения к потоковой передаче (без конца файла), но означает, что поиск невозможен, что хорошо для моего варианта использования.

2) Несмотря на то, что я использую фрагменты MP4 и пустой MOOV, мне все равно нужно использовать частичное содержимое HTTP, так как проигрыватель HTML5 будет ждать, пока весь поток не загрузится, прежде чем играть, что с живым потоком никогда не заканчивается, поэтому не работает.

3) Я не понимаю, почему передача потока STDOUT в ответ HTTP не работает при потоковой передаче в реальном времени. Если я сохраню в файл, я могу легко передать этот файл клиентам HTML5, используя подобный код. Может быть, это проблема синхронизации, так как для запуска порождения FFMPEG требуется секунда, он подключается к IP-камере и отправляет порции на узел, а события данных узла также нерегулярны. Однако поток данных должен быть точно таким же, как при сохранении в файл, и HTTP должен быть способен справляться с задержками.

4) При проверке сетевого журнала от HTTP-клиента при потоковой передаче файла MP4, созданного FFMPEG с камеры, я вижу, что есть 3 клиентских запроса: общий запрос GET для видео, который HTTP-сервер возвращает около 40 КБ, затем частичный запрос контента с байтовым диапазоном для последних 10КБ файла, затем окончательный запрос для битов в середине не загружен. Может быть, клиент HTML5, получив первый ответ, запрашивает последнюю часть файла для загрузки атома MP4 MOOV? В этом случае он не будет работать для потоковой передачи, так как нет файла MOOV и нет конца файла.

5) При проверке сетевого журнала при попытке потокового вещания я получаю прерванный начальный запрос только с полученными приблизительно 200 байтами, затем повторный запрос снова прерывается с 200 байтами и третьим запросом, длина которого составляет всего 2 КБ. Я не понимаю, почему клиент HTML5 прервал запрос, поскольку поток байтов точно такой же, какой я могу успешно использовать при потоковой передаче из записанного файла. Также кажется, что узел не отправляет оставшуюся часть потока FFMPEG клиенту, но я вижу данные FFMPEG в подпрограмме события .on, поэтому он попадает на HTTP-сервер узла FFMPEG.

6) Хотя я думаю, что передача потока STDOUT в буфер ответов HTTP должна работать, мне нужно создать промежуточный буфер и поток, которые позволят клиентским запросам частичного содержимого HTTP работать должным образом, как это происходит, когда он (успешно) читает файл ? Я думаю, что это главная причина моих проблем, но я не совсем уверен, как лучше всего это настроить. И я не знаю, как обработать клиентский запрос данных в конце файла, поскольку у него нет конца файла.

7) Я ошибаюсь, пытаясь обработать 206 частичных запросов контента, и должно ли это работать с обычными 200 HTTP-ответами? HTTP 200 ответы отлично работают для VLC, поэтому я подозреваю, что видео-клиент HTML5 будет работать только с частичными запросами контента?

Так как я все еще изучаю этот материал, трудно разобраться с различными уровнями этой проблемы (FFMPEG, узел, потоковая передача, HTTP, видео HTML5), поэтому любые указатели будут высоко оценены. Я провел часы, исследуя этот сайт и сеть, и я не встречал никого, кто мог бы выполнять потоковую передачу в реальном времени на узле, но я не могу быть первым, и я думаю, что это должно сработать (каким-то образом !).

deandob
источник
4
Это сложная тема. Первым делом первым. Ты поставил себе Content-Typeв голову? Вы используете чанк код? Вот где я бы начал. Кроме того, HTML5 не обязательно обеспечивает функциональность для потоковой передачи, вы можете прочитать об этом здесь . Скорее всего, вам потребуется реализовать способ буферизации и воспроизведения видеопотока своими собственными средствами ( см. Здесь ), хотя это, вероятно, не очень хорошо поддерживается. Также загляните в MediaSource API.
tsturzl
Спасибо за ответ. Да, тип контента - video / mp4, и этот код работает для потоковой передачи видеофайлов. К сожалению, MediaSource только для Chrome, я должен поддерживать другие браузеры. Есть ли спецификация о том, как видео-клиент HTML5 взаимодействует с потоковым сервером HTTP? Я уверен, что то, что я хочу, можно сделать, но не знаю точно, как (с помощью node.js, но можно было бы использовать C # или C ++, если это проще)
deandob
2
Проблема не на вашем сервере. Вы потоковое видео просто отлично. Проблема в вашем интерфейсе / клиенте, вам нужно реализовать потоковую передачу самостоятельно. HTML5 просто не обрабатывает потоки. Скорее всего, вам нужно изучить параметры для каждого браузера. Хорошим началом будет чтение стандартов w3 для тегов video и media API.
tsturzl
Похоже, что это должно быть возможно сделать. Я не предлагаю однозначного ответа, но подозреваю, что эта проблема связана с тем, что браузер ожидает остаток заголовка / атомов контейнера mp4 в начале, а не следующий кадр в видеопотоке. Если вы отправите атом MOOV для очень длинного видео (чтобы проигрыватель продолжал запрашивать), а также для других ожидаемых заголовков, а затем начнете копирование из ffmpeg, это может сработать. Вы также должны скрыть панель очистки, используя js в браузере, чтобы они не могли сканировать вперед.
jwriteclub
Я бы предложил рассмотреть WebRTC, который изо дня в день получает лучшую межбраузерную поддержку.
Алекс Кон

Ответы:

209

РЕДАКТИРОВАТЬ 3: Начиная с IOS 10, HLS будет поддерживать фрагментированные файлы mp4. Ответ теперь заключается в создании фрагментированных ресурсов mp4 с манифестом DASH и HLS. > Притворяться, что Flash, iOS9 и ниже и IE 10 и ниже не существует.

Все под этой строкой устарело. Держу это здесь для потомков.


РЕДАКТИРОВАТЬ 2: Как люди в комментариях указывают, все меняется. Почти все браузеры будут поддерживать кодеки AVC / AAC. iOS все еще требует HLS. Но с помощью таких адаптеров, как hls.js, вы можете играть в HLS в MSE. Новый ответ - HLS + hls.js, если вам нужна iOS. или просто фрагментированный MP4 (т.е. DASH), если вы этого не сделаете

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

Сначала я повторю: ОФИЦИАЛЬНОЙ ПОДДЕРЖКИ ДЛЯ ПРЯМОГО ПОТОКА НЕ НУЖНО . Есть взломы, но ваш пробег может отличаться.

РЕДАКТИРОВАТЬ: с тех пор как я написал этот ответ Расширения Media Source созрели, и теперь очень близки к тому, чтобы стать жизнеспособным вариантом. Они поддерживаются в большинстве основных браузеров. IOS продолжает держаться.

Далее, вы должны понимать, что видео по запросу (VOD) и живое видео очень разные. Да, они оба видео, но проблемы разные, поэтому форматы разные. Например, если часы на вашем компьютере работают на 1% быстрее, чем должны, вы не заметите на VOD. С живым видео вы будете пытаться воспроизвести видео до того, как это произойдет. Если вы хотите присоединиться к текущему потоковому видео, вам нужны данные, необходимые для инициализации декодера, поэтому они должны быть повторены в потоке или отправлены вне полосы. С VOD вы можете прочитать начало файла, к которому они стремятся, в любую точку, которую вы хотите.

Теперь давайте немного покопаемся.

Платформы:

  • IOS
  • ПК
  • макинтош
  • Android

Кодеки:

  • vp8 / 9
  • h.264
  • Тора (vp3)

Распространенные способы доставки живого видео в браузерах:

  • DASH (HTTP)
  • HLS (HTTP)
  • вспышка (RTMP)
  • вспышка (HDS)

Общие методы доставки для VOD в браузерах:

  • DASH (HTTP Streaming)
  • HLS (потоковое HTTP)
  • вспышка (RTMP)
  • flash (HTTP Streaming)
  • MP4 (псевдопоток HTTP)
  • Я не буду говорить о MKV и OOG, потому что я не очень хорошо их знаю.

HTML5 тег видео:

  • MP4
  • WebM
  • OGG

Давайте посмотрим, какие браузеры поддерживают какие форматы

Сафари:

  • HLS (только для iOS и Mac)
  • h.264
  • MP4

Fire Fox

  • DASH (через MSE, но без h.264)
  • h.264 только через Flash!
  • VP9
  • MP4
  • OGG
  • WebM

IE

  • вспышка
  • DASH (только через MSE IE 11+)
  • h.264
  • MP4

Хром

  • вспышка
  • DASH (через MSE)
  • h.264
  • VP9
  • MP4
  • WebM
  • OGG

MP4 нельзя использовать для живого видео (ПРИМЕЧАНИЕ: DASH - это расширенный набор MP4, поэтому не путайтесь с этим). MP4 разбит на две части: moov и mdat. mdat содержит необработанные аудио-видео данные. Но это не индексируется, поэтому без moov это бесполезно. Moov содержит индекс всех данных в mdat. Но из-за своего формата его нельзя «сплющить», пока не будут известны временные метки и размер КАЖДОГО кадра. Может быть возможно построить moov, который «подставляет» размеры кадра, но это очень расточительно с точки зрения пропускной способности.

Поэтому, если вы хотите доставить везде, нам нужно найти наименьший общий знаменатель. Вы увидите, что здесь нет LCD, не прибегая к примеру со вспышкой:

  • iOS поддерживает только видео h.264. и он поддерживает только HLS для жизни.
  • Firefox вообще не поддерживает h.264, если только вы не используете flash
  • Flash не работает в iOS

Самое близкое к ЖК-дисплею - использование HLS для привлечения пользователей iOS и прошивка для всех остальных. Мой личный фаворит - это кодирование HLS, а затем использование Flash для воспроизведения HLS для всех остальных. Вы можете воспроизводить HLS во флэш-памяти через JW player 6 (или написать свой собственный HLS для FLV в AS3, как я)

Вскоре наиболее распространенным способом сделать это будет HLS на iOS / Mac и DASH через MSE везде (это то, что Netflix будет делать в ближайшее время). Но мы все еще ждем, чтобы все обновили свои браузеры. Вам также, вероятно, понадобится отдельный DASH / VP9 для Firefox (я знаю об open264; он отстой. Он не может делать видео в основном или высоком профиле. Поэтому в настоящее время он бесполезен).

szatmary
источник
Спасибо szatmary за подробную справочную информацию и за / против о различных вариантах. Я выбрал этот ответ в качестве принятого, поскольку наброски концепций более важны, чем конкретное исправление, которое я нашел для ответа на исходный вопрос. Удачи с щедростью!
deandob
9
Это не рабочее решение этого вопроса. Ниже приведено рабочее решение этой проблемы.
jwriteclub
2
Firefox теперь поддерживает MSE и h.264 изначально. Перейдите на сайт www.youtube.com/html5 с последним браузером FF для подтверждения. Я протестировал с FF 37. Safari 8+ на Mac также теперь поддерживает MSE.
BigTundra
@ BigTundra Да, Safari поддерживает MSE с момента запуска Yosemite на Mac. Но не iOS. Не уверен насчет Windows. (Safari на Windows все еще вещь?) Firefox 37.0.2 на (моем) Mac, кажется, вообще не поддерживает MSE согласно этой ссылке. Но поддерживает H.264. В прошлом Firefox добавлял, удалял и повторно добавлял поддержку H.264.
Сатмари
Современный браузер поддерживает видеоформат MPEG-4 / H.264: caniuse.com/#feat=mpeg4
Maxence
75

Спасибо всем, особенно szatmary, так как это сложный вопрос, имеющий много слоев, и все это должно работать, прежде чем вы сможете транслировать живое видео. Чтобы прояснить мой исходный вопрос и использование HTML5-видео вместо флэш-памяти, мой вариант использования имеет сильные предпочтения для HTML5, потому что он универсальный, его легко реализовать на клиенте и в будущем. Flash занимает второе место, поэтому давайте придерживаться HTML5 для этого вопроса.

Я многому научился благодаря этому упражнению и согласен, что потоковое вещание намного сложнее, чем VOD (который хорошо работает с видео HTML5). Но я сделал так, чтобы это работало удовлетворительно для моего варианта использования, и решение оказалось очень простым, после того, как мы выбрали более сложные опции, такие как MSE, flash, сложные схемы буферизации в Node. Проблема заключалась в том, что FFMPEG искажал фрагментированный MP4, и мне пришлось настраивать параметры FFMPEG, а перенаправление канала потока стандартного узла через http, которое я использовал изначально, было всем, что было нужно.

В MP4 есть опция 'фрагментации', которая разбивает mp4 на гораздо меньшие фрагменты, которые имеют свой собственный индекс, и делает опцию прямой трансляции mp4 жизнеспособной. Но невозможно вернуться обратно в поток (хорошо для моего варианта использования), и более поздние версии FFMPEG поддерживают фрагментацию.

Время синхронизации заметок может быть проблемой, и с моим решением у меня задержка составляет от 2 до 6 секунд, вызванная сочетанием ремикса (фактически FFMPEG должен получать живой поток, ремуксировать его, а затем отправлять его на узел для обслуживания по HTTP) , С этим ничего не поделаешь, однако в Chrome видео пытается наверстать упущенное, что делает его немного нервным, но более актуальным, чем IE11 (мой предпочтительный клиент).

Вместо того, чтобы объяснять, как работает код в этом посте, ознакомьтесь с GIST с комментариями (код клиента не включен, это стандартный HTML-тег HTML5 с адресом http-узла). GIST находится здесь: https://gist.github.com/deandob/9240090

Мне не удалось найти похожие примеры этого варианта использования, поэтому я надеюсь, что приведенное выше объяснение и код помогут другим, особенно потому, что я многому научился на этом сайте и все еще считаю себя новичком!

Хотя это ответ на мой конкретный вопрос, я выбрал ответ szatmary в качестве принятого, поскольку он является наиболее полным.

deandob
источник
33
Извините, но я нашел это самостоятельно, запись моего ответа проясняет это. Все предыдущие ответы были полезны и оценены, но не внесли значительного вклада, и я даже представил рабочий код в GIST, и никто другой не сделал этого. Меня не интересует «репутация», мне интересно узнать, можно ли улучшить мой подход и код. И ответ, который я поставил, решил мою проблему, поэтому я не понимаю, в чем проблема. Я довольно новичок в SO, поэтому я рад, что мне сказали, что я могу взаимодействовать по-другому, я считаю этот сайт полезным, и мой ответ должен помочь другим.
deandob
2
Похоже, что в этом сообществе неправильно выбирать свой ответ в качестве принятого, если вы задали вопрос, даже если он устраняет первоначальную проблему. Хотя это кажется нелогичным, документирование концепций более важно, чем фактическое исправление, с которым я согласен, поскольку оно помогает другим учиться. Я отменил свой ответ и выбрал szatmary как наиболее четко сформулированные в отношении этих концепций.
deandob
6
@deandob: я отправил вознаграждение за рабочее решение этой проблемы, которое вы успешно предоставили. В принятом ответе утверждается, что рабочего решения не существует и, следовательно, оно явно неточное.
jwriteclub
2
Спасибо. Кажется, другие опровергли мой первоначальный ответ как неправильный, и, поскольку я новичок, я просто предположил, что так все и происходит. Я не хочу вызывать суеты, но я проверю людей на переполнение стека мета. КСТАТИ - мое решение работает очень хорошо, и оно должно быть работоспособным для других, и есть опубликованное изменение, которое может уменьшить начальную задержку (буфер в файле node.js сначала затем ищет конец потока на стороне клиента) ,
deandob
4
У меня есть объяснение от модератора, что мой первоначальный подход - сам ответить на вопрос и выбрать его в качестве ответа, был правильным. Для получения дополнительной информации (или если вы хотите обсудить это далее) см. Ветку на мета-сайте. meta.stackexchange.com/questions/224068/…
deandob
14

Взгляните на проект JSMPEG . Там реализована отличная идея - декодировать MPEG в браузере с помощью JavaScript. Байты из кодировщика (например, FFMPEG) могут быть переданы в браузер, например, с помощью WebSockets или Flash. Если сообщество наверстает упущенное, я думаю, что это пока будет лучшим решением для потокового видео в формате HTML5.

Михаил Романенко
источник
10
Это видео декодер MPEG-1. Я не уверен, что вы понимаете, насколько древний MPEG-1; это старше чем DVD. Это немного сложнее, чем GIF-файл.
Камило Мартин
13

Я написал видеоплеер HTML5 на базе кодека Broadway H264 (emscripten), который может воспроизводить видео H264 в реальном времени (без задержки) во всех браузерах (настольный компьютер, iOS, ...).

Видеопоток отправляется через websocket клиенту, декодированный кадр за кадром и отображается в канве (используя webgl для ускорения)

Проверьте https://github.com/131/h264-live-player на github.

131
источник
1
github.com/Streamedian/html5_rtsp_player Эти ребята сделали нечто подобное, используя rtp h264 поверх websocket
Victor.dMdB
12

Один из способов прямой трансляции веб-камеры на основе RTSP на клиент HTML5 (включает перекодирование, поэтому ожидайте потери качества и требуйте некоторой мощности ЦП):

  • Настройте сервер Icecast (может быть на той же машине, на которой находится веб-сервер, или на машине, которая получает RTSP-поток с камеры)
  • На машине, принимающей поток с камеры, не используйте FFMPEG, а gstreamer. Он может принимать и декодировать RTSP-поток, перекодировать его и передавать на сервер icecast. Пример конвейера (только видео, без звука):

    gst-launch-1.0 rtspsrc location=rtsp://192.168.1.234:554 user-id=admin user-pw=123456 ! rtph264depay ! avdec_h264 ! vp8enc threads=2 deadline=10000 ! webmmux streamable=true ! shout2send password=pass ip=<IP_OF_ICECAST_SERVER> port=12000 mount=cam.webm

=> Затем можно использовать тег <video> с URL-адресом icecast-stream ( http://127.0.0.1:12000/cam.webm ), и он будет работать во всех браузерах и устройствах, поддерживающих webm

Jannis
источник
3

Посмотрите на это решение . Как я знаю, Flashphoner позволяет воспроизводить потоковое аудио + видео на чистой странице HTML5.

Для воспроизведения используются кодеки MPEG1 и G.711 . Хак рендерит декодированное видео в элемент HTML5 canvas и проигрывает декодированное аудио через аудио-контекст HTML5.

ankitr
источник
3

Как насчет использования решения jpeg, просто разрешите серверу распространять jpeg один за другим в браузер, а затем используйте элемент canvas для рисования этих jpegs? http://thejackalofjavascript.com/rpi-live-streaming/

Kiki.J.Hu
источник
2

Это очень распространенное заблуждение. Нет прямой поддержки видео HTML5 (за исключением HLS на iOS и Mac Safari). Возможно, вы сможете «взломать» его, используя контейнер webm, но я не ожидаю, что это будет поддерживаться повсеместно. То, что вы ищете, включено в Расширения Media Source, где вы можете передавать фрагменты в браузер по одному. но вам нужно будет написать некоторый клиентский JavaScript.

szatmary
источник
Есть, solutionsно не supportдля прямой трансляции. Это прямо ссылается на мой комментарий, увиденный выше. И webm поддерживается в основных браузерах, в основном это последняя стабильная версия.
tsturzl
1
Я действительно предпочел бы не транскодировать с H.264 на webm, и в этом нет необходимости. Также, поскольку я должен поддерживать IE11 и Safari, расширения MediaSource не помогут. Но я думаю, что если я симулирую файловый поток на стороне сервера (который работает!), То он должен работать, но мне придется симулировать файловый буфер на node.js.
deandob
1
Как и предполагалось, я бы искал возможность использовать WebRTC, который является родным в отличие от VLC или флэш-плагина. Я знаю, что эту технологию все еще сложно реализовать. Удачи.
1
Я заставил это работать, обновив до последней версии FFMPEG, поскольку кажется, что в mp4 при использовании фрагментированного режима произошла ошибка (необходимо для потоковой передачи MP4, поэтому клиент не ждет файл индекса moov, который никогда не появится, когда он будет работать) потоковое). И мой код node.js для перенаправления потока FFMPEG прямо в браузер теперь работает.
deandob
1
Да, отлично работает на IE11 (мой любимый браузер). Я получаю нервный ответ в Chrome.
deandob
2

Попробуйте бинарный файл. Это так же, как socket.io, но единственное, что он делает хорошо - это потоковое аудио-видео. Binaryjs Google это

Сиддхарт
источник
1
Binary.JS не имеет ничего общего с Socket.IO. И это не относится к потоковой передаче мультимедиа.
Брэд