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:12345@192.168.1.234: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.