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 para 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 para o cliente, mediante solicitação do cliente. Quando o cliente se conecta pela primeira vez, 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 o streaming de reprodução (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 isso da maneira certa. 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 de moov, que normalmente fica no final do arquivo, que não é relevante ao transmitir (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 o streaming 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 e enviar blocos 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 cliente: 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 solicitando a última parte do arquivo para carregar o átomo MP4 MOOV? Se for esse o caso, não funcionará para streaming, pois não há arquivo MOOV e nem final do arquivo.
5) Ao verificar o log de 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, 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 ? Eu acho que essa é a principal razão 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 que tenha conseguido fazer streaming em tempo real no nó, mas não posso ser o primeiro e acho que isso deve funcionar (de alguma forma !).
Content-Type
sua cabeça na sua cabeça? Você está usando a codificação de chunk? É aí que eu começaria. Além disso, o HTML5 não fornece necessariamente a funcionalidade para transmitir, você pode ler mais sobre isso aqui . Você provavelmente precisará implementar uma maneira de armazenar em buffer e reproduzir o fluxo de vídeo usando seus próprios meios ( veja aqui ), embora isso provavelmente não seja bem suportado. Também google na API MediaSource.Respostas:
Tudo abaixo desta linha está desatualizado. Mantendo-o aqui para a posteridade.
Há muitas razões pelas quais o vídeo e, especificamente, o vídeo ao vivo são muito difíceis. (Observe que a pergunta original especifica que o vídeo HTML5 é um requisito, mas o solicitante afirmou que o Flash é possível nos comentários. Portanto, imediatamente, essa pergunta é enganosa)
Primeiro, vou reafirmar: NÃO HÁ SUPORTE OFICIAL PARA TRANSMISSÃO AO VIVO EM HTML5 . Existem hacks, mas sua milhagem pode variar.
Em seguida, você precisa entender que o vídeo sob demanda (VOD) e o vídeo ao vivo são muito diferentes. Sim, ambos são de vídeo, mas os problemas são diferentes, portanto, os formatos são diferentes. Por exemplo, se o relógio no seu computador correr 1% mais rápido do que deveria, você não notará em um VOD. Com o vídeo ao vivo, você tentará reproduzir o vídeo antes que ele aconteça. Se você deseja ingressar em um fluxo de vídeo ao vivo em andamento, precisará dos dados necessários para inicializar o decodificador, para que ele seja repetido no fluxo ou enviado para fora da banda. Com o VOD, você pode ler o início do arquivo que eles procuram até o ponto que desejar.
Agora vamos cavar um pouco.
Plataformas:
Codecs:
Métodos comuns de entrega para vídeo ao vivo em navegadores:
Métodos comuns de entrega para VOD em navegadores:
tag de vídeo html5:
Vamos analisar quais navegadores suportam quais formatos
Safári:
Raposa de fogo
IE
cromada
O MP4 não pode ser usado para vídeo ao vivo (NOTA: DASH é um superconjunto do MP4, portanto, não se confunda com isso). MP4 é dividido em duas partes: moov e mdat. mdat contém os dados brutos de áudio e vídeo. Mas não é indexado, portanto, sem o moov, é inútil. O moov contém um índice de todos os dados no mdat. Porém, devido ao seu formato, ele não pode ser 'achatado' até que os carimbos de data e hora e o tamanho de TODOS os quadros sejam conhecidos. Pode ser possível construir um moov que 'diminua' o tamanho do quadro, mas é uma grande perda de largura de banda.
Portanto, se você deseja entregar em qualquer lugar, precisamos encontrar o denominador menos comum. Você verá que não há LCD aqui sem recorrer ao exemplo do flash:
O mais próximo de um LCD é usar o HLS para obter usuários do iOS e fazer o flash para todos os outros. Meu favorito pessoal é codificar HLS e, em seguida, usar o flash para reproduzir HLS para todos os outros. Você pode reproduzir HLS em flash via JW player 6 (ou gravar seu próprio HLS em FLV no AS3, como eu fiz).
Em breve, a maneira mais comum de fazer isso será o HLS no iOS / Mac e o DASH via MSE em qualquer outro lugar (é o que a Netflix fará em breve). Mas ainda estamos esperando que todos atualizem seus navegadores. Você provavelmente também precisará de um DASH / VP9 separado para o Firefox (eu sei sobre o open264; é péssimo. Ele não pode gravar vídeos no perfil principal ou alto. Portanto, atualmente é inútil).
fonte
Obrigado a todos, especialmente à szatmary, pois essa é uma pergunta complexa e tem muitas camadas, todas que precisam estar funcionando antes que você possa transmitir o vídeo ao vivo. Para esclarecer minha pergunta original e o uso de vídeo HTML5 vs flash - meu caso de uso tem uma forte preferência pelo HTML5, pois é genérico, fácil de implementar no cliente e no futuro. O Flash é o segundo melhor distante, então vamos ficar com o HTML5 para esta pergunta.
Aprendi muito com este exercício e concordo que a transmissão ao vivo é muito mais difícil que o VOD (que funciona bem com o vídeo HTML5). Mas consegui que isso funcionasse satisfatoriamente para o meu caso de uso e a solução foi muito simples, depois de procurar opções mais complexas como MSE, flash e esquemas de buffer elaborados no Node. O problema era que o FFMPEG estava corrompendo o MP4 fragmentado e eu tive que ajustar os parâmetros do FFMPEG, e o redirecionamento de pipe de fluxo de nó padrão por http que eu usei originalmente era tudo o que era necessário.
No MP4, existe uma opção de 'fragmentação' que divide o mp4 em fragmentos muito menores, com seu próprio índice e viabilizando a opção de transmissão ao vivo do mp4. Mas não é possível procurar novamente no fluxo (OK para o meu caso de uso) e versões posteriores do FFMPEG suportam fragmentação.
Observe que o tempo pode ser um problema e, com a minha solução, tenho um atraso entre 2 e 6 segundos causado por uma combinação do remuxing (efetivamente o FFMPEG precisa receber a transmissão ao vivo, remuxá-la e enviá-la ao nó para servir via HTTP) . Não se pode fazer muito sobre isso, no entanto, no Chrome, o vídeo tenta recuperar o máximo possível, o que torna o vídeo um pouco nervoso, mas mais atual que o IE11 (meu cliente preferido).
Em vez de explicar como o código funciona nesta postagem, confira o GIST com comentários (o código do cliente não está incluído, é uma tag de vídeo HTML5 padrão com o endereço do servidor http do nó). GIST está aqui: https://gist.github.com/deandob/9240090
Não consegui encontrar exemplos semelhantes desse caso de uso, por isso espero que a explicação e o código acima ajudem outras pessoas, especialmente porque aprendi muito neste site e ainda me considero iniciante!
Embora essa seja a resposta para minha pergunta específica, selecionei a resposta de szatmary como a mais aceita, pois é a mais abrangente.
fonte
Dê uma olhada no projeto JSMPEG . Há uma ótima idéia implementada lá - decodificar o MPEG no navegador usando JavaScript. Os bytes do codificador (FFMPEG, por exemplo) podem ser transferidos para o navegador usando WebSockets ou Flash, por exemplo. Se a comunidade alcançar, eu acho, será a melhor solução de streaming de vídeo ao vivo em HTML5 por enquanto.
fonte
Eu escrevi um player de vídeo HTML5 em torno do codec h264 da broadway (emscripten) que pode reproduzir vídeo h264 ao vivo (sem atraso) em todos os navegadores (desktop, iOS, ...).
O fluxo de vídeo é enviado através do websocket ao cliente, quadro decodificado por quadro e exibido em um canva (usando o webgl para aceleração)
Confira https://github.com/131/h264-live-player no github.
fonte
Uma maneira de transmitir ao vivo uma webcam baseada em RTSP para um cliente HTML5 (envolve recodificação, portanto, espere perda de qualidade e precise de energia da CPU):
Na máquina que recebe o fluxo da câmera, não use o FFMPEG, mas o gstreamer. É capaz de receber e decodificar o fluxo RTSP, recodificá-lo e transmiti-lo ao servidor icecast. Exemplo de pipeline (apenas vídeo, sem áudio):
=> Você pode usar a tag <video> com o URL do icecast-stream ( http://127.0.0.1:12000/cam.webm ) e funcionará em todos os navegadores e dispositivos que suportam webm
fonte
Dê uma olhada nesta solução . Como eu sei, o Flashphoner permite reproduzir áudio ao vivo + fluxo de vídeo na página HTML5 pura.
Eles usam os codecs MPEG1 e G.711 para reprodução. O hack é renderizar vídeo decodificado para o elemento de tela HTML5 e reproduzir áudio decodificado via contexto de áudio HTML5.
fonte
Que tal usar a solução jpeg, deixe o servidor distribuir o jpeg um por um no navegador e use o elemento canvas para desenhar esses jpegs? http://thejackalofjavascript.com/rpi-live-streaming/
fonte
Este é um equívoco muito comum. Não há suporte a vídeo HTML5 ao vivo (exceto HLS no iOS e Mac Safari). Você pode 'hackear' usando um contêiner de webm, mas eu não esperaria que isso fosse universalmente suportado. O que você está procurando está incluído nas Extensões de Origem de Mídia, onde você pode alimentar os fragmentos para o navegador, um de cada vez. mas você precisará escrever algum javascript do lado do cliente.
fonte
solutions
mas não existemsupport
para transmissão ao vivo. Isso está diretamente se referindo ao meu comentário visto acima. E o webm é suportado nos principais navegadores, principalmente a versão estável mais recente.Tente binaryjs. É como o socket.io, mas a única coisa que faz bem é transmitir vídeo de áudio. Binaryjs google it
fonte