Trabalho de Redes de Computadores II

Autores:

Tabela de Conteúdos

Introdução

O objetivo do trabalho é criar um servidor UDP de stream de áudio. O servidor deve ser capaz de transmitir à diversos clientes uma captura de voz por meio de seu microfone. Os clientes por sua vez poderão escutar o áudio em tempo real e visualizar as ondas do som com uma animação.

Implementação

O trabalho foi implementado em Python 3, utilizando as bibliotecas:

Concorrência

O programa principal do servidor é responsável por estabelecer as conexões com os clientes. Ao receber uma mensagem o servidor armazena o par endereço/porta do cliente e faz um fork. A partir deste momento o processo filho será responsável por captar e fazer streaming dos dados. O processo pai continua executando para receber novas conexões. Portanto cada cliente está contido em um processo diferente, permitindo o acesso simultâneo de multiplas maquinas.

Captura e execução do áudio

Para realizar a captura e execução do áudio utilizamos a biblioteca alsaaudio. Ela fornece recursos para a leitura e escrita de arquivos de audio com as configurações desejadas.

As configurações do áudio como canais, frequência e formato foram escolhidas apartir de experimentos realizados e configurados como valores fixos no cliente e no servidor. Desta forma, ao fazer a transmissão, os dados sempre estão no formato correto para serem executados.

O número de frames por segundo define o número de bytes, dividido por dois, mínimos que devem estar presentes no arquivo do dispositivo para que uma leitura ocorra. Números maiores aliviam a carga do processador e da rede, mas aumentam a latência e a qualidade do áudio. O número escolhido foi 160, representando 320 Bytes de dados por mensagem.

No servidor cada processo é responsável por relizar sua leitura do dispositivo de áudio, portanto o processo principal não faz leituras no arquivo.

Estrutura e envio de mensagens

A mensagem enviada para os clientes tem tamanho de 1024 Bytes e três campos:

A sequência é definida pelo servidor e inicia em 1. Quando a mensagem chega ao cliente ele verifica a sequência dela e compara com o valor que estava esperando. Se a sequência estiver correta o audio é tocado, se for maior do que esperado o áudio é tocado e as mensagens faltantes são consideradas perdidas. Caso a sequência for menor do que a esperada a mensagem deixa de ser considerada perdida e passa a ser considerada fora de ordem.

Uma vez que o stream de audio deve ser feito em tempo real, as mensagens que se perdem ou chegam fora de ordem podem ser simplesmente descartadas. Com um baixo nível de mensagens perdidas não há grande impacto na qualidade do áudio.

A mensagem é representada com uma tupla mas para o envio é necessário fazer uma serialização. Para isso utilizamos a biblioteca pickle, que converte a estrutura abstrata do python em uma série de Bytes. Esta biblioteca permite restaurar a estrutura de dados original no lado do cliente, facilitando o acesso e modificação dos dados.

A quantidade de mensagens enviada pelo servidor é muito alta. Quando o servidor produz e envia mais mensagens que o cliente pode processar é ocasionado a perda de alguns pacotes. Isso pode ocorrer mesmo quando a conexão é feita em loopback.

Operação com os dados

Para a operação que o cliente faz com os dados devemos serializar e concatenar a onda para permitir o desenho e animação dela na tela.

A operação de plot da onda de audio na tela é pesada, por causa disso desenhar todas as mensagens quando chegam é inviável. Para resolver este problema utilizamos a biblioteca numpy para converter e armazenar os dados em uma estrutura quando chegam e concatenamos diversas mensagens na mesma estrutura. Com a concatenação podemos desenhar uma onda maior com uma frequência menor, economizando recursos na hora da impressão. Como os dados brutos ja foram convertidos eles estão prontos para serem desenhados na animação.

A animação é feita atravez da biblioteca matplotlib. Utilizamos a biblioteca para criar uma animação que chama a função que faz a atualização de uma linha. Esta função é responsável por escutar as mensagens do servidor, tocar o áudio no dispositivo de saída e atualizar os dados que serão desenhados pela animação.

Instalação

Para que o trabalho rode sem problemas, devem ser instaladas as seguintes dependências:

Uso dos Arquivos

Rodando o servidor:

Rodando o cliente:

Código Fonte

O trabalho foi implementado todo em Python 3, o código está dividido entre cliente e servidor.

Logs de execução

Os seguintes logs de execução exemplificam o funcionamento correto do sistema, os resultados foram obtidos no Laboratório 12 do Departamento de Informática.