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


213

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


4
Este é um assunto complicado. Primeiras coisas primeiro. Você colocou Content-Typesua 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.
tsturzl 21/02

Obrigado pela resposta. Sim, o tipo de conteúdo é 'video / mp4' e esse código funciona para transmitir arquivos de vídeo. Infelizmente o MediaSource é apenas para cromo, tenho que suportar outros navegadores. Existe uma especificação sobre como o cliente de vídeo HTML5 interage com um servidor de streaming HTTP? Tenho certeza de que o que eu quero pode ser feito, só não tenho certeza exatamente como (com node.js mas poderia usar C # ou C ++, se é mais fácil)
deandob

2
O problema não está no seu back-end. Você está transmitindo vídeo muito bem. O problema está no seu frontend / cliente, você precisa implementar o streaming por conta própria. O HTML5 simplesmente não lida com fluxos. Você precisará explorar as opções por navegador, provavelmente. Ler os padrões w3 para as tags de vídeo e APIs de mídia seria um bom ponto de partida.
tsturzl

Parece que deveria ser possível fazer isso funcionar. Não estou oferecendo uma resposta definitiva, mas suspeito que esse problema esteja relacionado ao fato de o navegador estar aguardando o restante dos cabeçalhos / átomos do contêiner mp4 no início e não o próximo quadro no fluxo de vídeo. Se você enviar um átomo do MOOV para um vídeo muito longo (para que o player continue solicitando), bem como os outros cabeçalhos esperados e depois começar a copiar do ffmpeg, isso pode funcionar. Você também teria que ocultar a barra de opções usando js no navegador para que eles não possam avançar.
jwriteclub

Eu sugeriria considerar o WebRTC, que está obtendo melhor suporte entre navegadores no dia a dia.
Alex Cohn

Respostas:


209

EDIT 3: A partir do IOS 10, o HLS suportará arquivos mp4 fragmentados. A resposta agora é criar ativos mp4 fragmentados, com um manifesto DASH e HLS. > Finja que o flash, iOS9 e abaixo e IE 10 e abaixo não existem.

Tudo abaixo desta linha está desatualizado. Mantendo-o aqui para a posteridade.


EDIT 2: Como as pessoas nos comentários estão apontando, as coisas mudam. Quase todos os navegadores suportam codecs AVC / AAC. O iOS ainda requer HLS. Mas através de adaptadores como o hls.js, você pode jogar o HLS no MSE. A nova resposta é HLS + hls.js se você precisar do iOS. ou apenas MP4 fragmentado (ou seja, DASH) se você não

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.

EDIT: desde que escrevi esta resposta, as extensões de fonte de mídia amadureceram e agora estão muito perto de se tornar uma opção viável. Eles são suportados na maioria dos principais navegadores. O IOS continua sendo um obstáculo.

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:

  • iOS
  • PC
  • Mac
  • Android

Codecs:

  • vp8 / 9
  • h.264
  • thora (vp3)

Métodos comuns de entrega para vídeo ao vivo em navegadores:

  • DASH (HTTP)
  • HLS (HTTP)
  • flash (RTMP)
  • flash (HDS)

Métodos comuns de entrega para VOD em navegadores:

  • DASH (HTTP Streaming)
  • HLS (HTTP Streaming)
  • flash (RTMP)
  • flash (HTTP Streaming)
  • MP4 (pseudo streaming HTTP)
  • Não vou falar sobre MKV e OOG porque não os conheço muito bem.

tag de vídeo html5:

  • MP4
  • webm
  • ogg

Vamos analisar quais navegadores suportam quais formatos

Safári:

  • HLS (apenas iOS e Mac)
  • h.264
  • MP4

Raposa de fogo

  • DASH (via MSE, mas não h.264)
  • h.264 apenas via Flash!
  • VP9
  • MP4
  • OGG
  • Webm

IE

  • Instantâneo
  • DASH (apenas através do MSE IE 11+)
  • h.264
  • MP4

cromada

  • Instantâneo
  • DASH (via MSE)
  • h.264
  • VP9
  • MP4
  • webm
  • ogg

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 iOS suporta apenas vídeos h.264. e suporta apenas HLS para live.
  • O Firefox não suporta h.264, a menos que você use o flash
  • O Flash não funciona no iOS

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).


Obrigado szatmary pelo histórico detalhado e pelos prós / contras sobre as várias opções. Selecionei essa resposta como a aceita, pois o esboço dos conceitos é mais importante do que a correção específica que encontrei para responder à pergunta original. Boa sorte com a recompensa!
deandob

9
Esta não é uma solução funcional para esta pergunta. Há uma solução funcional para esse problema abaixo.
precisa saber é o seguinte

2
O Firefox agora suporta MSE e h.264 nativamente. Acesse www.youtube.com/html5 com o navegador FF mais recente para confirmar. Eu testei com o FF 37. O Safari 8+ no Mac também agora suporta MSE.
BigTundra

