Melhor abordagem para streaming http em tempo real para o cliente de vídeo HTML5

Estou realmente empolgado tentando entender a melhor maneira de transmitir a saída do ffmpeg em tempo real para um cliente HTML5 usando o node.js, pois há várias variáveis em jogo e não tenho muita experiência nesse espaço, tendo passado muitas horas tentando combinações diferentes.

Meu caso de uso é:

1) O fluxo RTSP H.264 da câmera de vídeo IP é captado pelo FFMPEG e remuxado em um contêiner mp4 usando as seguintes configurações do FFMPEG no nó, saída para STDOUT. Isso é executado apenas na conexão inicial do cliente, para que solicitações parciais de conteúdo não tentem gerar o FFMPEG novamente.

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) Utilizo o servidor http do nó para capturar o STDOUT e transmiti-lo de volta ao cliente mediante solicitação do cliente. Quando o cliente se conecta pela primeira vez, eu gero a linha de comando FFMPEG acima e canalize o fluxo STDOUT para a resposta HTTP.

liveFFMPEG.stdout.pipe(resp);

Também usei o evento de fluxo para gravar os dados do FFMPEG na resposta HTTP, mas não faz diferença

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

Eu uso o seguinte cabeçalho HTTP (que também é usado e funciona ao transmitir arquivos pré-gravados)

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) O cliente precisa usar tags de vídeo HTML5.

Não tenho problemas com a reprodução de streaming (usando fs.createReadStream com 206 conteúdo parcial HTTP) para o cliente HTML5, um arquivo de vídeo gravado anteriormente com a linha de comando FFMPEG acima (mas salvo em um arquivo em vez de STDOUT), portanto, conheço o fluxo FFMPEG está correto e posso até ver corretamente o vídeo ao vivo no VLC ao conectar-me ao servidor do nó HTTP.

No entanto, tentar transmitir ao vivo a partir do FFMPEG através do nó HTTP parece ser muito mais difícil, pois o cliente exibirá um quadro e depois parará. Eu suspeito que o problema é que não estou configurando a conexão HTTP para ser compatível com o cliente de vídeo HTML5. Eu tentei várias coisas, como usar HTTP 206 (conteúdo parcial) e 200 respostas, colocando os dados em um buffer e depois transmitindo sem sorte. Por isso, preciso voltar aos primeiros princípios para garantir que estou configurando corretamente maneira.

Aqui está o meu entendimento de como isso deve funcionar. Corrija-me se estiver errado:

1) O FFMPEG deve ser configurado para fragmentar a saída e usar um moov vazio (sinalizadores mov FFMPEG frag_keyframe e empty_moov). Isso significa que o cliente não usa o átomo moov, que normalmente fica no final do arquivo, que não é relevante durante o streaming (sem final do arquivo), mas significa que não é possível procurar o que é bom para o meu caso de uso.

2) Embora eu use fragmentos MP4 e MOOV vazio, ainda tenho que usar conteúdo parcial HTTP, pois o player HTML5 aguardará até que todo o fluxo seja baixado antes da reprodução, o que com um fluxo ao vivo nunca termina e, portanto, é impraticável.

3) Não entendo por que canalizar o fluxo STDOUT para a resposta HTTP ainda não funciona durante a transmissão ao vivo. Se eu salvar em um arquivo, eu posso transmitir esse arquivo facilmente para clientes HTML5 usando código semelhante. Talvez seja um problema de tempo, pois leva um segundo para o spawn do FFMPEG iniciar, conectar-se à câmera IP, era e enviar pedaços para o nó, e os eventos de dados do nó também são irregulares. No entanto, o bytestream deve ser exatamente o mesmo que salvar em um arquivo, e o HTTP deve poder atender a atrasos.

4) Ao verificar o log de rede do cliente HTTP ao transmitir um arquivo MP4 criado pelo FFMPEG a partir da câmera, vejo três solicitações de clientes: Uma solicitação GET geral para o vídeo, que o servidor HTTP retorna cerca de 40 KB, depois uma parcial solicitação de conteúdo com um intervalo de bytes para os últimos 10 K do arquivo e, em seguida, uma solicitação final para os bits no meio não carregados. Talvez o cliente HTML5, depois de receber a primeira resposta, esteja pedindo a última parte do arquivo para carregar o átomo MP4 MOOV? Se esse for o caso, ele não funcionará para streaming, pois não há arquivo MOOV e nem final do arquivo.

5) Ao verificar o log da rede ao tentar transmitir ao vivo, recebo uma solicitação inicial abortada com apenas 200 bytes recebidos, uma solicitação novamente abortada com 200 bytes e uma terceira solicitação com apenas 2K de comprimento. Não entendo por que o cliente HTML5 abortou a solicitação, pois o bytestream é exatamente o mesmo que posso usar com êxito ao transmitir a partir de um arquivo gravado. Também parece que o nó não está enviando o restante do fluxo FFMPEG para o cliente, mas eu posso ver os dados do FFMPEG na rotina de eventos .on, para que ele chegue ao servidor HTTP do nó do FFMPEG.

6) Embora eu pense que canalizar o fluxo STDOUT para o buffer de resposta HTTP deve funcionar, eu tenho que construir um buffer e um fluxo intermediários que permitam que as solicitações do cliente de conteúdo parcial HTTP funcionem corretamente como quando lê (com êxito) um arquivo ? Acho que esse é o principal motivo dos meus problemas, no entanto, no Node não sei exatamente como configurá-lo da melhor maneira. E não sei como lidar com uma solicitação de cliente para os dados no final do arquivo, pois não há fim do arquivo.

7) Estou no caminho errado ao tentar manipular 206 solicitações de conteúdo parcial, e isso deve funcionar com 200 respostas HTTP normais? As respostas HTTP 200 funcionam bem para o VLC, então eu suspeito que o cliente de vídeo HTML5 funcione apenas com solicitações parciais de conteúdo?

Como ainda estou aprendendo essas coisas, é difícil trabalhar com as várias camadas desse problema (FFMPEG, nó, streaming, HTTP, vídeo HTML5), para que qualquer ponteiro seja muito apreciado. Passei horas pesquisando neste site e na rede e não encontrei ninguém capaz de fazer streaming em tempo real no nó, mas não posso ser o primeiro e acho que isso deve funcionar (de alguma forma !).

questionAnswers(9)

yourAnswerToTheQuestion