Найкращий підхід у поточному режимі 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 з частковим вмістом 206 HTTP) для клієнта HTML5 відеофайл, записаний раніше за вказаним вище командним рядком FFMPEG (але збережений у файл замість STDOUT), тому я знаю потік FFMPEG є правильним, і я навіть можу правильно бачити потокове відео в VLC під час підключення до сервера вузлів HTTP.

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

Ось моє розуміння того, як це має працювати, будь ласка, виправте мене, якщо я помиляюся:

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

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

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

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

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

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

7) Чи я не так, намагаючись обробити 206 часткових запитів на вміст, і чи потрібно це працювати з нормальними 200 HTTP-відповідями? Відповіді HTTP 200 добре працюють для VLC, тому я підозрюю, що відео-клієнт HTML5 працюватиме лише з частковими запитами на вміст?

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


4
Це хитра тема. Насамперед. Ви поставили собі Content-Typeв голову? Використовуєте кодування фрагментів? Ось з чого я б почав. Крім того, HTML5 не обов'язково надає функціональність для передачі потоку, ви можете прочитати більше про це тут . Вам, швидше за все, потрібно буде реалізувати спосіб буферизації та відтворення відеопотоку власними засобами ( див. Тут ), вважаючи, що це, ймовірно, недостатньо підтримується. Також перейдіть в Google MediaSource API.
tsturzl

