Certa vez, em dos artigos antigos e já abandonados, escrevi sobre como é fácil e despretensiosamente transmitir vídeo de um canvas através de websockets. Naquele artigo, falei superficialmente sobre como capturar vídeo da câmera e áudio do microfone usando , como codificar o fluxo obtido e enviá-lo via websockets para o servidor. No entanto, na realidade, isso não é feito; para transmissões, utiliza-se ou software especial que precisa ser instalado e configurado, como , ou emprega-se o WebRTC, que funciona direto da caixa, ou seja, não requer a instalação de nenhum plugin como o flash player, que será removido do navegador Chromium em dezembro.
Hoje, vamos falar sobre WebRTC.
A Comunicação em Tempo Real pela Web (WebRTC) não é um protocolo único, mas uma coleção inteira de padrões, protocolos e APIs JavaScript que juntos garantem comunicações de vídeo e áudio em tempo real peer-to-peer, além de poderem ser usados para transferir quaisquer dados binários. Normalmente, os pares são navegadores, mas isso também pode ser um aplicativo móvel, por exemplo. Para organizar a comunicação p2p entre os clientes, é necessária a compatibilidade do navegador com vários tipos de codificação de vídeo e áudio, suporte a muitos protocolos de rede e interação do hardware com o navegador (através de camadas do sistema operacional): webcams e placas de som. Todo esse emaranhado de tecnologias é ocultado por uma abstração da API JavaScript para conveniência do desenvolvedor.
Tudo se resume a três APIs:
— que discutimos na última vez, hoje vou escrever um pouco mais sobre isso. Serve para obter fluxos de vídeo/aúdio do ‘hardware’
— assegura comunicações entre dois clientes (p2p)
— serve para transmitir dados arbitrários entre dois clientes
Preparação de fluxos de áudio e vídeo para transmissão
Tudo começa com a "captura" dos fluxos de mídia da webcam e do microfone. Os fluxos brutos, é claro, não são adequados para uma videoconferência, cada fluxo precisa ser processado: melhorar a qualidade, sincronizar o áudio com o vídeo, marcar pontos de sincronização no fluxo de vídeo, garantir que a taxa de bits corresponda à largura de banda variável. O navegador cuida de tudo isso, o desenvolvedor não precisa se preocupar em codificar os fluxos de mídia. Dentro de um navegador moderno, já existem camadas de software para captura, melhoria da qualidade (eliminar eco e ruído do som, melhorar a imagem), codificação de vídeo e áudio. O esquema das camadas está mostrado na Fig. 1:
Fig. 1. Camadas de processamento de áudio e vídeo no navegador
Todo o processamento ocorre diretamente no navegador, nenhum plugin adicional é necessário. No entanto, a situação ainda não é tão otimista em 2020. Existem navegadores que ainda não suportam completamente , você pode seguir o link e, na parte inferior, ver a tabela de compatibilidade. Em particular, o IE desaponta novamente.
Com os fluxos obtidos, é possível fazer coisas muito interessantes: você pode clonar, mudar a resolução do vídeo, manipular a qualidade do áudio, pode "anexar" o fluxo Media Stream ao <video> tag e ver a si mesmo na página HTML. Você também pode desenhar o fluxo no canvas, usar WebGL ou CSS3, aplicar vários filtros ao vídeo, capturar o vídeo processado do canvas e enviá-lo pela rede para o servidor, transcodificar e publicar para todos interessados (olá bigo live, twitch, e outros). Aqui, não vou detalhar como essas coisas são feitas, apenas vou dar alguns exemplos encontrados na web:
— um grupo está trabalhando em CV em tempo real com Javascript. Eles têm um verdadeiro de várias bibliotecas js para trabalhar com fluxos de vídeo no canvas: detecção de rostos, objetos, aplicação de filtros (máscaras, como no Instagram), etc. Um ótimo exemplo de como é possível processar vídeo em tempo real diretamente no navegador sem plugins adicionais.
— documentação da API para captura de fluxos de vídeo do canvas. Já é suportada no Chrome, Opera e Firefox
RTCPeerConnection
Chegamos à parte de como transferir o vídeo para outro usuário? Surge a questão Se formos resumir, nesta etapa, você precisa criar um objeto RTCPeerConnection:
const peerConnection = new RTCPeerConnection({
iceServers: [{
urls: 'stun:stun.l.google.com:19302'
}]
});Uma das opções que especificamos é iceServers - é um servidor que ajuda a garantir a conexão entre dois navegadores que estão atrás de um NAT. Ou seja, aqui resolve-se o problema: como descobrir o IP do interlocutor se ele estiver atrás do NAT de seu provedor? O protocolo ICE vem em auxílio, na verdade, o ICE não pertence exatamente ao WebRTC, mas falaremos sobre isso mais adiante.
Anteriormente, obtivemos fluxos de Usermedia:
navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(stream => {
// Fluxos de Usermedia, geralmente são vídeo e áudio
const tracks = stream.getTracks();
for (const track of tracks) {
// cada trilha é adicionada ao peerConnection
peerConnection.addTrack(track);
}
}).catch(console.error);Em seguida, no peerConnection, o evento onnegotiationneeded é acionado, no manipulador, devemos criar uma oferta (em termos de SDP - Session Description Protocol) e atribuí-la ao peerConnection através do método setLocalDescription. Sobre o SDP - o que é isso e sobre os formatos de oferta e resposta - falaremos em breve.
Após a designação de LocalDescription no peerConnection, o navegador "coleta" candidatos ice, ou seja, encontra diferentes caminhos para comunicação através do NAT. O evento onicegatheringstatechange é acionado. No manipulador onicegatheringstatechange, permitimos a conexão com o servidor de sinalização webrtc para trocar a Session Description entre os pares:
peerConnection.oniceconnectionstatechange = (event) => {
console.log('Connection state: ', peerConnection.iceConnectionState);
if (peerConnection.iceConnectionState === 'connected') {
// Можем активировать кнопку Start broadcast
setBroadcasting(true);
setBroadcastingBtnActive(true);
}
};
// Событие срабатывает сразу, как только добавился медаиапоток в peerConnection
peerConnection.onnegotiationneeded = (event) => {
// Создаем и назначаем SDP offer
peerConnection.createOffer().
then((offer) => peerConnection.setLocalDescription(offer)).
catch(console.error);
};
// Событие срабатывает каждый раз, как появляется ICE кандидат
peerConnection.onicegatheringstatechange = (ev) => {
let connection = ev.target;
// Now we can activate broadcast button
if (connection.iceGatheringState === 'complete') {
let delay = 50;
let tries = 0;
let maxTries = 3;
let timerId = setTimeout(function allowStreaming() {
if (isOnline) {
setBroadcastingBtnActive(true);
return;
}
if (tries < maxTries) {
tries += 1;
delay *= 2;
timerId = setTimeout(allowStreaming, delay);
} else {
// TODO: show user notification
console.error("Can't connect to server");
alert("Can't connect to server");
}
}, delay);
}
};O servidor de sinalização webrtc é um servidor necessário para garantir a troca da descrição da sessão entre dois pares, pode ser um websocket simples ou um servidor xhr em qualquer linguagem de programação. Sua tarefa é simples: aceitar a descrição da sessão de um par e transmitir a outro.
Após a troca de descrições de sessão, ambas as partes estão prontas para transmitir e receber fluxos de vídeo; do lado que recebe o fluxo de vídeo, o evento ontrack é acionado no peerConnection, no manipulador deste evento, as trilhas recebidas podem ser atribuídas ao
Links e literatura:
— documentação
— implementação dos protocolos WebRTC em Go
— um livrinho dos criadores do pion
— livro High Performance Browser Networking. Detalha questões relacionadas à garantia de alto desempenho de aplicativos da web. No final, descreve sobre WebRTC. É claro que o livro é antigo (2013), mas ainda se mantém relevante.
Na próxima parte, quero fornecer um pouco mais de teoria e analisar na prática a recepção e o processamento de fluxo de vídeo no servidor usando o pion, realizando transcodificação em HLS através do ffmpeg para a transmissão subsequente para os espectadores no navegador.
Para os impacientes: (é apenas um experimento).
Fonte: habr.com
