Estoy realmente atrapado tratando de entender la mejor manera de transmitir la salida en tiempo real de ffmpeg a un cliente HTML5 usando node.js, ya que hay una serie de variables en juego y no tengo mucha experiencia en este espacio, Después de pasar muchas horas probando diferentes combinaciones.
Mi caso de uso es:
1) FFMPEG recoge el flujo RTSP H.264 de la cámara de video IP y lo remuxa a un contenedor mp4 usando la siguiente configuración de FFMPEG en el nodo, salida a STDOUT. Esto solo se ejecuta en la conexión inicial del cliente, de modo que las solicitudes de contenido parcial no intenten generar FFMPEG nuevamente.
liveFFMPEG = child_process.spawn("ffmpeg", [
"-i", "rtsp://admin:[email protected]:554" , "-vcodec", "copy", "-f",
"mp4", "-reset_timestamps", "1", "-movflags", "frag_keyframe+empty_moov",
"-" // output to stdout
], {detached: false});
2) Uso el servidor http del nodo para capturar el STDOUT y transmitirlo al cliente cuando lo solicite el cliente. Cuando el cliente se conecta por primera vez, engendro la línea de comando FFMPEG anterior y luego canalizo el flujo STDOUT a la respuesta HTTP.
liveFFMPEG.stdout.pipe(resp);
También he usado el evento stream para escribir los datos FFMPEG en la respuesta HTTP, pero no hay diferencia
xliveFFMPEG.stdout.on("data",function(data) {
resp.write(data);
}
Uso el siguiente encabezado HTTP (que también se usa y funciona al transmitir archivos pregrabados)
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) El cliente tiene que usar etiquetas de video HTML5.
No tengo problemas con la reproducción de transmisión (usando fs.createReadStream con 206 contenido parcial HTTP) al cliente HTML5 un archivo de video previamente grabado con la línea de comando FFMPEG anterior (pero guardado en un archivo en lugar de STDOUT), así que sé la transmisión FFMPEG es correcto, e incluso puedo ver correctamente la transmisión de video en vivo en VLC cuando me conecto al servidor de nodo HTTP.
Sin embargo, tratar de transmitir en vivo desde FFMPEG a través del nodo HTTP parece ser mucho más difícil ya que el cliente mostrará un cuadro y luego se detendrá. Sospecho que el problema es que no estoy configurando la conexión HTTP para que sea compatible con el cliente de video HTML5. He intentado una variedad de cosas como usar HTTP 206 (contenido parcial) y 200 respuestas, poner los datos en un búfer y luego transmitir sin suerte, por lo que necesito volver a los primeros principios para asegurarme de que estoy configurando esto correctamente camino.
Aquí entiendo cómo debería funcionar esto, corríjame si me equivoco:
1) FFMPEG debe configurarse para fragmentar la salida y utilizar un moov vacío (FFMPEG frag_keyframe y empty_moov mov flags). Esto significa que el cliente no usa el átomo de moov, que generalmente se encuentra al final del archivo, que no es relevante cuando se transmite (sin final de archivo), pero significa que no es posible buscar, lo cual está bien para mi caso de uso.
2) Aunque uso fragmentos MP4 y MOOV vacío, todavía tengo que usar contenido parcial HTTP, ya que el reproductor HTML5 esperará hasta que se descargue toda la transmisión antes de reproducirla, lo que con una transmisión en vivo nunca termina, por lo que es inviable.
3) No entiendo por qué la canalización de la transmisión STDOUT a la respuesta HTTP no funciona cuando se transmite en vivo, pero si guardo en un archivo, puedo transmitir este archivo fácilmente a clientes HTML5 usando un código similar. Tal vez sea un problema de tiempo, ya que la generación de FFMPEG tarda un segundo en iniciarse, conectarse a la cámara IP y enviar fragmentos al nodo, y los eventos de datos del nodo también son irregulares. Sin embargo, bytestream debería ser exactamente lo mismo que guardar en un archivo, y HTTP debería poder atender las demoras.
4) Al verificar el registro de red desde el cliente HTTP al transmitir un archivo MP4 creado por FFMPEG desde la cámara, veo que hay 3 solicitudes de cliente: una solicitud GET general para el video, que el servidor HTTP devuelve aproximadamente 40Kb, luego un parcial solicitud de contenido con un rango de bytes para los últimos 10K del archivo, luego una solicitud final para los bits en el medio no cargados. ¿Quizás el cliente HTML5 una vez que recibe la primera respuesta está pidiendo la última parte del archivo para cargar el átomo MP4 MOOV? Si este es el caso, no funcionará para la transmisión ya que no hay un archivo MOOV ni un final del archivo.
5) Cuando reviso el registro de la red cuando intento transmitir en vivo, recibo una solicitud inicial cancelada con solo unos 200 bytes recibidos, luego una nueva solicitud abortada nuevamente con 200 bytes y una tercera solicitud que solo tiene 2K de longitud. No entiendo por qué el cliente HTML5 abortaría la solicitud, ya que bytestream es exactamente lo mismo que puedo usar con éxito cuando se transmite desde un archivo grabado. También parece que el nodo no está enviando el resto de la secuencia FFMPEG al cliente, pero puedo ver los datos FFMPEG en la rutina del evento .on, por lo que está llegando al servidor HTTP del nodo FFMPEG.
6) Aunque creo que la conexión de la secuencia STDOUT al búfer de respuesta HTTP debería funcionar, ¿tengo que crear un búfer intermedio y una secuencia que permita que las solicitudes de cliente de contenido parcial HTTP funcionen correctamente como lo hace cuando lee (con éxito) un archivo ? Creo que esta es la razón principal de mis problemas, sin embargo, no estoy exactamente seguro en Node sobre cómo configurarlo mejor. Y no sé cómo manejar una solicitud de cliente para los datos al final del archivo, ya que no hay un final de archivo.
7) ¿Estoy en el camino equivocado al tratar de manejar 206 solicitudes de contenido parcial, y esto debería funcionar con 200 respuestas HTTP normales? Las respuestas HTTP 200 funcionan bien para VLC, ¿así que sospecho que el cliente de video HTML5 solo funcionará con solicitudes de contenido parcial?
Como todavía estoy aprendiendo estas cosas, es difícil trabajar a través de las diversas capas de este problema (FFMPEG, nodo, transmisión, HTTP, video HTML5), por lo que cualquier puntero será muy apreciado. He pasado horas investigando en este sitio y en la red, y no he encontrado a nadie que haya podido hacer streaming en tiempo real en el nodo, pero no puedo ser el primero, y creo que esto debería funcionar (de alguna manera !).
Content-Type
en tu cabeza? ¿Estás usando codificación de fragmentos? Ahí es donde comenzaría. Además, HTML5 no necesariamente proporciona la funcionalidad para transmitir, puede leer más sobre eso aquí . Lo más probable es que necesite implementar una forma de almacenar en búfer y reproducir la transmisión de video utilizando sus propios medios ( ver aquí ), aunque es probable que esto no sea compatible. También google en MediaSource API.Respuestas:
Todo debajo de esta línea está desactualizado. Manteniéndolo aquí para la posteridad.
Hay muchas razones por las que el video y, específicamente, el video en vivo es muy difícil. (Tenga en cuenta que la pregunta original especificaba que el video HTML5 es un requisito, pero el autor de la pregunta declaró que Flash es posible en los comentarios. Entonces, de inmediato, esta pregunta es engañosa)
Primero voy a repetir: NO HAY APOYO OFICIAL PARA LA TRANSMISIÓN EN VIVO SOBRE HTML5 . Hay hacks, pero su kilometraje puede variar.
A continuación, debe comprender que Video on demand (VOD) y video en vivo son muy diferentes. Sí, ambos son videos, pero los problemas son diferentes, por lo tanto, los formatos son diferentes. Por ejemplo, si el reloj de su computadora funciona un 1% más rápido de lo que debería, no lo notará en un VOD. Con el video en vivo, intentará reproducir el video antes de que suceda. Si desea unirse a una transmisión de video en vivo en progreso, necesita los datos necesarios para inicializar el decodificador, por lo que debe repetirse en la transmisión o enviarse fuera de banda. Con VOD, puede leer el comienzo del archivo que buscan hasta el punto que desee.
Ahora profundicemos un poco.
Plataformas:
Códecs:
Métodos comunes de entrega de video en vivo en navegadores:
Métodos de entrega comunes para VOD en navegadores:
etiqueta de video html5:
Veamos qué navegadores admiten qué formatos
Safari:
Firefox
ES DECIR
Cromo
MP4 no se puede usar para video en vivo (NOTA: DASH es un superconjunto de MP4, así que no se confunda con eso). MP4 se divide en dos partes: moov y mdat. mdat contiene los datos de audio y video sin procesar. Pero no está indexado, por lo que sin el moov, es inútil. El moov contiene un índice de todos los datos en el mdat. Pero debido a su formato, no se puede 'aplanar' hasta que se conozcan las marcas de tiempo y el tamaño de CADA cuadro. Puede ser posible construir un moov que 'engorde' el tamaño de los cuadros, pero es muy costoso en cuanto a ancho de banda.
Entonces, si desea realizar entregas en todas partes, necesitamos encontrar el mínimo común denominador. Verá que no hay LCD aquí sin recurrir al ejemplo de flash:
Lo más parecido a una pantalla LCD es usar HLS para obtener sus usuarios de iOS y flashear para todos los demás. Mi favorito personal es codificar HLS, luego usar flash para reproducir HLS para todos los demás. Puedes jugar HLS en flash a través del reproductor JW 6 (o escribir tu propio HLS en FLV en AS3 como lo hice yo)
Pronto, la forma más común de hacer esto será HLS en iOS / Mac y DASH a través de MSE en cualquier otro lugar (esto es lo que Netflix hará pronto). Pero todavía estamos esperando que todos actualicen sus navegadores. También es probable que necesite un DASH / VP9 separado para Firefox (sé acerca de open264; apesta. No puede hacer videos en el perfil principal o alto. Por lo tanto, actualmente es inútil).
fuente
Gracias a todos, especialmente a Szatmary, ya que esta es una pregunta compleja y tiene muchas capas, todas las cuales deben estar funcionando antes de poder transmitir video en vivo. Para aclarar mi pregunta original y el uso de video HTML5 vs flash: mi caso de uso tiene una fuerte preferencia por HTML5 porque es genérico, fácil de implementar en el cliente y en el futuro. Flash es un segundo mejor distante, así que sigamos con HTML5 para esta pregunta.
Aprendí mucho a través de este ejercicio y estoy de acuerdo en que la transmisión en vivo es mucho más difícil que VOD (que funciona bien con video HTML5). Pero logré que esto funcione satisfactoriamente para mi caso de uso y la solución resultó ser muy simple, después de perseguir opciones más complejas como MSE, flash, esquemas de almacenamiento intermedio elaborados en Node. El problema era que FFMPEG estaba corrompiendo el MP4 fragmentado y tuve que ajustar los parámetros de FFMPEG, y la redirección de la tubería de flujo de nodo estándar sobre http que usé originalmente era todo lo que se necesitaba.
En MP4 hay una opción de 'fragmentación' que divide el mp4 en fragmentos mucho más pequeños que tiene su propio índice y hace viable la opción de transmisión en vivo mp4. Pero no es posible buscar nuevamente en la secuencia (OK para mi caso de uso), y las versiones posteriores de FFMPEG admiten la fragmentación.
Tenga en cuenta que el tiempo puede ser un problema, y con mi solución tengo un retraso de entre 2 y 6 segundos causado por una combinación de remuxing (efectivamente, FFMPEG tiene que recibir la transmisión en vivo, remuxarla y luego enviarla al nodo para servir a través de HTTP) . No se puede hacer mucho al respecto, sin embargo, en Chrome el video intenta ponerse al día tanto como sea posible, lo que hace que el video sea un poco nervioso pero más actual que IE11 (mi cliente preferido).
En lugar de explicar cómo funciona el código en esta publicación, consulte el GIST con comentarios (el código del cliente no está incluido, es una etiqueta de video HTML5 estándar con la dirección del servidor http del nodo). GIST está aquí: https://gist.github.com/deandob/9240090
No he podido encontrar ejemplos similares de este caso de uso, así que espero que la explicación y el código anteriores ayuden a otros, especialmente porque he aprendido mucho de este sitio y todavía me considero un principiante.
Aunque esta es la respuesta a mi pregunta específica, he seleccionado la respuesta de szatmary como la aceptada, ya que es la más completa.
fuente
Echa un vistazo al proyecto JSMPEG . Hay una gran idea implementada allí: decodificar MPEG en el navegador usando JavaScript. Los bytes del codificador (FFMPEG, por ejemplo) se pueden transferir al navegador mediante WebSockets o Flash, por ejemplo. Creo que si la comunidad se pone al día, será la mejor solución de transmisión de video en vivo HTML5 por ahora.
fuente
Escribí un reproductor de video HTML5 alrededor del códec broadway h264 (emscripten) que puede reproducir video h264 en vivo (sin demora) en todos los navegadores (escritorio, iOS, ...).
La transmisión de video se envía a través de WebSocket al cliente, se decodifica cuadro por cuadro y se muestra en una canva (usando webgl para la aceleración)
Echa un vistazo a https://github.com/131/h264-live-player en github.
fuente
Una forma de transmitir en vivo una cámara web basada en RTSP a un cliente HTML5 (implica volver a codificar, así que espere una pérdida de calidad y necesite algo de potencia de CPU):
En la máquina que recibe la transmisión de la cámara, no use FFMPEG sino gstreamer. Es capaz de recibir y decodificar el flujo RTSP, volver a codificarlo y transmitirlo al servidor Icecast. Canalización de ejemplo (solo video, sin audio):
=> Luego puede usar la etiqueta <video> con la URL de icecast-stream ( http://127.0.0.1:12000/cam.webm ) y funcionará en todos los navegadores y dispositivos que admitan webm
fuente
Echa un vistazo a esta solución . Como sé, Flashphoner permite reproducir audio en vivo + transmisión de video en la página HTML5 pura.
Utilizan códecs MPEG1 y G.711 para la reproducción. El truco es renderizar video decodificado a un elemento de lienzo HTML5 y reproducir audio decodificado a través del contexto de audio HTML5.
fuente
¿Qué hay de usar la solución jpeg, solo deje que el servidor distribuya jpeg uno por uno al navegador, luego use el elemento de lienzo para dibujar estos jpegs? http://thejackalofjavascript.com/rpi-live-streaming/
fuente
Esto es un malentendido muy común. No hay soporte de video HTML5 en vivo (excepto HLS en iOS y Mac Safari). Es posible que pueda 'hackearlo' utilizando un contenedor webm, pero no esperaría que eso sea universalmente compatible. Lo que está buscando se incluye en las Extensiones de origen de medios, donde puede alimentar los fragmentos al navegador de uno en uno. pero deberá escribir algunos javascript del lado del cliente.
fuente
solutions
pero no haysupport
para la transmisión en vivo. Esto se refiere directamente a mi comentario visto anteriormente. Y webm es compatible con los principales navegadores, principalmente la última versión estable.Prueba binaryjs. Es como socket.io, pero lo único que hace bien es que transmite audio y video. Binaryjs google it
fuente