Дякую за відповідь. Так, тип вмісту - це "video / mp4", і цей код працює для потокової передачі відеофайлів. На жаль MediaSource є лише хромованим, я маю підтримувати інші браузери. Чи є специфіка, як взаємодіє відео-клієнт HTML5 з потоковим сервером HTTP? Я впевнений, що я хочу зробити, просто не впевнений, як саме (з node.js, але міг би використовувати C # або C ++, якщо це простіше)
deandob

2
Проблема не у вашому бекенді. Ви передаваєте відео просто чудово. Проблема у вашому інтерфейсі / клієнті, вам потрібно реалізувати потокове передавання самостійно. HTML5 просто не обробляє потоки. Вам потрібно буде вивчити варіанти для кожного браузера, швидше за все. Читання стандартів w3 для API тегів для відео та медіа було б гарним місцем для початку.
tsturzl

Здається , що це повинно бути можливим , щоб зробити цю роботу. Я не пропоную остаточної відповіді, але підозрюю, що ця проблема пов'язана з тим, що браузер очікує залишок заголовка / атомів mp4 контейнера на початку, а не наступний кадр у відеопотоці. Якщо ви надішліть атом MOOV для дуже довгого відео (щоб програвач продовжував запитувати), а також інші очікувані заголовки, а потім почніть копіювати з ffmpeg, це може спрацювати. Вам також доведеться сховати панель скрабу за допомогою js у браузері, щоб вони не могли сканувати вперед.
jwriteclub

Я б запропонував розглянути WebRTC, який з кожним днем ​​отримує кращу підтримку крос-браузера.
Алекс Кон

Відповіді:


209

EDIT 3: Станом на IOS 10 HLS підтримуватиме фрагментарні файли mp4. Зараз відповідь полягає у створенні фрагментованих активів mp4 з маніфестом DASH та HLS. > Прикиньте флеш, iOS9 і нижче та IE 10 і нижче не існують.

Все під цим рядком застаріло. Зберігаючи це тут для нащадків.


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

Є багато причин, чому відео та, зокрема, відео в прямому ефірі дуже важкі. (Будь ласка, зауважте, що в оригінальному запитанні було вказано, що відео в HTML5 є обов'язковою вимогою, але запитувач вказав Flash у коментарях. Тож негайно це питання вводить в оману)

По-перше, я ще раз зазначу: НІМАЄ ОФІЦІАЛЬНОЇ ПІДТРИМКИ ЖИВОГО СТРАХУВАННЯ ЗА HTML5 . Є хаки, але ваш пробіг може відрізнятися.

EDIT: з часу написання цієї відповіді розширення медіа-джерела визріли, і тепер дуже близькі до того, щоб стати життєздатним варіантом. Вони підтримуються в більшості основних браузерів. IOS продовжує залишатись в очах.

Далі вам потрібно зрозуміти, що відео на вимогу (VOD) та відео наживо сильно відрізняються. Так, вони обидва відео, але проблеми різні, отже, формати різні. Наприклад, якщо годинник у вашому комп’ютері працює на 1% швидше, ніж слід, ви не помітите на VOD. За допомогою відео наживо ви намагатиметесь відтворити відео, перш ніж це станеться. Якщо ви хочете приєднатись до поточного відеопотоку, який триває, вам потрібні дані, необхідні для ініціалізації декодера, тому вони повинні бути повторені в потоці або відправлені з діапазону. За допомогою VOD ви можете прочитати початок файлу, який вони шукають, до будь-якого пункту.

Тепер трохи розкопаємося.

Платформи:

  • iOS
  • ПК
  • Мак
  • Android

Кодеки:

  • vp8 / 9
  • ч.264
  • гру (vp3)

Загальні способи доставки відео в прямому ефірі в браузерах:

  • DASH (HTTP)
  • HLS (HTTP)
  • спалах (RTMP)
  • спалах (HDS)

Загальні способи доставки VOD у браузерах:

  • DASH (потокова передача HTTP)
  • HLS (HTTP Streaming)
  • спалах (RTMP)
  • спалах (HTTP Streaming)
  • MP4 (псевдострілка HTTP)
  • Я не буду говорити про MKV та OOG, тому що я їх не дуже добре знаю.

html5 відеотег:

  • MP4
  • webm
  • огг

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

Сафарі:

  • HLS (лише для iOS та Mac)
  • ч.264
  • MP4

Firefox

  • DASH (через MSE, але не h.264)
  • h.264 лише через Flash!
  • VP9
  • MP4
  • ОГГ
  • Вебм

IE

  • Спалах
  • DASH (лише через MSE IE 11+)
  • ч.264
  • MP4

Хром

  • Спалах
  • DASH (через MSE)
  • ч.264
  • VP9
  • MP4
  • webm
  • огг

MP4 не можна використовувати для прямого відео (ПРИМІТКА. DASH - це супернабір MP4, тому не плутайте з цим). MP4 розбивається на дві частини: moov і mdat. mdat містить необроблені дані аудіо-відео. Але він не індексований, тому без муву він марний. Moov містить індекс всіх даних у mdat. Але через його формат, його не можна «вирівняти», поки не будуть відомі часові позначки та розмір КОЖНОГО кадру. Можливо, можна сконструювати Moov, який "фіксує" розміри кадру, але це дуже марно.

Тож якщо ви хочете доставляти всюди, нам потрібно знайти найменш поширений знаменник. Ви побачите, що тут немає РК-дисплея, не вдаючись до прикладу спалаху:

  • iOS підтримує лише h.264 відео. і він підтримує лише HLS наживо.
  • Firefox взагалі не підтримує h.264, якщо ви не використовуєте flash
  • Flash не працює в iOS

Найближче до РК - це використання HLS для отримання ваших користувачів iOS та флеш для всіх інших. Мій особистий фаворит - це кодувати HLS, а потім використовувати Flash для відтворення HLS для всіх інших. Ви можете грати в HLS спалахом через JW-плеєр 6 (або написати свій HLS в FLV в AS3, як я)

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


Дякую 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. (Чи є сафарі у Windows ще річ?) Firefox 37.0.2 на (моєму) Mac, схоже, не підтримує MSE за цим посиланням. Але підтримує H.264. У минулому Firefox додав та видалив та знову додав підтримку H.264.
szatmary

Сучасний браузер підтримує формат відео 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 живучою. Але неможливо повернутись назад у потік (OK для мого випадку використання), а пізніші версії FFMPEG підтримують фрагментацію.

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

Замість того, щоб пояснювати, як працює код у цій публікації, перегляньте GIST із коментарями (код клієнта не включений, це стандартний HTML5 відеотег із адресою сервера http вузла). ГІСТ тут: https://gist.github.com/deandob/9240090

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

Хоча це відповідь на моє конкретне запитання, я вибрав відповідь szatmary як прийняту, оскільки вона є найбільш повною.


33
Вибачте, але я знайшов це самостійно, написання моєї відповіді робить це досить зрозумілим. Раніші відповіді були корисними та оціненими, але не суттєво сприяли, і я навіть подав робочий код у GIST, і ніхто більше не має. Мене не цікавить «репутація», мені цікаво навчитися знати, чи можна вдосконалити мій підхід і код. І відповідь, яку я поставив, вирішив мою проблему, тому я плутаюся в тому, в чому тут питання. Я досить новачок, тому я радий, що мені кажуть, що він може взаємодіяти по-іншому, я вважаю цей сайт корисним і моя відповідь повинна допомогти іншим.
deandob

2
Здається, у цій спільноті не належним чином вибрати свою відповідь як прийняту відповідь, якщо ви поставили запитання, навіть якщо це вирішує початкову проблему. Хоча це здається протиінтуїтивним, документація концепцій важливіша, ніж фактичне виправлення, з яким я добре, оскільки це допомагає іншим дізнатися. Я вибрав свою відповідь і вибрав szatmary як найбільш чітко виражений навколо понять.
deandob

6
@deandob: Я опублікував нагороду за робоче рішення цієї проблеми, яке ви успішно надали. Прийнята відповідь стверджує, що немає робочого рішення і тому явно неточна.
jwriteclub

2
Дякую. Здається, що інші сприйняли мою оригінальну відповідь як неправильну, і, як я новий, я просто припустив, що тут усе працює. Я не хочу викликати суєту, але я перевіряю, чи люди на мета-стеку переповнюються. BTW - моє рішення працює дуже добре, і воно повинно бути працездатним для інших, і в розміщеному рішенні є варіант, який може зменшити початкове відставання (буфер в node.js спочатку потім прагнути закінчити потік на клієнтському кінці) .
деандоб

4
У мене є уточнення від модератора, що мій оригінальний підхід відповісти на питання сам і вибрати, що як відповідь був правильний підхід. Для отримання додаткової інформації (або якщо ви хочете обговорити це далі), перегляньте нитку на мета-сайті. meta.stackexchange.com/questions/224068/…
deandob

14

Погляньте на проект JSMPEG . Там реалізована чудова ідея - декодувати MPEG у браузері за допомогою JavaScript. Байти з кодера (наприклад, FFMPEG) можна перенести у браузер за допомогою, наприклад, WebSockets або Flash. Якщо спільнота наздожене, я думаю, це буде найкращим поточним поточним потоковим рішенням HTML5.


10
Це декодер відео MPEG-1. Я не впевнений, що ви розумієте, наскільки древній MPEG-1; він старший за DVD. Це дещо досконаліший ніж файл GIF.
Каміло Мартін

13

Я написав відеоплеєр HTML5 навколо широкоформатного кодеку h264 (emscripten), який може відтворювати відео (без затримки) h264 відео у всіх браузерах (настільних ПК, iOS, ...).

Відеопотік надсилається через веб-сокет клієнту, розшифровується кадр на кадр і відображається на полотні (використовуючи webgl для прискорення)

Ознайомтеся з https://github.com/131/h264-live-player на github.


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

=> Ви можете використовувати тег <відео> з URL в IceCast потоку ( http://127.0.0.1:12000/cam.webm ) , і він буде працювати в будь-якому браузері і пристрої , яке підтримує WebM


3

Погляньте на це рішення . Як я знаю, Flashphoner дозволяє відтворювати аудіо-відео + відеопотік на чистому HTML5-сторінці.

Для відтворення вони використовують кодеки MPEG1 та G.711 . Хак - це відтворення декодованого відео до елемента полотна HTML5 та відтворення декодованого аудіо в аудіо контексті HTML5.



2

Це дуже поширене оману. Немає підтримки відео HTML5 (крім HLS на iOS та Mac Safari). Можливо, ви зможете "зламати" його за допомогою контейнера webm, але я не очікував, що він буде підтримуватися повсюдно. Те, що ви шукаєте, включено до розширень Media Source Extensions, де ви можете подавати фрагменти в браузер по одному. але вам потрібно буде написати деякий JavaScript на стороні клієнта.


Є, 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

Спробуйте binaryjs. Це так само, як socket.io, але єдине, що він робить добре, це те, що він передає аудіо відео. Binaryjs google це


1
Binary.JS - це не що інше, як Socket.IO. І це не характерно для потокового передавання медіа.
Бред
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.