@BigTundra sim, o safari suporta MSE desde o lançamento do Yosemite no Mac. Mas não iOS. Não tenho certeza sobre o Windows. (O safari no Windows ainda é uma coisa?) O Firefox 37.0.2 no (meu) Mac parece não suportar o MSE de acordo com esse link. Mas suporta H.264. O Firefox adicionou e removeu e voltou a adicionar o suporte H.264 no passado.
Szatmary

Suporte atualizado ao navegador para o formato de vídeo MPEG-4 / H.264: caniuse.com/#feat=mpeg4
Maxence

75

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.


33
Desculpe, mas eu encontrei isso sozinho, a redação da minha resposta deixa isso bem claro. As respostas anteriores foram úteis e apreciadas, mas não contribuíram significativamente, e eu até enviei o código de trabalho no GIST e ninguém mais o enviou. Não estou interessado na 'reputação', estou interessado em aprender a saber se minha abordagem e código podem ser aprimorados. E a resposta que marquei resolveu o meu problema, por isso estou confuso sobre qual é o problema aqui. Sou bastante novo no SO, por isso estou feliz em receber instruções para interagir de uma maneira diferente. Acho este site útil e minha resposta deve ajudar os outros.
Deandob #

2
Parece que não é apropriado nesta comunidade selecionar sua resposta como a resposta aceita, se você fez a pergunta, mesmo que ela resolva o problema original. Embora isso pareça contra-intuitivo, a documentação dos conceitos é mais importante que a correção real, com a qual estou bem, pois ajuda os outros a aprender. Desativei minha resposta e selecionei a szatmary como a mais articulada em torno dos conceitos.
precisa saber é o seguinte

6
@ deandob: Publiquei uma recompensa por uma solução funcional para esse problema que você forneceu com sucesso. A resposta aceita afirma que não há solução de trabalho e, portanto, é claramente imprecisa.
precisa saber é o seguinte

2
Obrigado. Parece que outras pessoas votaram negativamente na minha resposta original como errada e, como sou nova, presumi que é assim que as coisas funcionam por aqui. Eu não quero causar nenhum barulho, mas vou verificar com o pessoal no estouro de meta stack. BTW - minha solução está funcionando muito bem e deve ser viável para outras pessoas, e há uma variação na solução postada que pode reduzir o atraso inicial (o buffer no node.js inicialmente busca o final do fluxo no final do cliente) .
deandob

4
Tenho um esclarecimento de um moderador que minha abordagem original de responder à pergunta pessoalmente e selecioná-la como resposta foi a abordagem correta. Para mais informações (ou se você quiser debater mais sobre isso), consulte o tópico no meta site. meta.stackexchange.com/questions/224068/…
deandob 4/14

14

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.


10
Esse é um decodificador de vídeo MPEG-1. Não sei se você entende como o MPEG-1 é antigo; é mais antigo que DVDs. É um pouco mais avançado que um arquivo GIF.
Camilo Martin

13

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.


1
github.com/Streamedian/html5_rtsp_player Esses caras fizeram algo semelhante que usa rtp h264 sobre websocket
Victor.dMdB

12

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):

  • Configurar um servidor icecast (pode estar na mesma máquina em que o servidor web está ou na máquina que recebe o fluxo RTSP da câmera)
  • 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):

    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

=> 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


3

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.



2

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.


Existem solutionsmas não existem supportpara 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.
precisa saber é o seguinte

1
Eu realmente preferiria não transcodificar do H.264 para o webm e isso não seria necessário. Além disso, como tenho que dar suporte ao IE11 e Safari, as extensões MediaSource não ajudarão. Mas acho que se simular um fluxo de arquivos no servidor (o que funciona!), Ele deve funcionar, mas terei que simular um buffer de arquivo no node.js.
Deandob

1
Como outro sugerido, eu procuraria a possibilidade de usar o WebRTC, que é nativo, diferentemente do VLC ou do plugin flash. Eu sei que essa tecnologia ainda é difícil de implementar. Boa sorte.

1
Consegui que isso funcionasse atualizando para a versão mais recente do FFMPEG, pois parece que houve corrupção no mp4 ao usar o modo fragmentado (necessário para o streaming ao vivo do MP4, para que o cliente não esteja esperando o arquivo de índice do moov que nunca será lançado quando estiver ao vivo transmissão). E meu código node.js para redirecionar o fluxo FFMPEG diretamente para o navegador agora funciona.
deandob

1
Sim, funciona bem no IE11 (meu navegador preferido). Recebo uma resposta irregular no Chrome.
deandob

2

Tente binaryjs. É como o socket.io, mas a única coisa que faz bem é transmitir vídeo de áudio. Binaryjs google it


1
Binary.JS não é nada como Socket.IO. E não é específico para streaming de mídia.
21417 Brad
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.