Fundação Universidade Federal do Rio Grande Engenharia de Computação Streaming de Vídeo e Áudio via Multicast: A FURG TV na Internet Rafael Vieira Coelho Rio Grande, 17 de Janeiro de 2008 Fundação Universidade Federal do Rio Grande Engenharia de Computação Streaming de Vídeo e Áudio via Multicast: A FURG TV na Internet Rafael Vieira Coelho Trabalho de Conclusão de Curso de Graduação em Engenharia de Computação submetido à avaliação, como requisito parcial à obtenção do Título de Engenheiro de Computação. Orientador (a): Prof. Dr. Nelson Duarte Filho Rio Grande, 17 de Janeiro de 2008 2 Este trabalho foi analisado e julgado adequado para a obtenção do título de Engenheiro de Computação e aprovado em sua forma final pelo orientador. ________________________________ Prof. Dr. Nelson Lopes Duarte Filho ________________________________ Prof. Dra. Silvia Silva da Costa Botelho ________________________________ Prof. MSc. Ivete Martins Pinto Banca Examinadora: Prof. Dr. Nelson Duarte Filho Departamento de Matemática – FURG (Orientador) Prof. Dra. Silvia Silva da Costa Botelho Departamento de Física – FURG Prof. MSc. Ivete Martins Pinto Departamento de Matemática – FURG 3 DEDICATÓRIA Dedico este trabalho a todos os que acreditaram em mim e me apoiaram do início ao fim. Principalmente a minha querida família que sempre esteve do meu lado nos momentos bons e ruins. Um agradecimento especial em memória a meu pai Carlos Henrique que sempre foi um exemplo de sabedoria e tranqüilidade. Serei feliz se um dia conseguir ser a metade do homem que ele foi. Minha mãe Maria da Graça, pela dedicação extrema quanto aos filhos. Possibilitando assim todo o meu desenvolvimento como profissional e como pessoa. Minha irmã Raquel, pela ajuda que mesmo a distância conseguiu transmitir. Sempre dedicada aos estudos tornou-se para mim um exemplo a ser seguido. Minha namorada Vanessa, por sempre estar do meu lado me trazendo paz e boa companhia no desenrolar dos trabalhos que culminaram com esta monografia. Graças a todos vocês, consegui ultrapassar mais uma barreira em minha vida. E é por causa de vocês que consigo sempre continuar neste caminho tão bonito que é a vida. 4 AGRADECIMENTOS Agradeço ao meu orientador Prof. Dr. Nelson Lopes Duarte Filho por ter me ajudado sempre que pôde, mesmo com o pouco tempo que tem devido aos vários projetos que desenvolve. Também devo muito aos meus colegas de trabalho do NCC (Núcleo de Computação Científica) que sempre procuraram manter um bom ambiente de trabalho e de pesquisa científica. 5 SUMÁRIO Lista de Figuras..................................................................................................................................8 Lista de Tabelas................................................................................................................................10 Lista de Abreviaturas.......................................................................................................................11 1 INTRODUÇÃO.............................................................................................................................15 2 INFRAESTRUTURA DE COMUNICAÇÃO............................................................................20 2.1 Endereçamento Multicast.............................................................................................................22 2.2 MBONE........................................................................................................................................23 2.4 UDP..............................................................................................................................................25 3 SREAMING...................................................................................................................................28 3.1 Codecs de Vídeo...........................................................................................................................28 3.2 Codecs de Áudio...........................................................................................................................30 3.3 Métricas de QoS...........................................................................................................................33 4 TRANSMISSÃO EM TEMPO REAL.........................................................................................34 4.1 RTP...............................................................................................................................................34 4.2 RTCP............................................................................................................................................41 5 ARQUITETURA...........................................................................................................................52 5.1 Módulo de Configuração..............................................................................................................59 5.2 Módulo Transmissor.....................................................................................................................61 5.3 Módulo Receptor..........................................................................................................................65 6 JAVA MEDIA FRAMEWORK...................................................................................................67 6.1 Estrutura de Classes......................................................................................................................67 6.2 Funcionamento da API JMF.........................................................................................................71 6 7 TESTES E RESULTADOS..........................................................................................................76 8 CONCLUSÃO................................................................................................................................79 9 REFERÊNCIAS BIBLIOGRÁFICAS........................................................................................81 7 LISTA DE FIGURAS Figura 1: IGMP...................................................................................................................................17 Figura 2: Transmissão Multimídia.....................................................................................................19 Figura 3: Multicast e Unicast.............................................................................................................21 Figura 4: Endereço Multicast.............................................................................................................22 Figura 5: Multicast Nível 3 e Nível 2.................................................................................................23 Figura 6: Multicast MBONE..............................................................................................................24 Figura 7: Datagrama UDP..................................................................................................................25 Figura 8: Datagrama IP......................................................................................................................26 Figura 9: Representação gráfica de um sinal sonoro..........................................................................30 Figura 10: Jitter...................................................................................................................................33 Figura 11: Skew..................................................................................................................................33 Figura 12: Modelo ISO OSI...............................................................................................................34 Figura 13: Pilha de Protocolos RTP...................................................................................................35 Figura 14: Cabeçalho RTP.................................................................................................................36 Figura 15: Cabeçalho Fixo RTP.........................................................................................................36 Figura 16: Cabeçalho RTCP...............................................................................................................41 Figura 17: Pacote RTCP Composto...................................................................................................42 Figura 18: Pacote Receiver Report.....................................................................................................43 Figura 19: Pacote Sender Report........................................................................................................45 Figura 20: Item SDES........................................................................................................................46 Figura 21: Pacote SDES.....................................................................................................................47 Figura 22: Pacote BYE.......................................................................................................................47 Figura 23: Pacote APP........................................................................................................................48 Figura 24: Funcionamento do Servidor de Streaming........................................................................53 Figura 25: Localização do Socket......................................................................................................54 Figura 26: Fragmentação de Pacotes RTP..........................................................................................55 Figura 27: Funcionamento da Aplicação Cliente...............................................................................56 Figura 28: Arquitetura Proposta.........................................................................................................57 Figura 29: Aplicação de Configuração...............................................................................................60 Figura 30: Transmissor FURGTV......................................................................................................61 Figura 31: Selecionando o arquivo através do javax.swing.JfileChooser..........................................62 8 Figura 32: Janela de Temporização....................................................................................................62 Figura 33: Classe Transmissor...........................................................................................................63 Figura 34: Conexão via socket...........................................................................................................64 Figura 35: Janela de Status de Transmissão.......................................................................................65 Figura 36: Aba Sobre..........................................................................................................................65 Figura 37: Abstração da API JMF......................................................................................................71 Figura 38: Transmissão JMF..............................................................................................................73 Figura 39: Recepção JMF...................................................................................................................74 Figura 40: Arquitetura JMF................................................................................................................75 Figura 41: Modelo de Eventos JMF...................................................................................................68 Figura 42: Diagrama de Classes do Modelo de Eventos JMF............................................................68 Figura 43: Tipos de DataSource.........................................................................................................70 Figura 44: Player FURGTV...............................................................................................................76 Figura 45: Tamanho dos Pacotes........................................................................................................77 Figura 46: Pacotes Recebidos pelo Primeiro Receptor......................................................................78 Figura 47: Pacotes Recebidos pelo Terceiro Receptor.......................................................................78 9 LISTA DE TABELAS Tabela 1: Características dos fluxos multimídia típicos.....................................................................28 Tabela 2: Tipos de resolução de vídeo...............................................................................................29 Tabela 3: Padrões de Videoconferência.............................................................................................29 Tabela 4: Padrões do ITU...................................................................................................................30 Tabela 5: Valores de MOS.................................................................................................................31 Tabela 6: Codecs de áudio e valores de MOS....................................................................................31 Tabela 7: RFCs do Perfil RTP............................................................................................................38 Tabela 8: Formatos de vídeo suportados pelo JMF............................................................................68 Tabela 9: Formatos de áudio suportados pelo JMF............................................................................69 10 LISTA DE ABREVIATURAS ASM Any Source Multicast API Application Programming Interface APP Application-defined packet name CBR Constant Bit Rate Codec enCOder – DECoder DHCP Dynamic Host configuration Protocol DNS Domain Name System DVMRP Distance Vector Multicast Routing Protocol HTTP Hiper Text Transfer Protocol IANA Internet Assigned Numbers Authority IETF Internet Engenering Task Force IGMP Internet Group Management Protocol IP Internet Protocol ISSO Hypertext Transfer Protocol ITU União Internacional de Telecomunicações MBONE Multicast Backbone MCU Unidades de Controle Multiponto MC Controlador Multiponto MOS Mean Opinion Score MOSPF Multicast extensions to Open Shortest Path First MP Processador de Multiponto MTU Maximum Transmission Unit MSDP Multicast Source Discovery Protocol NAT Network Address Translation NTP Network Time Protocol OSI Open Systems Interconnection PCM Pulse Code Modulation PIM-SM Protocol Independent Multicast – Sparse Mode QoS Quality of Service RAS Registration, Admission and Status RP Rendevous Point RTCP Real-Time Transport Control Protocol 11 RTP Real-Time Transport Protocol RTT Round-Trip Time SDES Source Description SDP Session Description Protocol SNMP Simple Network Management Protocol SIP Session Initiation Protocol SSM Source Specific Multicast TCP Transmission Control Protocol TTL Time To Live UDP User Datagram Protocol URL Universal Resource Locator VOD Video-On-Demand Wi-Fi Wireless Fidelity 12 RESUMO Um canal de Televisão pode ser disponibilizado através da Internet com capacidades plenas. Para isto, a capacidade de streaming via Multicast deve ser implementada. A comunicação é feita de um transmissor para múltiplos receptores. Como o endereçamento Multicast permite enviar pacotes IP para um determinado grupo de usuários, estes são cadastrados previamente no grupo. Para entrar, sair e se manter em grupos Multicast, as estações utilizam o protocolo IGMP. A diferença do Multicast para unicast e broadcast está no roteamento dos pacotes que são enviados do transmissor para os receptores. Ele utiliza o maior número possível de rotas coincidentes, economizando assim a capacidade dos canais de comunicação. Foram implementados dois módulos em Java: servidor e cliente. Primeiramente, o aplicativo servidor abre o arquivo de vídeo e áudio que começa a ser transmitido via multicast pela rede. Após cria-se um loop que espera conexões via socket. Quando alguma requisição é recebida, dispara-se uma thread para adicionar o cliente em questão no grupo e controlar a permanência dele através de sessões. O cliente é um Applet que roda embutido no servidor Tomcat. Ele será disponibilizado através de um link na página da FURG TV (www.furgtv.furg.br). Desta maneira, será disponibilizado o canal de televisão da Fundação Universidade Federal do Rio grande, via streaming, na Internet. 13 ABSTRACT A television channel can be set over the Internet with full capacity. For this, the capacity for streaming with Multicast must be implemented. The communication is done from a transmitter to multiple receivers. As the Multicast Protocols allows sending IP packets to a specific group of users, they are registered in advance in a group. To enter, leave and remain in Multicast groups, stations use the protocol IGMP. The difference of Multicast for unicast and broadcast is in the routing of packages that are sent to the receivers from the transmiter. Multicast Protocols use the possible greater number of coincident parcial routes. Two modules were implemented in java: server and client. Initially, the server application open the file and begin to transmit video and audio multicast via the network. Then it creates a loop waiting connections via socket. When a request is received, it fired a thread to add the customer in question in the group and control its permanence in sessions. The client is an Applet that runs embedded in a Tomcat server. It can be downloaded from a link on the page of FURG TV (www.furgtv.furg.br). Thus, the channel of the Fundação Universidade Federal do Rio Grande has been available with the capacities of streaming multimidia data over the Internet, through multicast. 14 1 INTRODUÇÃO As comunicações multimídia são definidas como todo tipo de comunicação na qual existe transmissão de múltiplas mídias sobre uma rede de comunicação. Em geral, as mídias são de áudio (voz ou música) e vídeo. E, normalmente, a rede de comunicação em questão é a Internet. Neste contexto podem ser implementados muitos serviços como: ensino a distância, telemedicina, conversa remota, etc. Um dos serviços mais populares atualmente é o de Web TV, no qual os telespectadores podem assistir ao seu canal favorito em tempo-real no momento que desejar através da Internet via browser. Os aplicativos multimídia enviam e recebem fluxos contínuos de dados em tempo real. Por esta razão, o escalonamento e a alocação de recursos (Ex: largura de banda) devem ser precisas para o atendimento das especificações necessárias a um funcionamento razoável das aplicações. Também é necessária a utilização de buffers para o armazenamento dos elementos recebidos via rede. Por isso, deve haver uma maior atenção em relação à QoS oferecida às aplicações. Caso existam outros aplicativos executando e consumindo recursos, o que é bastante provável, a largura de banda e a quantidade de memória disponível não devem restringir a qualidade da transmissão de vídeo. Esta é outra razão para haver um cuidado maior com QoS. Mesmo assim, as aplicações atuais são baseadas na regra do melhor esforço e em poucas soluções quanto à qualidade de serviço. Típicas aplicações multimídia, segundo [21], são: Multimídia baseada na web: Os dados de áudio e vídeo são publicados na Internet. Depende da largura de banda disponível, da latência variável encontrada nas redes atuais de comunicação e da falta de capacidade dos sistemas operacionais em realizar o escalonamento em tempo real. Vídeo sob demanda: Este tipo de aplicação tem resultados eficientes quando utiliza um servidor de vídeo dedicado. Ele trata requisições para assistir determinado arquivo multimídia em um determinado horário e data. Telefone de rede e conferência por áudio: Exige pouca largura de banda. Trata-se de comunicação bastante interativa e com necessidade de baixos RTTs. Afora essas, a transmissão de conteúdo multimídia ao vivo está crescendo cada vez mais devido à popularização dos links de banda larga. Esse tipo de transmissão está se tornando comum aos usuários na Internet [1]. Com a televisão na Internet, o usuário solicita apenas os programas de seu interesse e não precisa ser escravo da programação pré-definida pelas estações corporativas. A 15 maioria das televisões disponibilizados na Internet, hoje em dia, consiste em um decodificador de sinal capturando sinal de TV e retransmitindo-o para a web através de um servidor de streaming. O modelo utilizado atualmente pelas TVs abertas sofrerá uma mudança bastante relevante, devido às próprias limitações. A forma de transição provavelmente será consolidada como digital, substituindo a atual que é analógica. Isto se deve a alguns fatores como melhor definição e maior número de canais disponibilizados. Onde se captava um único programa por canal, poderão existir vários (sistema de múltiplos programas). Existem ainda outras vantagens que não serão abordadas neste trabalho. Como exemplos podem-se citar o T-commerce, o comércio eletrônico pela TV, e o T-Banking, serviços bancários também pelo aparelho de TV. Isto sem falar na TV interativa que é uma das áreas de maior atratividade e de progresso rápido neste contexto. Uma forma de disponibilizar um canal de TV através da Internet com capacidades plenas é através da utilização de dois módulos, em Java por exemplo, através dos quais é feito o envio de dados de um transmissor para múltiplos receptores. Para economizar recursos de comunicação pode-se utilizar endereçamento Multicast, o qual permite o envio de pacotes IP para um determinado grupo de usuários que previamente se cadastraram neste (identificado por um endereço IP classe D, ou seja, entre 224.0.0.0 até 239.255.255.255). Para entrar, sair e se manter em grupos Multicast, as estações devem utilizar o protocolo IGMP [16]. A diferença entre o Multicast e o unicast reside no modo como é feito o roteamento dos pacotes. Enquanto no unicast a comunicação é feita ponto a ponto, no multicast alguns protocolos de roteamento são utilizados para fazer isso, como, por exemplo, o DVMRP, o MOSPF e o PIM. Eles usados para troca de informações entre os roteadores e definição de quais rotas os pacotes deverão seguir e a comunicação é feita de um para muitos. É necessária a manutenção dos grupos multicast. Para isso, é utilizado o protocolo IGMP em hosts IPv4. Esse tipo de gerenciamento é feito através da notificação aos roteadores vizinhos quando um host entra ou sai de algum grupo. Já em hosts IPv6, o IGMP é implementado através do ICMPv6. Existem mensagens características deste protocolo como, por exemplo, a mensagem IGMP Host Membership Query que é enviada por roteadores, quando querem descobrir quais hosts estão em seu grupo, para o endereço 224.0.0.1 com TTL igual a 1. Outra mensagem característica é a Host Membership Report. Esta é enviada pelos hosts, que participam de algum grupo multicast, para os roteadores vizinhos. Mensagens periódicas IGMP devem ser enviadas para que seja mantida a consistência dos grupos multicast. 16 Figura 1: IGMP O diagrama na Figura 1 representa os estados dos membros durante uma sessão multicast do protocolo IGMP, assim como os eventos que causam as transições entre os estados. Os estados possíveis dos membros estão representados pelos círculos cinzas com bordas pretas. A começar pelo estado Não-membro, esse é o estado inicial para todos os grupos em todas as interfaces de rede. Um membro também pode pertencer a um grupo numa interface e ter um temporizador de report executando (membro com temporizador). Além destes, existe o estado sem temporizador. Os eventos que causam transições de estado, e podem ocorrer em qualquer das interfaces de rede nele existentes são: Join Group: ocorre quando o host quer participar de um grupo. Leave Group: ocorre quando um host quer abandonar um grupo. Query Received: ocorre quando um host recebe uma mensagem Host Membership Query. Report Receive: ocorre quando um host recebe uma mensagem Host Membership Report. Timer Expired: ocorre quando o temporizador de atraso para o grupo na interface expira. Para as redes que não podem usufruir o endereçamento Multicast, existe o backbone Multicast da Internet (MBONE) [2] que será melhor explicado posteriormente. Os pacotes somente são encaminhados para aqueles caminhos que possuem clientes cadastrados em algum grupo Multicast, evitando tráfego desnecessário. Para limitar o alcance geográfico do Multicast, o campo TTL é utilizado, através de uma configuração de threshold. Para roteadores que não suportam Multicast, os pacotes são enviados pelos túneis Multicast que encapsulam os pacotes em túneis IP unicast. 17 Um dos grandes desafios atuais da Internet é o acesso às transmissões multimídia, mesmo na presença de receptores localizados em redes heterogêneas, com diferentes larguras de banda, velocidade de enlace, condições de acesso e capacidade de processamento. Para resolver esse problema deve ser implementado algum tipo de controle de banda para disponibilizar a transmissão igualitária do vídeo entre os receptores. Existem dois tipos de abordagem de controle de congestionamento: SSM [3] e ALM [4]. A abordagem SSM deve ser escolhida quando apenas uma fonte pode enviar dados Multicast. Já na abordagem ALM, o Multicast é implementado na camada de aplicação para que redes com diferentes topologias possam compartilhar da mesma transmissão com a mesma qualidade de imagem, o que normalmente gera problemas na distribuição de pacotes. Além disso, os protocolos ALM implementam as facilidades de Multicast nos end-hosts ao invés de nos roteadores da rede. Os dados são replicados nos end-hosts e não dentro da rede como é feito com IP Multicast. O grande problema desta abordagem é a utilização, normalmente, inadequada da banda. Existem três protocolos ALM bastante conhecidos: Narada [5], Yoid [6] e NICE [7]. No trabalho aqui descrito, foram construídos dois módulos: um cliente e um servidor de streaming. O sinal multimídia é codificado e transmitido pelo servidor para um grupo multicast de clientes. O sinal multimídia tanto pode ser obtido por uma filmadora ou quanto pode ser lido em um arquivo, sendo que o transmissor deve encapsular os pacotes utilizando protocolo RTP [8]. A transmissão via Multicast compõe uma solução alternativa ao excesso de tráfego redundante de pacotes que ocorre nas transmissões unicast e broadcast. Multicasting é, pois, um método ou técnica de transmissão de um pacote de dados para múltiplos destinos ao mesmo tempo, com otimização dos recursos de comunicação. Durante este tipo de transmissão, o servidor envia os pacotes de uma vez, dependendo dos receptores a captura da transmissão e, a partir disto, sua reprodução. Por exemplo, no caso de um filme a ser enviado por um servidor remoto para vários clientes, Multicast é a solução ideal pois diminui bastante o tráfego já que não envia pacotes para cada um dos participantes e sim para todo o grupo de uma só vez. Além disso, é importante ressaltar que também existe o padrão H.323 que provê uma base tecnológica para transmissão de dados, áudio e vídeo [9]. O funcionamento de uma transmissão multimídia é bastante simples de compreender: o sinal gerado é inicialmente digitalizado para então passar por um processo de compressão, o que diminui a quantidade de dados a ser transmitido, tornando viável o envio dos dados pela rede. Esta insere alguns atrasos no sistema, como pode ser visto na Figura 2. No receptor, os pacotes são reordenados, descomprimidos e reconvertidos ao estado original, normalmente com perdas inseridas no processo de compressão. 18 Figura 2: Transmissão Multimídia As mídias, normalmente, são grandes demais para serem enviados diretamente sem nenhum tipo de tratamento porque geram atrasos demasiados, congestionando a rede envolvida na comunicação. A solução para este problema é a codificação e decodificação das mídias. Para isto são utilizados algoritmos de codec que definem o formato no qual serão codificadas e comprimidas as mídias. Além de definir como serão decodificadas as informações no receptor. Tendo isto em consideração, a escolha do codec é crucial para que a transmissão seja eficiente. Tanto a qualidade de transmissão quanto a quantidade de banda consumida depende do tipo de codec escolhido. Além disso, é importante ressaltar que tanto na transmissão quanto na recepção devem ser utilizados o mesmo tipo de codec. Para que uma comunicação seja considerada em tempo-real, os telespectadores devem ter a percepção de que estão vendo as imagens no mesmo momento em que estão sendo filmadas ou lidas de um arquivo. Mas sempre existirá algum tipo de atraso ou perda em relação ao que está sendo produzido. Para minimizar este problema, deve ser adotada alguma alternativa que impeça o usuário de perceber esses atrasos ou perdas. Grandes atrasos prejudicam a característica de tempo real da comunicação e atrasos variáveis prejudicam a continuidade da reprodução da mídia. Muitas vezes, as comunicações multimídia em tempo real são consideradas CBR, o que indica que devem manter uma taxa de bit constante durante a transmissão das mídias. Esta característica deve ser mantida, mas a manutenção não pode exigir recursos em demasia da rede, pois conseqüentemente irá prejudicar a transmissão. O funcionamento da nossa arquitetura é bastante simples: o aplicativo servidor abre o arquivo de vídeo e começa a transmitir via multicast pela rede. Após é criado um loop que espera conexões via socket. Quando alguma requisição é recebida, dispara-se uma thread para adicionar o cliente em questão no grupo e fazer o controle da permanência nas sessões. O cliente é um Applet que roda embutido no servidor Tomcat. Ele será disponibilizado através de um link na página da FURG TV (www.furgtv.furg.br). Desta maneira, será disponibilizado o canal de televisão da Universidade Fundação Universidade Federal do Rio grande via streaming na Internet. 19 2 INFRAESTRUTURA DE COMUNICAÇÃO Existem três tipos de endereçamento que podem ser adotados no protocolo IPv4 para encaminhar pacotes: unicast, multicast e broadcast. No tipo unicast, a comunicação dá-se de um para um, ou seja, de uma entidade origem para outra destino. No multicast, é um para vários. Já no broadcast é um para todos. O endereçamento multicast permite enviar pacotes IP para um determinado grupo de interfaces de rede que estejam previamente cadastradas no grupo. Para entrar, sair e se manter em grupos multicast, as estações devem utilizar o protocolo IGMP, que será mostrado em maiores detalhes adiante. Em termos de qualidade de serviço, uma transmissão multicast é tratada da mesma forma que unicast, ou seja, possui as mesmas características de “melhor esforço” do IP unicast, sofrendo as mesmas políticas de controle de acesso e confirmação de tráfego. Como uma transmissão multicast possui o mesmo cabeçalho IP do unicast, os métodos de garantia de qualidade de serviço são os mesmos e ambos podem usar diffserv, RSVP [17], MPLS, e assim por diante. No roteamento de pacotes multicast, o roteador deve saber em quais portas existem usuários cadastrados em grupos. Com isso ele replica os pacotes por todos os caminhos por onde existirem receptores. Os protocolos de roteamento multicast são os seguintes: DVMRP, o MOSPF e o PIM. Através destes protocolos, os roteadores conversam entre si e descobrem quais rotas devem ser usadas para encaminhar os pacotes. O grande ponto a favor do multicast é que ele permite uma distribuição simultânea para um grande número de clientes, sem exigir muito dos recursos do servidor e sem gerar muito tráfego na rede. Assim, esta tecnologia facilita a existência de uma nova geração de aplicações “um para muitos” em rede, como tráfego de vídeo, áudio, trabalho colaborativo, transmissão de arquivos simultaneamente para muitos usuários, e assim por diante. As novas aplicações podem ou não exigir confiabilidade, entretanto, para transmissões confiáveis existe uma complexidade maior para controlar fluxo e o feedback dos receptores. Em resumo: Tráfego multicast: o transmissor gera um fluxo de pacotes IP multicast, que distribui para todas suas portas. Os clientes cadastrados no grupo multicast pegam a informação e a processam. O roteador, por sua vez, verifica se tem alguma de suas portas com clientes cadastrados no grupo multicast. Caso tenha, envia um fluxo multicast para essa porta. Tráfego unicast: para cada cliente, o transmissor deve gerar um fluxo de pacotes IP unicast. 20 Tráfego broadcast: a rede é sobrecarregada pois a mesma mensagem é enviada pelo transmissor para todos os receptores ao mesmo tempo, inundando assim a rede com pacotes replicados. Figura 3: Multicast e Unicast A transmissão de pacotes por multicast compõe uma solução alternativa ao excesso de tráfego redundante de pacotes. Multicasting é um método ou técnica de transmissão de um pacote de dados para múltiplos destinos simultaneamente. Durante transmissão desse tipo, o servidor envia os pacotes e depende dos receptores a captura da transmissão e, conseqüente, reprodução. O endereçamento multicast é caracterizado por IPs classe D. Ou seja, são reservados para comunicação multicast os IPs de 224.0.2.0 até 238.255.255.255. Os endereços reservados para protocolo de roteamento, descobrimento de topologia, etc. estão na faixa de 224.0.0.0 até 224.0.0.255. Já em aplicações desenvolvidas para criar um novo tipo de protocolo deve-se utilizar a faixa 224.0.1.0 até 224.0.1.255. Os endereços IPs restantes da classe D são reservados para fins administrativos (239.0.0.0 até 239.255.255.255), segundo [22]. No caso de uma aplicação multimídia, o transmissor gera um fluxo de pacotes IP multicast, que distribui para todas suas portas. Os clientes cadastrados no grupo multicast recebem a informação e esta é apresentada na tela. O roteador verifica se tem alguma de suas portas com clientes cadastrados no grupo multicast. Em caso afirmativo, ele envia um fluxo multicast para essa porta. No nosso caso: um canal de televisão sendo transmitido para vários clientes por um servidor de vídeo, multicast torna-se a solução ideal porque diminui significantemente o tráfego na rede. 21 2.1 Endereçamento Multicast Cada grupo multicast é reconhecido como um endereço multicast que tem características singulares que o diferenciam de um endereço IP comum. Isto se torna necessário porque pacotes enviados a eles não serão enviados a apenas um host, e sim, a vários destinos ao mesmo tempo. Desta forma, uma faixa de endereços são reservados para endereçamento multicast, como acima detalhado. Multicast no Nível de Rede (Nível 3) O multicast utiliza a classe D de endereçamento da Internet, ou seja, de 224.0.0.0 a 239.255.255.255. A diferença no cabeçalho IP de um pacote unicast e um multicast é apenas o endereço. A classe D permite até 28 bits, ou seja, 268 milhões de endereços diferentes. Existem alguns endereços multicast reservados pelo IANA [16], como o 224.0.0.0, que é a base e não deve ser usado. Já os endereços de 224.0.0.1 a 224.0.0.255 são reservados para protocolos de roteamento, como, por exemplo, “todos sistemas na subrede” (224.0.0.1), “todos roteadores nesta subrede” (224.0.0.2), “todos roteadores DVMRP” (224.0.0.4), “todos roteadores MOSPF” (224.0.0.6). Outros são reservados para determinadas aplicações, como o “IETF1-áudio” (224.0.1.11) e o IETF1-vídeo (224.0.1.12). O conjunto de endereços de 239.0.0.0 a 239.255.255.255 é reservado para uso local, podendo ser usado em intranets. Maiores detalhes podem ser encontrados na RFC 1700 [23]. Multicast no Nível de Enlace (Nível 2) Numa rede local, existe um conjunto de endereços MAC (Médium Access Control) especial destinado a multicast. Assim, no protocolo de nível 2, o host do cliente sabe se a mensagem é destinada a ele ou não. Esse conjunto de números foi reservado pelo IANA e compreende todos os endereços de 01-00-5E-00-00-00 a 01-00-5E-7F-FF-FF, conforme a Figura 5 (somente 23 bits, comparado aos 28 do endereço multicast). Figura 4: Endereço Multicast 22 Existe um processo de mapeamento entre o IP multicast e o Ethernet multicast, que é simplesmente a substituição dos 23 bits menos significativos do Ethernet pelos 23 bits menos significativos do IP. Assim, quando uma estação cliente se cadastra em determinado grupo multicast, como, por exemplo, o 238.173.117.105 (EE.AD.75.69), automaticamente o driver da placa de rede passa a receber mensagens MAC que cheguem no endereço 01-00-5E-2D-75-69, como mostra a Figura 7. Já o transmissor, que criou o grupo 238.173.117.105, quando enviar a mensagem para a rede, vai encaminhá-la ao endereço 01-00-5E-2D-75-69 do nível 2, como explicado anteriormente. Figura 5: Multicast Nível 3 e Nível 2 Observa-se que o mapeamento mostrado na figura provoca a existência de 32 números multicast iguais para o mesmo número Ethernet. Isso acontece, pois os cinco bits mais significativos do endereço multicast são ignorados no mapeamento. Quando se faz o mapeamento para o nível 2, utiliza-se somente os 23 bits menos significativos. Ou seja, de (224 a 239).0.0.0 até (224 a 239).127.255.255. Isso faz com que, para 32 endereços, a placa de rede receba os grupos como se fossem o mesmo. A filtragem para verificar se a máquina que está cadastrada se dá no nível 3. A maioria dos switches Ethernet atuais quando recebem um número MAC multicast tratam como se fosse endereço broadcast. Ou seja, enviam para todas suas portas. Sendo assim, qualquer cliente cadastrado no grupo irá receber o pacote e o colocará no nível a cima, e qualquer cliente não cadastrado vai receber o mesmo pacote e ignorá-lo. Existem alguns tipos de switch que executam IGMP, e são úteis para minimizar o tráfego desnecessário em redes nível 2, especialmente em configurações compostas de muitos switches e poucos roteadores. 2.2 MBONE 23 O MBONE ou backbone multicast da Internet nada mais é do que um conjunto de roteadores interligados a fim de distribuir tráfego multicast. Ou seja, uma rede virtual de hosts conectados pela Internet para comunicação estritamente multicast. Seu tempo de vida tem seus dias contados, pois quando todos os roteadores da Internet suportarem protocolos de roteamento multicast, não será mais necessário o uso do MBONE. Desde de 1992, o MBONE encontra-se em funcionamento. Ele foi originado de um esforço realizado pela IETF para dar suporte de áudio e vídeo através de multicast para reuniões à distância. A título de curiosidade, certa vez, um show dos Rolling Stones foi transmitido pelo MBONE, como também partes do Festival de Cinema de Cannes [10]. Isto revela sua capacidade de utilização real. Atualmente, vários protocolos estão sendo desenvolvidos sobre o MBONE. Através dele, pacotes são enviados via unicast pela Internet. Caso os roteadores suportassem roteamento multicast, os túneis (linhas de comunicação bidirecionais entre hosts) seriam inúteis, pois o roteador faria o papel do mrouter. Além disso, a manutenção dos túneis é um processo bem trabalhoso, ficando bem mais fácil o uso no roteador. Entretanto, o roteador vai ser afetado no desempenho da entrega de pacotes porque vai ter mais uma tarefa por fazer. Os roteadores do MBONE são chamados de mrouters e suportam endereçamento multicast. Todo o funcionamento é baseado em tunneling, no qual os pacotes são enviados para as subredes MBONE por mrouters que não suportam IP Multicast. A estrutura do MBONE está representada na Figura 6. Figura 6: Multicast MBONE Os pacotes são encaminhados apenas para os caminhos que possuem clientes cadastrados em algum grupo multicast, evitando tráfego desnecessário. Para limitar o alcance geográfico do multicast, o campo TTL é utilizado com uma configuração de threshold. Os túneis multicast encapsulam os pacotes em túneis IP unicast através do protocolo IP-IP. A configuração dos túneis é feita através de um arquivo chamado “/etc/mrouted.conf”. 24 Um túnel necessita ser configurado em ambos mrouters a fim de que possa ser usado. O arquivo /etc/mrouted.conf deve ser configurado com os seguintes parâmetros: IPs dos túneis, Threshold (define o valor mínimo de TTL que um pacote multicast deve ter para que seja encaminhado por determinada interface ou túnel), Metric (define o custo associado a esse túnel) e Rate_limit (largura de banda, em Kbit/s, do fluxo multicast que passa pelo mrouter). Em geral, todos os roteadores multicast conectados a um determinado túnel devem usar a mesma métrica e threshold para este túnel. 2.3 UDP Uma aplicação que necessita da comunicação via Internet deve utilizar os protocolos de transporte como pontos de acesso. Para que esta comunicação seja implementável, cada ponto de acesso é representado por um endereço de transporte denominada porta. O conceito de porta também é utilizado para tornar possível a multiplexação de várias comunicações do nível de aplicação de forma independente e simultânea. Os protocolos de transporte denominam-se UDP e TCP. Na próxima seção será explicado como funciona o protocolo UDP e porque ele foi o escolhido para implementar a comunicação proposta neste trabalho. UDP é um protocolo de transporte no qual não existe a verificação do recebimento dos dados pelo destino, não possui serviço de reenvio e nenhum tipo de ordenamento de mensagens. Sendo assim, as mensagens recebidas são agrupadas da maneira como chegam, sem nenhuma verificação da ordem de chegada. Além disso, não existe nenhuma verificação de integridade das mesmas. Essas características podem parecer prejudiciais, mas são elas que fazem com que o protocolo UDP seja muito rápido. Por conseqüência, tornou-se um protocolo extremamente simples que oferece um serviço de pacotes sem conexão. O funcionamento é baseado em portas de comunicação. São definidas portas de origem e de destino e a comunicação é feita através do envio de datagramas UDP. O formato dos datagramas encontra-se na Figura 7. Figura 7: Datagrama UDP 25 No datagrama UDP, são definidas a porta de origem e a de destino (ambas com 15 bits). Além disso, existe um campo de tamanho (15 bits) que descreve quantos bytes o pacote terá, um campo de checksum (15 bits) que é opcional e faz uma soma verificadora para proporcionar a verificação de erros. Para que seja enviado, o datagrama UDP é encapsulado num pacote IP, como ilustrado na Figura 8. Figura 8: Datagrama IP Existem alguns protocolos do nível de aplicação que fazem uso do protocolo UDP para as suas implementações. Um exemplo é o protocolo DHCP que, entre outras coisas, é utilizado para distribuição automática de endereços IPs. Outro protocolo que também o utiliza é o DNS, adotado para traduzir nomes de hosts em endereços IPs. Além desses, o SNMP também utiliza o UDP. Através dele, por exemplo, é possível configurar dispositivos como switches ou roteadores. Possibilitando que enviem informações de status a outros elementos da rede. Tendo em vista o que foi explicado sobre o protocolo UDP, fica claro que ele é usado quando se quer implementar uma comunicação em tempo real porque não existe muita sobrecarga na rede e por causa da característica de poder mandar um pacote para vários destinos, o que o torna diferente do outro protocolo de transporte mais popular, o TCP (orientado a conexões). Sendo não orientado a conexão, pacotes de confirmação de recebimento, por exemplo, não precisam ser enviados. Ou seja, existem vários pacotes apenas de verificação que não são enviados quando se utiliza UDP, pois esta não é orientada a conexões. Portanto UDP é muito mais rápida que TCP. Mesmo sendo menos segura, UDP é muito mais indicada para transmissões multimídia porque o que se quer neste tipo de transmissão é velocidade. 26 3 STREAMING Há algum tempo atrás, executar um arquivo de vídeo, ou seja, assistir um pequeno clip na Internet significava um longo período de download. Então, levantou-se a possibilidade de reproduzir o vídeo desejado antes mesmo que todo o arquivo fosse gravado localmente. Assim, nasceu a tecnologia de Streaming, que é um misto de técnicas de compressão e armazenamento em memória temporária (buffering). Ela permite a transmissão de vídeo em tempo real através da Internet. Na tecnologia streaming, a imagem e som começam a ser reproduzidos antes mesmo que o arquivo que os contém seja copiado totalmente para o computador local. Isto reduz a espera inicial a poucos segundos, um tempo relativamente razoável para o contexto atual da Internet. Nas transmissões de streaming, a ordem de chegada dos pacotes contendo unidades de um arquivo é fundamental, pois a visualização ou execução do conteúdo do arquivo se inicia antes do término da transmissão, como foi mencionado. Por outro lado, alguns pacotes podem ser descartados, sendo que em algum nível razoável de descartes isso se torna praticamente imperceptível ao olho humano. Além disso, esta tecnologia permite ainda enviar o vídeo de uma forma que ele não pode ser copiado. Para rodar um vídeo clip em streaming, normalmente é necessária a presença de um plugin (software especial que trabalha em conjunto com o navegador da Web), o qual manipula o download e a descompressão do arquivo. Na compressão do vídeo, a imagem é particionada em uma seqüência de quadros denominados frames. Cada frame é quebrado em partes estáticas ou dinâmicas. Essas partes, também chamadas de objetos, possuem conteúdo parado ou móvel, respectivamente. Um software de compressão age sobre estes objetos, atualizando aqueles com conteúdo móvel e reciclando aqueles com conteúdo estático. Desta forma, se reduz o tamanho e o tempo de transmissão de um arquivo. O armazenamento em memória temporária ou bufferização trata da utilização de um espaço alocado em memória que visa manter um ritmo constante na reprodução das imagens. Caso a conexão fique demasiadamente lenta, o buffer fica encarregado de suprir os atrasos e lacunas na transmissão, esvaziando seu reservatório de frames. Um outro aspecto a ser destacado diz respeito aos diferentes tipos de fluxos multimídia. A Tabela 1 [21], exemplifica alguns desses tipos e as taxas de dados e de quadro por amostragem, por eles tipicamente utilizadas. 27 Tipo de Fluxo Taxa de Dados Tamanho Freqüência Vídeo de TV 120 Mbps Até 640x480 com 16 bits por pixel 24 frames/s Vídeo de TV MPEG-1 1,5 Mbps Variável 24 frames/s Vídeo HDTV 1000-3000 Mbps Até 1920x1080 com 24 bpp 24-60 frames/s Vídeo HDTV MPEG-2 10-30 Mbps Variável 24-60 frames/s Tabela 1: Características dos fluxos multimídia típicos 3.1 Codecs de Vídeo Para que um sinal de vídeo possa ser transmitido através de uma rede de comunicação digital torna-se necessária a sua adequada codificação/decodificação, o que é realizada através de codecs. Os codecs de vídeo são utilizados para codificar sinais de vídeo que tanto pode vir de uma câmera conectada ao computador como de um arquivo de vídeo pré-armazenado. Pela mesma lógica, o receptor decodifica o vídeo com o mesmo codec para então apresentá-lo. A apresentação do vídeo recebido pode ser feita tanto em um display apropriado quanto pode ser armazenado em um arquivo. Como um arquivo de vídeo normalmente é bastante grande, ele deve ser divido em pacotes para que possa ser transmitido sem demora. Um vídeo nada mais é do que uma seqüência de imagens que são exibidas rapidamente, causando a impressão de movimento. Tendo isto em mente, uma das medidas que devem ser escolhidas pelo codec é o número de imagens (quadros) por unidade de tempo que serão enviados pelo transmissor aos receptores. Esta medida normalmente é dada em Frames Per Second (fps). Na televisão convencional é utilizado 30fps. Numa comunicação em tempo real via Internet, esta medida para ser aceitável deve estar entre 15 e 30fps. Um codec pode ser considerado melhor que outro caso consiga enviar mais quadros por segundo, proporcionando assim uma melhor qualidade na apresentação. Afora, a resolução do vídeo adotada pelo codec também influi na banda de transmissão. Algumas resoluções especiais foram especificadas para multimídia em tempo real com vistas à manutenção da qualidade e da temporalidade natural do vídeo. Estes padrões estão descritos na Tabela 2. 28 Formato Significado Resolução SQCIF Sub-quarter Common Intermediate Format 128 x 96 pixels QCIF Quarter Common Intermediate Format 176 x 144 pixels CIF Common Intermediate Format 352 x 288 pixels 4CIF 4 x Common Intermediate Format 704 x 576 pixels 16CIF 16 x Common Intermediate Format 1048 x 1152 pixels Tabela 2: Tipos de resolução de vídeo O vídeo, depois de carregado pelo servidor, deve ser divido em pacotes que irão passar por um processo de compressão, o que diminui seu tamanho, tornando assim viável a transmissão pela rede. No receptor, os pacotes são reordenados, descomprimidos e reconvertidos ao estado original, normalmente com algumas perdas decorrentes do processo de compressão. Alguns padrões de vídeo possibilitam a interação entre soluções de diferentes fabricantes de forma transparente. As especificações de maior importância são apresentadas na Tabela 3, para diferentes tipos de redes de comunicação. Padrão Tecnologia Qualidade Oferecida H.320 Videoconferência sobre ISDN Comunicação coorporativa H.321 Videoconferência sobre ATM Alta qualidade H.323 Videoconferência sobre IP/Ethernet Qualidade “best effort” H.324 Videoconferência sobre POTS Baixa qualidade H.310 Videoconferência MPEG-2 sobre ATM Qualidade para broadcast Tabela 3: Padrões de Videoconferência O padrão H.323 é a especificação indicada pelo IEEE para transmissões de áudio e vídeo sobre a Internet. Permite também que produtos de multimídia e aplicações de fabricantes diferentes possam interoperar de forma eficiente e usuários possam se comunicar de forma adaptada à velocidade da rede. Ele é um padrão para controle de conferências de vídeo e voz sobre redes TCP/IP da série H do ITU-T, um subgrupo do ITU [12] que é responsável por padronizações. A título de curiosidade, alguns padrões do ITU, associados aos assuntos aqui tratados, são apresentados na Tabela 4. 29 Padrões Descrição H.100 a H.199 Sistemas de telefonia visual H.220 a H.229 Transmissão, multiplexação e sincronização H.230 a H.239 Características básicas dos sistemas multimídia H.240 a H.259 Controle de procedimentos de comunicação H.260 a H.279 Codecs de vídeo H.280 a H.299 Serviços de suporte as comunicações multimídia H.300 a H.399 Serviços audiovisuais H.450 a H.499 Serviços de suporte as comunicações multimídia Tabela 4: Padrões do ITU 3.2 Codecs de Áudio O som, para que possa ser transmitido, deve ser transformado em sinais elétricos que por sua vez devem ser digitalizados. O microfone transforma as ondas sonoras em tensões elétricas com amplitudes altas e baixas, correspondendo a sons de maior e menor intensidade. Da mesma maneira, e com variações de freqüência mais rápidas ou mais lentas, correspondendo a sons mais agudos e mais graves. A Figura 9 apresenta a representação gráfica de um sinal sonoro, depois de convertido em tensões elétricas através do microfone. Figura 9: Representação gráfica de um sinal sonoro Várias características podem ser implementadas através dos codecs de áudio. Um exemplo é a supressão de silêncio que faz com que não seja consumido quase nenhum recurso durante a ausência de som em uma comunicação. Apenas é transmitido o ruído no momento de silêncio. E isto acontece para que a outra pessoa na linha de comunicação não pense que foi abandonada pelo outro falante. 30 Outro fator crucial na escolha do codec é o valor do MOS, uma medida de qualidade que se quer implementar para a aplicação. Tal valor pode ser escolhido de modo subjetivo pelo usuário, dependendo da qualidade percebida pelo mesmo durante a comunicação. Para isto são associados valores entre zero e um à percepção subjetiva da qualidade do som, segundo a Tabela 5. Normalmente são utilizados codecs com MOS acima de 3,5. Valor MOS Qualidade Exemplo 0a1 Péssima Conversa impraticável 1a2 Ruim Conversa com fala quebrada e atraso 2a3 Regular Conversa razoável 3a4 Boa Conversa telefônica de longa distância 4a5 Ótima Conversa telefônica local Tabela 5: Valores de MOS Valores de MOS associados a alguns tipos de codec de áudio são mostrados Tabela 6 [21]. Codec Valor MOS Taxa de transmissão G.729 3,7 8Kbps G.728 3,6 16Kbps G.711 4,1 64Kbps G.726 3,8 32Kbps G.723.1 3,9 6,3Kbps Tabela 6: Codecs de áudio e valores de MOS Por tratar-se de um codec de domínio público e larga utilização, destaca-se aqui o codec G.711. Ele foi o precursor nas comunicações de tempo real na Internet. Baseado no sistema telefônico convencional, ele é obrigatório na arquitetura H.323. É de fácil implementação e oferece uma boa qualidade de mídia. O codec G. 711 utiliza o algoritmo PCM para codificar áudio e representa cada sinal amostrado através de oito bits, gerando uma stream de áudio de 8Kbps. Por se tratar de um algoritmo leve, sua execução é bastante rápida. Sendo assim, possui uma ótima qualidade de transmissão em redes com pouca perda de pacotes, boa largura de banda e atrasos constantes e reduzidos. Apesar de estar implementado em quase todas as aplicações atuais de multimídia, ele é pouco utilizado devido ao atual cenário da Internet. 31 3.3 Métricas de QoS O crescimento das aplicações multimídia em rede tornou necessário controlar a qualidade de serviço ou QoS a que uma seqüência de pacotes de uma origem a um destino deve ser submetida. Redes que seguem as diretrizes de transmissão pela regra do melhor esforço, caso das redes IP, fazem com que os pacotes que as percorrem sejam transmitidos hop a hop na maior velocidade possível, independente das aplicações a que pertençam. Nelas, os pacotes devem chegar aos seus destinos o mais rápido possível, mesmo que para isso necessitem utilizar rotas alternativas devido ao congestionamento da rede. Devem-se, pois, em alguma camada superior dessas redes, introduzir mecanismos de controle capazes de atribuir prioridades aos fluxos gerados pelas diferentes aplicações que se encontrem em execução corrente. Considerando a característica isócrona dos pacotes tratados pela aplicação multimídia, foco desta monografia, e conseqüentemente as severas restrições a que as entidades que transportam esses pacotes devem ser submetidas, destacam-se a seguir, para que se possa melhor entender o funcionamento da proposta, os principais parâmetros para controle de qualidade dos fluxos de dados na Internet. Latência Latência é o tempo que um pacote leva da origem ao destino. Caso esse atraso seja muito grande, prejudica a troca de dados através da rede. Os principais responsáveis pela latência são o atraso de transmissão, de codificação e de empacotamento, que podem ser definidos da seguinte forma: Atraso de transmissão: tempo que leva para o pacote sair da placa de rede do computador origem e chegar na placa de rede do computador destino (atraso no meio físico, atrasos de processamento em roteadores e atraso devido ao tempo de espera nas filas de transmissão dos equipamentos intermediários). Atraso de codificação e decodificação: tempo de processamento na máquina origem e na máquina destino para codificação e decodificação de sinais. O atraso depende do padrão escolhido. Atraso de empacotamento e desempacotamento: depois de codificado, o dado deve ser empacotado através dos níveis na pilha de protocolos a fim de ser transmitido na rede. 32 Jitter Jitter é a variação estatística do retardo entre as chegadas dos pacotes. Deve ser inferior a 50ms para que as comunicações multimídia não fiquem comprometidam. Para o cálculo do jitter devem-se considerar o atraso do processamento dos codecs e o atraso na transmissão dos pacotes. O conceito de jitter e latência são ilustrados na Figura 10. Figura 10: Jitter A solução para o jitter é criar um buffer no destino com tamanho dependente do valor do mesmo, o que conseqüentemente reduz o atraso na transmissão. O buffer serve como uma reserva para manter a taxa de entrega constante no receptor. Skew Skew é um parâmetro utilizado para medir a diferença entre os tempos de chegada de diferentes mídias que deveriam estar sincronizadas. Em diversas aplicações existe dependência entre mídias, como no caso de uma TV disponibilizada em um browser via Internet, que se compõem de áudio e vídeo. Assim, numa transmissão de TV, o áudio deve estar sincronizado com o movimento dos lábios (ou levemente atrasado, visto que a luz viaja mais rápido que o som, e o ser humano percebe naturalmente o som levemente atrasado em relação à visão). Figura 11: Skew 33 4 TRANSMISSÃO EM TEMPO REAL Para transportar dados em tempo real, como é o caso da aplicação multimídia tratada no presente trabalho, são necessários protocolos que tratem aspectos de sincronismo e de temporização. A seguir abordam-se os protocolos RTP e RTCP, que são capazes de dar suporte a esses tipos de necessidades. 4.1 RTP O protocolo RTP (Real-time Transport Protocol) especifica um formato para transmissões em tempo real, característica necessária a aplicações de transminssão de TV sobre a Internet. Através desse protocolo podem ser detectadas perdas de pacotes UDP, por exemplo. E isso é realizado utilizando números de seqüência nos pacotes. O RTP é considerado um protocolo de middleware, pois se localiza entre a camada de aplicação e a camada de transporte, conforme ilustrado na Figura 12, onde foi utilizada a representação adotada pela arquitetura ISO OSI. Figura 12: Modelo ISO OSI Esse protocolo também dá suporte a sincronização intermídia e intramídia. O campo de timestamp do cabeçalho informa ao receptor o momento exato em que esse deve passar os dados ao usuário. Essa informação é usada pelo receptor para absorver o jitter da rede através de um buffer auxiliar. Esse campo também é usado em conjunto com o protocolo NTP a fim de sincronizar as diferentes mídias, permitindo ao receptor a adaptação ao skew. 34 O RTP utiliza o RTCP, adiante descrito, para monitorar a qualidade de serviço e repassar informações sobre os participantes de uma sessão em andamento. Em termos do modelo OSI [10], como foi dito anteriormente, o RTP se situa entre a camada de aplicação e a de transporte, coforme melhor mostrado na Figura 13. O IP pode ser tanto unicast como multicast, e o protocolo Ethernet utilizado na figura é apenas um exemplo. No caso aqui tratado, utiliza-se multicast como modo de endereçamento e os pacotes RTP são encapsulados em pacotes UDP, para transmissão via rede. Figura 13: Pilha de Protocolos RTP Uma sessão RTP é caracterizada por uma mídia por sessão apenas, podendo ser de áudio ou de vídeo. Também são utilizados um conjunto único de IP, porta e SSRC para cada participante. Mesmo havendo as sessões distintas, a sincronização entre áudio e vídeo pode ser atingida através do feedback de tempo proporcionado pelos pacotes RTCP, que serão explanados posteriormente. O pacote RTP é composto por um cabeçalho fixo, uma lista de fontes de contribuição opcional, um cabeçalho de extensão opcional e um campo de payload. Esta estrutura pode ser vista na Figura 14 e o cabeçalho fixo será detalhado na próxima subseção. A lista de contribuição ou CSRC (utilizado para mixers) contêm 32 bits, assim também como o campo de payload (representa o conteúdo do pacote) que tanto pode ser de áudio quanto de vídeo e é codificado por um codec (Ex: G.711 ou H.263). 35 Figura 14: Cabeçalho RTP O cabeçalho fixo e a lista opcional de fontes de contribuição são mostrados na Figura 15. Figura 15: Cabeçalho Fixo RTP Os primeiros doze bytes estão presentes em todos os pacotes RTP, enquanto que a lista dos identificadores CSRC está presente somente quando inserido por um multiplexador. Os campos têm o seguinte significado: Version (V): Identifica a versão do protocolo RTP. A versão 2 é a utilizada atualmente (2 bits); Padding (P): Indica a opção de preenchimento do pacote RTP. Se este bit for 1, o pacote contém um ou mais bytes (octetos) de preenchimento no final do pacote e é utilizado para criptografia. Já quando ele é setado para 0, não existe nenhum dado útil e os dados complementares devem ser ignorados (1 bit); Extension (X): Se este bit estiver setado para 1, indica que o cabeçalho terá uma extensão com o mesmo número de bytes (1 bit). Caso contrário, o campo de extensão deve ser desconsiderado; CSRC Counter(CC): Indica o número de identificadores CSRC que seguem o campo fixo do cabeçalho (4 bits). E é útil na união de várias fontes de áudio em um único fluxo de dados; Marker (M): Permite que eventos significativos, tal como limites de quadro, sejam marcados no fluxo de pacotes (1 bit); 36 Payload Type(PT): Identifica o formato da carga útil do pacote, de forma que possa ser interpretado pela aplicação, ou seja, o tipo de codec utilizado (7 bits); Número de seqüência: É um valor que é incrementado de 1 a cada pacote RTP transmitido e pode ser usado pelo receptor para detectar perda de pacotes, bem como para restaurar a seqüência correta do fluxo (16 bits). O primeiro valor de uma seqüência é criado aleatoriamente; Timestamp: Identifica o instante de amostragem do primeiro byte no payload, permitindo assim a sincronização e o cálculo do jitter. Por isso, é fundamental na remontagem de mídias recebidas. Se pacotes consecutivos possuírem o mesmo timestamp, isto significa que pertencem ao mesmo instante de vídeo (32 bits); SSRC: Identifica a origem de transmissão. Esse número é escolhido aleatoriamente, procurando fazer com que todas as fontes de sincronização tenham identificadores diferentes. Cada sessão deve ter um SSRC único. Sendo assim, existirá um SSRC para uma sessão de transmissão de vídeo e outro para uma sessão de transmissão de áudio (32 bits); CSRC: identifica as fontes que contribuíram para a carga de dados existente no pacote RTP e é opcional (32 bits). Identifica cada transmissor, durante uma múltipla transmissão. Vários participantes de uma sessão RTP podem contribuir para criar pacotes. Quando setado, este campo é adicionado à lista de participantes conhecidos. O cabeçalho de extensão é opcional e contém 32 bits. É composto pelo ID do perfil (16 bits), comprimento (16 bits) e informações extras de extensão. O perfil RTP estabelece mapeamentos entre os codecs e os valores do campo PT (Payload Type). Este mapeamento é feito com base em valores constantes de PT que indicam os codecs correspondentes. A Tabela 7 apresenta os codecs mais conhecidos e utilizados atualmente. 37 PT Formato Especificação 0 PCMU (áudio) RFC 1890 3 GSM (áudio) RFC 1890 8 PCMA (áudio) RFC 1890 14 MPA (áudio) RFC 2250 26 JPEG (vídeo) RFC 2435 31 H.261 (vídeo) RFC 2032 32 MPEG I e II (vídeo) RFC 2250 34 H.263 (vídeo) RFC 2190 Tabela 7: RFCs do Perfil RTP Mesmo sendo a divulgação do tipo de payload estática (também pode ser dinâmica), é necessário que seja feita a descrição da sessão para a aplicação, para que esta possa saber qual o tipo de payload utilizado. Um dos protocolos mais utilizados para descrição de sessão é o SDP ou Session Description Protocol. Através desse protocolo os participantes de uma sessão multimídia podem padronizar o codec utilizado na comunicação. Além disso, podem trocar informações entre si sobre suas capacidades de processamento de mídia. O SDP faz uso de uma codificação textual que é composta por campos de controle nos quais podem ser descritos o número da versão do protocolo, o identificador de início de comunicação, nome da sessão, descrições da mídia, tempo de ínício e fim de sessão e informações de controle. Para isso, seus dados são encapsulados em mensagens SIP. Mesmo que seja comum o uso de SDP em sessões RTP, seu uso não é especificado como padrão. O protocolo SIP é uma especificação do grupo de trabalho MMUSIC do IETF publicada em 1999. Mas a versão atual do protocolo, contida na RFC 3261, foi publicada em 2002. Esse protocolo também é baseado em troca de mensagens. Quando utilizado para sinalização sobre UDP, torna-se mais rápido e eficiente do que utilizando versões iniciais do H.323. As mensagens podem indicar um pedido de abertura de comunicação, uma confirmação de recebimento de mensagem, o cancelamento de pedidos, o registro de usuários e a ocorrência de eventos em geral. Já em aplicações que utilizam o padrão H.323, é utilizado o H.245 para descrição de sessões. Neste caso, o protocolo H.245 é utilizado para negociar e estabelecer os canais de comunicação do RTP. A escolha do formato do payload tem várias implicações intrínsecas. Definindo também a taxa do clock de mídia RTP e o formato de qualquer cabeçalho de payload. A maioria dos formatos de payload tem uma variação limitada de taxas de clock. E através da especificação do formato de payload são definidos quais as taxas válidas. Múltiplos formatos de payload podem ser usados por 38 sessão para que possam ser adotados vários formatos por sessão RTP. Dessa forma, o formato pode mudar no decorrer do tempo durante uma sessão. Mesmo tendo essa propriedade, o protocolo RTP especifica que se áudio e vídeo serão transmitidos por uma aplicação, devem ser criados duas sessões. Uma para transmissão de vídeo e outra para a transmissão de áudio, em diferentes portas e endereços. Esta separação é necessária para que a aplicação em questão possa requisitar diferentes QoS para os tipos de mídia. Além de permitir que o protocolo de controle RTCP possa funcionar corretamente. O campo Timestamp é utilizado para o escalonamento da visualização do vídeo. Ele é um campo de 32 bits contendo um inteiro sem sinal que, inicialmente escolhido aleatoriamente, é incrementado em função de uma taxa que depende da mídia. Isso é feito para que ataques ao stream RTP criptografados sejam dificultados. Também é possível utilizar um timestamp estendido de 64 bits. É importante ressaltar que os timestamps criados devem manter uma seqüência contínua de valores crescentes e não devem acontecer variações entre eles. Essa exigência é necessária para que servidores de streaming de mídia trabalhem corretamente. Algo que pode ocorrer é que podem existir timestamps repetidos quando são enviados múltiplos pacotes RTP. Mas nesse caso, cada pacote terá um número de seqüência diferente. Os formatos de payload de vídeo, normalmente, usam clock de 90kHz. Isto é feito para ter compatibilidade com vídeos MPEG e para incrementar timestamps para quadros de taxa de 24Hz, 25Hz, 29.97Hz e 30Hz. A qualidade de resolução não é implementada pelo protocolo RTP, e deve ser garantida pela aplicação. O número de seqüência é o que identifica cada pacote como único. É através dele que o receptor descobre se pacotes estão sendo perdidos ou entregues fora de ordem. Em uma comunicação típica de voz sobre IP, enviando áudio em pacotes a períodos de 20 milisegundos, o número de seqüência se sobrepõe a cada 20 minutos, aproximadamente. Isso significa que não deve ser apenas considerado como identificador o número de seqüência padrão de 16 bits. O recomendado é o uso de números de seqüência estendidos a um inteiro sem sinal de 32 bits ou maior. Sendo primeiros os 16 bits o número de seqüência do RTP, e os 16 bits restantes um contador do número de vezes que o número de seqüência foi sobreposto, o que pode ser representado pela seguinte expressão: Num_seq_estendido = num_seq + (65536 * contador_empacotamento) O contador de sobbreposição ou empacotamento deve ter um controle maior do que o incremento do número de seqüência. Este contador só é incrementado no momento que o número 39 atual de seqüência for menor que o número máximo de seqüência. O número de seqüência pode ser inválido e isto deve ser tratado também na implementação. Caso se decida não levar em conta o contador de sobreposição, o número de pacotes perdidos e dados estatísticos de perda não são verificados. Mas é importante o seu uso estendido, pois é bastante comum a ocorrência de perda de pacotes ou erros na reordenação dos pacotes durante o momento da sobreposição do número de seqüência. O número de seqüência sempre inicia com um valor randômico. E isto ocorre pela mesma razão que acontece com o timestamp. Os números de seqüência são reportados nos pacotes de relatório de recepção RTCP. Isso é feito para controle de erros de transmissão e criação de dados estatísticos de perda. Além disso, o número de seqüência deve sempre seguir uma ordem crescente, somando-se um, na medida que os pacotes são transmitidos. Jamais poderão existir saltos de valores tanto para um número maior quanto menor. Mesmo quando um vídeo é subdividido, o número de seqüência é mantido contínuo. Quando é detectado um intervalo entre os números de pacotes, o receptor percebe que houve perdas no envio dos pacotes por parte do transmissor. A partir disso, pode ser alterada a ordem dos pacotes através de uma atualização. Eventos relevantes de um fluxo de mídia são reportados pelo bit Marker ou M. No perfil RTP é definido o significado do evento e o tipo de cada mídia. Ele indica à aplicação que algo importante aconteceu naquele momento da transmissão. Durante uma transmissão de áudio, o bit M é setado para 1 quando um pacote é enviado após um período em silêncio. Isto serve como uma dica para a aplicação atualizar o ponto de reprodução já que um pequeno lapso no silêncio não é percebido pelo usuário. Entretanto, em um fluxo de vídeo, o bit M é setado para 1 quando o último pacote for transmitido. Para identificar os participantes de uma sessão, o campo SSRC ou fonte de sincronização é utilizado. Ele nada mais é do que um inteiro de 32 bits que é aleatoriamente escolhido localmente pelos participantes da sessão em questão no momento de associação a sessão. Como cada participante escolhe, dois de uma mesma sessão podem ter o mesmo valor. Este tipo de colisão é percebido no momento em que existe a troca de pacotes entre estes participantes. Quando isto ocorre, um dos participantes deve enviar uma mensagem BYE e selecionar um novo valor de SSRC. Sendo assim, o número de fonte de sincronização dos participantes é único em uma sessão RTP. Desta forma, quando é necessário algum tipo de playback, é feita a sincronização para agrupar pacotes pelo SSRC do participante. Caso um participante da sessão (por exemplo, o servidor de streaming) envia múltiplos fluxos aos participantes, cada fluxo deve ter um SSRC único. Isso é necessário para que seja feita a distinção entre cada fluxo na recepção dos pacotes. 40 Outra característica do protocolo RTP é a possibilidade do uso de cabeçalhos de extensão. Para isto deve ser setado para 1 o bit X do cabeçalho padrão. Os tamanhos dos cabeçalhos de extensão são variáveis. Mas o mínimo é de 32 bits, sendo os primeiros 16 bits contendo o tipo e os 16 bits restantes contendo o tamanho do mesmo. Normalmente, não são utilizados, mas foram criados para uso experimental para novos tipos de formato de payload e para otimização de alguns tipos de payload. O conteúdo do cabeçalho RTP pode ser tanto estático como dinâmico. No caso estático, ele é duplicado para toda a sessão que usa um determinado tipo de payload. Normalmente, tudo o que é considerado dinâmico em um cabeçalho RTP é configurado pelo SDP. O campo de payload ou de dados pode suportar vários frames por pacote. Por isso, o receptor pode checar o tamanho do pacote caso seja um número constante de frames por pacotes. Além disso, existem alguns formatos de payload que referem a quantidade de frames. Isso é muito importante para aplicação receptora distinguir qual o ponto de reprodução. Esse caso ocorre quando o número de frames pode ser variável durante a transmissão. 4.2 RTCP O protocolo RTCP (RTP Control Protocol) tem como objetivo fornecer feedback sobre a qualidade de serviço oferecida à distribuição de dados RTP. Isto é obtido através de transmissões periódicas de pacotes de controle a todos participantes da sessão RTP, utilizando o mesmo mecanismo de distribuição do RTP (no caso deste trabalho, multicast) e possuindo uma porta específica de controle na sessão. O pacote RTCP padrão pode ser visto na Figura 16. Figura 16: Cabeçalho RTCP O tamanho total padrão é de 32 bits. Os cinco principais campos são: Version (V): Identifica a versão do protocolo RTP. A versão 2 é a utilizada atualmente (2 bits). Não é prevista a criação de novas versões e as versões anteriores não são utilizadas. 41 Padding (P): Indica se o tamanho do pacote extrapolou ou não. Caso o bit esteja setado para 1, isto significa que um ou mais octetos foram adicionados no final do mesmo. Já quando ele é setado para 0, não existe nenhum dado útil e os dados complementares devem ser ignorados (1 bit). Item Count (IC): É utilizado para indicar a quantidade da lista de itens que alguns pacotes têm. Podem ser adicionados até 31 itens em pacotes RTCP. Quando o IC contem o valor zero, significa que a lista de itens está vazia. Aplicações que não necessitam deste campo podem utilizá-lo com outra função. Packet Type (PT): Identifica o tipo de informação que está sendo enviada pela rede. Existem cinco tipos padrões que são descritos na especificação RTP. Outras mais específicas podem ser definidas no futuro. Length: Este campo contém o comprimento do pacote que vem abaixo do cabeçalho padrão. São medidos em unidades, pois todos pacotes RTCP são múltiplos de 32. Caso o campo de Length esteja com o valor zero, o pacote RTCP apenas terá o cabeçalho padrão. O encapsulamento do pacote RTCP é similar ao RTP. Ele é embarcado em pacotes UDP que por sua vez são empacotados em pacotes IP. Podem ser enviados mais de um pacote RTCP por pacote IP. Isto é chamado de pacote RTCP composto e encontra-se ilustrado na Figura 17. O primeiro pacote RTCP é o que segue o cabeçalho do UDP, enquanto o segundo pacote RCTP está imediatamente após ao primeiro. Um conjunto como esse, com dois pacotes, chama-se pacote RTCP composto. Figura 17: Pacote RTCP Composto 42 Existem alguns tipos de ações que podem ser reportados através dos pacotes RTCP. Uma das mais primitivas utilizações do RTCP é para reporte de relatórios sobre qualidade de recepção. Isso é realizado através de pacotes RTCP Receiver Report (RR). Qualquer participante de uma sessão que esteja recebendo dados pode enviar esse tipo de pacote. O valor PT para esse tipo de relatório é 201 e seu formato pode ser visto em maiores detalhes na Figura 18. Figura 18: Pacote Receiver Report Como pode ser visto na figura, o pacote RR é composto por uma série de itens que descrevem os dados relacionados à qualidade transmissão. O campo RC apresenta o número de blocos reportados. Caso ele seja zero, nenhum item está anexado ao cabeçalho padrão. Mas quando o campo Length for igual a 1, além do cabeçalho padrão, o pacote RTCP em questão terá o item Reporter SSRC de 32 bits. Um pacote RTCP RR pode conter 31 blocos descrevendo a qualidade de recepção de uma única fonte de sincronização. Caso existam mais de trinta e um transmissores, os receptores devem enviar pacotes RR em um pacote RTCP composto. Cada bloco de relatório é composto por sete campos num total de vinte e quatro octetos. O campo Reportee SSRC indica quem é o participante do grupo que é o dono do bloco de relatório que está sendo enviado. Ou seja, quem gerou o pacote RTCP RR e está reportando informações para controle de qualidade e criação de estatísticas de transmissão e recepção. Algumas estatísticas são válidas apenas durante a existência da sessão. O Cumulative number of packets lost é um campo inteiro de 24 bits que representa o número de pacotes que deveriam ser entregues menos o número de pacotes perdidos. O número de pacotes esperados para recepção é calculado a partir do último número de seqüência inicial menos o número de seqüência inicial. O número de pacotes recebidos leva em conta os pacotes duplicados e atrasados. Por essa razão, o número cumulativo de pacotes perdidos pode ser negativo. E ele é calculado por sessão e não por intervalos. Outro campo calculado por sessão é o número estendido 43 de número de seqüência. Já o campo Loss Fraction representa o número de pacotes perdidos por intervalo de relatório, dividido pelo número de pacotes esperado. O valor da fração é sempre multiplicado por 256. Se o número de pacotes recebidos for maior que o número de pacotes esperados, o Loss Fraction é zerado. E é através desse valor que o transmissor pode ter uma noção se a quantidade de pacotes perdidos é constante ou variável. E se a perda é transiente ou em longo prazo. Com isso, é possível fazer a escolha certa sobre o tipo de formato que será usado para transmitir a mídia. O Interarrival jitter é uma estimativa estatística da variância do tempo de transição na rede, para pacotes de dados enviados pelo Reportee SSRC. Ele é medido em unidades de timestamp e expresso por um inteiro sem sinal de 32 bits. Mesmo sendo impossível calcular o exato tempo de transição, porque o receptor e transmissor não estão com seus relógios sincronizados, é calculado o tempo de transição estimado, através da diferença entre o timestamp do pacote RTP e o clock do receptor, no momento em que o pacote chegou. Para isto, é necessário manter um clock para cada fonte de transmissão. O campo LSR representa o timestamp do último relatório recebido pelo transmissor. Ele contém trinta e dois dos 64 bits de timestamp do protocolo NTP, incluído nos pacotes SR que serão explicados em maiores detalhes a seguir. Caso não tenha sido recebido nenhum pacote SR do transmissor, o LSR terá o valor zero. Já o campo DLSR indica o tempo de atraso entre o último pacote RS e tempo de envio do pacote de relatório de recepção. Ele é expresso em unidades de 1/65.536 segundos. Assim como o LSR, o DLSR é composto pelo valor zero se nenhum pacote RS tenha sido recebido. A qualidade de recepção não é importante apenas para o transmissor. Porque é através do receptor que o transmissor pode ter algum tipo de feedback sobre a qualidade de transmissão. Dessa forma, o transmissor pode adaptar os seus próprios parâmetros às atuais características da transmissão. E em geral elas são altamente dinâmicas, pois podem se modificar de várias formas. Apenas a entrada de um novo participante no grupo multicast pode modificar a velocidade de transmissão de todos, já que fazem parte do mesmo grupo. Além disso, através desse tipo de feedback, o transmissor pode verificar se o problema reportado por um receptor também é um problema que assola os outros participantes ou se é um problema isolado. O RTT é calculado pelo transmissor através dos campos LSR e DLSR computados e enviados em pacotes RR pelo receptor. Podem ser calculados RTTs entre o transmissor e cada um dos participantes. Além dos pacotes RR enviados pelos receptores através do protocolo RTCP, os transmissores também têm um equivalente. Eles enviam pacotes RS através do protocolo de controle RTP. O formato e campos deste tipo de pacote podem ser vistos na Figura 19. 44 Figura 19: Pacote Sender Report Os pacotes RS são relatórios enviados aos receptores em um esforço conjunto para melhorar a qualidade de transmissão e recepção. Um dos objetivos mais usuais deste tipo de mensagem é para sincronização dos lábios entre áudio e vídeo na reprodução multimídia. Como pode ser observado na Figura 19, o código de tipo de payload é 200 para este tipo de pacote. O campo NTP timestamp é composto por um inteiro sem sinal de 64 bits e para que medidas de tempo possam ser transformadas no tempo NTP, é preciso adicionar 2.208.988.800 segundos. Já o RTP timestamp é expresso em unidades de clock de mídia RTP, mas representa o mesmo instante que o NTP timestamp expressa no pacote. O contador de pacote tem o valor da quantidade de pacotes gerados pela fonte de transmissão desde o início da sessão. Enquanto isso, o contador de octetos, como o próprio nome diz, tem o valor total de octetos contidos no payload destes pacotes, sem incluir os cabeçalhos padrão. Caso ocorra uma colisão e um novo valor SSRC tenha que ser escolhido, os contadores, como explicado anteriormente, são zerados. Através dos valores fornecidos pelo transmissor pelos pacotes RR, é possível calcular uma média das taxas de pacotes e de payload enviadas em um intervalo de tempo. A razão para o cálculo dessa média é o tamanho do payload. Assim, o throughput disponível para o receptor pode ser calculado pela multiplicação da média do tamanho de payload pelo número de pacotes recebidos pelo receptor em questão. Além disso, o protocolo RTCP também é usado na identificação dos usuários que participam do grupo multicast. O pacote SDES pode conter informações que vão desde o número do telefone até o e-mail do participante do grupo. Normalmente, contêm dados que são informados pelo usuário e mostrados na aplicação cliente. Como o pacote SDES é composto por vários itens, torna-se primordial o detalhamento dos itens SDES para que os pacotes SDES possam ser compreendidos 45 em sua plenitude. Na Figura 20 podem ser observados o formato do item e do tamanho e a ordem de seus componentes internos. Figura 20: Item SDES Os itens SDES que compõe os pacotes SDES são formados por três itens. São eles: Type: Indica o tipo de item que será descrito ao longo do item SDES. Length: Contém o tamanho do item de descrição, ou seja, a quantidade de octetos de texto do campo Value. Value: É o texto que descreve o item e está na codificação UTF-8 [19]. Não existe nenhum tipo de separação ou bit de indicação entre os itens contidos nos pacotes SDES. O que acontece é que são colocados um ou mais octetos nulos entre os itens SDES dentro do pacote SDES. Quando o campo Type for zero, a lista de itens acabou. Os tipos padrões atuais de itens SDES são: CNAME: É um campo que provê um nome canônico para cada participante. Isto faz com que exista um modo estável e seguro de identificar, independente do SSRC, os participantes do grupo multicast. Isto se torna necessário pois podem existir nomes iguais e é necessário ser independente do SSRC pois podem haver colisões ou a aplicação pode reiniciar. Nestes casos, o SSRC será modificado e a identificação será perdida. Mas com este campo isto não acontece. Ele é composto pelo nome e IP do usuário e intermediado pelo símbolo @. Quando se faz uso das caixas NAT de tradução, é necessário que haja também a tradução do RTCP CNAME para criar uma única forma de identificação. É o único campo exigido pela especificação RTP que deve constar nos itens SDES (TYPE = 1). NAME: Contém o nome do participante para ser disposto na lista de participantes como parte da interface gráfica da aplicação cliente (TYPE = 2). EMAIL: Contém o endereço eletrônico no formato da RFC 822 [20]. Este dado deve ser validado como e-mail válido para se certificar de que é um usuário idôneo (TYPE = 3). PHONE: Contém o número telefônico do participante. A especificação RTP recomenda que seja um número internacional de telefone (TYPE = 4). LOC: Identifica o local de cada participante. Pode ou não incluir coordenadas GPS para localização (TYPE = 5). TOOL: Inclui o nome e a versão da implementação RTP (TYPE = 6). 46 NOTE: É um recado que pode ser escrito pelo usuário para os outros usuários, mas não é próprio para mensagens instantâneas (TYPE = 7). PRIV: É um mecanismo de extensão privada, utilizado para extensões específicas de descrição. São raramente usados, pois podem ser definidos novos itens para extensão ao invés de utilizar este campo (TYPE = 8). O exemplo de um pacote SDES RTCP pode ser visto na Figura 21. Neste exemplo, é representado um pacote SDES com os itens CNAME e NAME. Figura 21: Pacote SDES Outro pacote especificado pelo protocolo RTCP é o pacote BYE. Este é utilizado para controle de associação de membros. Caso um membro queira sair do grupo, ele deve mandar um pacote BYE para o grupo. Este tipo de pacote é diferenciado dos demais pelo tipo 203 e tem o formato igual ao da Figura 22. A partir do momento que a aplicação recebe um pacote BYE, ela obtém o campo RC que contém o número SSRC para ignorar qualquer pacote RTP ou RTCP da fonte especificada. É importante manter algum tipo de histórico mesmo algum tempo após o recebimento dos pacotes BYE, para permitir a prorrogação dos pacotes de dados. Figura 22: Pacote BYE 47 Além disso, nos pacotes BYE podem existir campos de texto explicando a razão da saída do grupo. Isto é útil para mostrar na interface da aplicação para os demais usuários. Porém, trata-se de campos opcionais. Podendo ser ignorados no recebimento. À medida que as aplicações são desenvolvidas e problemas novos vão sendo descobertos, torna-se adequado criar algumas extensões padronizadas para melhorar o funcionamento da aplicação específica e utilizar as facilidades do protocolo RTP de uma maneira melhor para a própria aplicação. O pacote RTCP utilizado para isto é o APP e nada mais é que uma maneira de aprimorar o uso do protocolo RTCP. À medida que desenvolvedores criam novas facilidades, eles podem adicioná-las à lista de tipos de pacotes padrões, caso sejam aceitas como adequadas para uso geral. O formato dos pacotes APP é mostrado na Figura 23. O campo PT deve ser preenchido pelo valor numérico 204 e deve ser criado um nome de pacote que se relacione ao nome da aplicação que está sendo desenvolvida e que seja único em relação aos demais pacotes e aplicações. Além disso, existem os dados dispostos em octetos no decorrer do pacote APP. Figura 23: Pacote APP Tendo em vista os pacotes RTCP descritos anteriormente neste capítulo, torna-se bastante trivial o entendimento geral do funcionamento do protocolo e de todos as precauções que o circundam. Caso um participante servidor gere um pacote RTCP composto, este deve começar com um pacote RTCP SR. Caso seja um cliente, deve iniciar com um pacote RTCP RR. Após o pacote RR ou RS, é necessário colocar em seguida um pacote SDES que deve incluir obrigatoriamente um item CNAME no mínimo. A inclusão dos outros itens é conduzida em relação ao perfil RTP escolhido. Por fim, devem ser incluídos sempre como último pacote o BYE. Os outros pacotes descritos não necessitam seguir uma ordem de preferência. Estas regras servem para que exista um modo de validação de pacotes seguros. 48 Um banco de dados de informações sobre a sessão e sobre os participantes da mesma é guardado por cada aplicação que está na sessão RTP. Existem algumas variáveis que são guardados para controle de tempo através do protocolo RTCP. E são elas: A largura de banda RTP: É configurada a partir do início de vida da aplicação e trata-se da largura de banda disponível para a sessão; A fração da largura de banda RTCP: É a fração da largura de banda RTP reservada para pacotes de relatório RTCP. Normalmente, separa-se 5% para isto. Mas pode ser diferente a porcentagem, dependendo do perfil RTP escolhido pela aplicação. Caso seja escolhido 0%, nenhum pacote RTCP será enviado; A média de tamanho dos pacotes RTCP enviados e recebidos pelos participantes; O número de membros na sessão; A fração dos participantes que enviaram pacotes RTP durante o intervalo de tempo de processamento de relatórios; O tempo no qual a ultima aplicação enviou o ultimo pacote RTCP; O tempo marcado para enviar a próxima transmissão; Um flag indicando qual aplicação enviou o último pacote RTP desde os últimos dois pacotes RTCP; Um flag indicando se a aplicação enviou algum pacote RTCP desde que foi inicializada. Além das variáveis descritas anteriormente, outras devem ser mantidas para que possam ser incluídas em pacotes RTCP SR. Ou seja, pacotes de controle dos transmissores. E são elas: O número de pacotes de dados RTP que foram enviados ao longo da sessão; O número de octetos contidos nos pacotes de dados RTP que foram enviados ao longo da sessão; O último número de seqüência utilizado; A correspondência entre o timestamp NTP e do clock RTP que está sendo usado. Uma aplicação de streaming em tempo real, que utiliza o protocolo RTP e RTCP, é segura e consistente se cada participante mantém o estado dos outros membros da sessão durante o tempo de vida da mesma. As informações que os participantes devem manter também podem ser guardadas em variáveis e devem conter os seguintes dados: O identificador SSRC; 49 As informações de descrição para pacote RTCP SDES: CNAME e outros específicos dependendo da aplicação necessitada; Dados que permitem o cálculo de estatísticas sobre qualidade de recepção para que seja possível a criação e envio de pacotes RTCP RR pelos receptores: jitter e número de pacotes perdidos; Dados recebidos pelos pacotes RTCP RS que permitem a sincronização labial entre som e imagem; O último momento no qual o participante enviou algum tipo de pacote na rede para o grupo multicast da sessão para que não hajam participantes inativos. Caso existam participantes inexistentes (cadastrados no grupo), a largura de banda utilizada será mal usada porque pacotes dos demais participantes serão encaminhados pela rede para este participante inexistente; Um flag que indica se este participante enviou algum tipo de dado durante o intervalo de tempo para relatórios RTCP; O buffers de reprodução de mídia; O estado do codec utilizado; Informações para controle de erros em geral, para que possam ser corrigidos. Qualquer tipo de estrutura criada a partir dos dados descritos anteriormente, devem ser indexadas pelo SSRC que é o que identifica unicamente cada participante da sessão RTP. Mas para ser feita a sincronização labial entre som e imagem, os participantes devem ser encontrados também pelos CNAMEs. Uma das etapas que devem ser desenvolvidas é a etapa de validação dos participantes recém cadastrados para que apenas participantes válidos sejam cadastrados. Por isto, antes que um participante seja aceito pelo grupo multicast que representa a sessão RTP, um pacote de validação deve ser enviado aos demais. Para isto, não devem ser utilizados apenas pacotes RTP. Portanto, devem ser usados pacotes RTCP para esta função para que não seja um método vulnerável de validação. Uma das maneiras mais simples é manter uma tabela de participantes da sessão que tenham recebido apenas um pacote RTP com um tamanho fixo. Esta deve ser leve e mantida com um time out constantemente verificado. Desta forma, usuários inativos podem ser descartados. Cada pacote RTP que contenha um CSRC válido, este deve ser adicionado no banco de dados. Outra forma de descartarem participantes é através dos pacotes BYE. Quando um destes é recebido, o participante que o enviou decidiu sair da sessão RTP. Como não existe nenhuma segurança quanto à ordem de chegada dos pacotes, já que os pacotes são empacotados em 50 datagramas UDP, um pacote RTCP BYE pode ser recebido como não sendo o último pacote do participante em questão. Para prevenir este tipo de problema, pode ser usado algum tipo de delay para o envio da mensagem BYE. Permitindo assim uma sincronização probabilística das mensagens. Também deve ser definido algum tipo de tempo limite para inatividade. Caso o participante fique um tempo maior que o esperado sem mandar pacotes, ele também será descartado. Outra questão que deve ser tratada é quando mais de um membro decide sair da sessão ao mesmo tempo. Isto pode causar congestionamento na rede com pacotes RTCP BYE. A versão 2 do protocolo RTP permite que pacotes BYE sejam mandados imediatamente apenas se menos que 50 membros decidem sair da sessão. Caso o número seja maior, precisará ser utilizado algum tipo de delay. Este tempo de espera deve ser proporcional ao número de membros que está tentando sair ao mesmo tempo. 51 5 ARQUITETURA Para disponibilizar a programação da FURG TV – canal de TV administrado pela FURG e distribuído via cabo pelas operadoras desse tipo de mídia na cidade do Rio Grande - na Internet, foi implementado um sistema cuja arquitetura e funcionamento são apresentados a seguir. O sistema para disponibilização do vídeo e audio de um canal de TV sobre a Internet se constitui de dois módulos em Java [5]: servidor e cliente. Considerando que a geração dos sinais de áudio e vídeo são realizados a partir de arquivos contendo a programação a ser distribuída (assim funciona a FURG TV) o aplicativo servidor abre os arquivos de vídeo e áudio e começa a transmitilos via multicast pela rede. Para isto, os arquivos são dividos em vetores de bytes que são encapsulados na medida que são enviados. Esses bytes são agrupados em pacotes RTP, que são transmitidos via UDP. O servidor passa então a executar um laço de espera por conexões via socket [6]. Também foi implementado um módulo de configuração através do qual, são ajustados os parâmetros da conexão entre o transmissor e o receptor. Toda a vez em que for necessário mudar os parâmetros, ele também deve ser executado. Através da sua execução é criado um arquivo texto de configuração, contendo os seguintes dados: Protocolo de Transporte, Porta, IP Multicast e TTL. No momento que o módulos transmissor e receptor iniciam a sua execuão eles acessam o arquivo texto contendo os parâmetros da conexão. Dessa forma é possível ajustar o mesmo endereço multicast e por isso é importante que o módulo de configuração seja executado antes da execução dos demais módulos. O usuário que transmitirá o arquivo multimídia também deve informar o tempo de duração do arquivo que será transmitido. Esse valor é armazenado em um arquivo texto com o nome de tempo.txt que será lido pelo módulo receptor para que seja definido o tempo de duração do loop de espera por dados. O servidor comprime os arquivos antes de enviá-los aos clientes. Os dados comprimidos são encapsulados em pacotes RTP. À medida que o transmissor lê o arquivo multimídia ele coloca os bytes lidos em um buffer do qual são produzidos frames codificados. São assinalados um número de seqüência e um timestamp para cada frame e colocados em pacotes RTP, prontos para o envio. Caso um frame seja maior do que o tamanho padrão de pacote, ele é fragmentado em vários pacotes para poder ser transmitido. Da mesma forma, se um frame for bem menor que o tamanho padrão de um pacote, ele pode ser agrupado a outros frames pequenos e, estes, colocados em um pacote RTP. O módulo servidor também é responsável por enviar pacotes RTCP, periodicamente, contendo relatórios do status da transmissão atual das mídias. Como se trata de uma comunicação full-duplex, ele também recebe feedback dos clientes sobre a qualidade de transmissão em pacotes 52 RTCP, dessa forma podendo otimizar a transmissão. O funcionamento do servidor de streaming está esquematicamente ilustrado na Figura 24. Figura 24: Funcionamento do Servidor de Streaming Os recursos fundamentais de redes em Java são proporcionados pelo pacote java.net, pelo qual é disponibilizada a comunicação baseada em fluxos. Isso significa que os aplicativos podem visualizar as redes como fluxos de dados. Além disso, esse esquema também proporciona a visão de comunicação baseada em pacotes, o que possibilita às aplicações a condição de transmitirem pacotes individuais, normalmente utilizados para a transmissão de vídeo e áudio pela Internet. Através dos sockets (também disponibilizado pelo Java.net), um processo pode estabelecer uma conexão com outro. E proporciona uma abstração capaz de fazer com que as operações sobre a rede sejam vistas como operações de E/S sobre um arquivo. Um programa pode ler ou escrever em um socket, como se faz com um arquivo, mas ao invés de salvar localmente o arquivo, um fluxo é enviado até outro processo. No caso de streaming, são utilizados sockets de datagrama do protocolo de transporte UDP que oferece serviços sem conexão. Ou seja, não existem garantias de que os pacotes enviados chegarão e que chegarão na ordem certa. Com primitivas não bloqueantes, esse tipo de conexão pode ser comparado com o serviço postal. Ou seja, a maneira como o correio envia suas cartas se assemelha bastante ao modo de comunicação UDP. Como pode ser visto na figura 25, é necessário fazer um bind para associar um socket a uma porta. 53 Figura 25: Localização do Socket Durante a captura e transmissão de multimídia, o codificador é invocado internamente para produzir os frames comprimidos, que são entregues a rotina de criação de pacotes RTP. Até que seja montado um frame completo de áudio, são coletadas seqüências de bytes representando o sinal a ser transmitido. Os dados do buffer de entrada são colocados em frames de tamanho fixo. Aparelhos de captura de áudio comuns podem produzir sinais digitais com uma resolução que varia de 8 a 24 bits. Usando quantização µ-law or A-law com taxas entre 8000 e 96000 quadros por segundo, mono ou estereo. A escolha a ser adotada é altamente dependente do tipo de codec escolhido. Os quadros de áudio são passados pelo encoder para compressão, mas podem ser passados para o buffer de saída com frames de tamanho fixo ou variável, dependendo do codec. A taxa continua fixa, mesmo sendo frames tamanho fixos ou variáveis. Tratando-se de vídeo, é aconselhável converter o arquivo para um formato mais compatível com o tipo de codec escolhido. Quando uma requisição de transmissão é recebida pelo servidor, é disparada uma thread para adicionar o cliente que a realizou no grupo multicast e fazer todo o controle necessário para a permanência dele através da sessão. Também é implementado um controle da largura de banda para que no momento da distribuição do vídeo para os clientes não ocorra sobrecarga nem lentidão em demasia. O cliente é executado sob a forma um Applet embutido no servidor Tomcat [7]. O Tomcat é um servidor de aplicações Java para web, distribuído como software livre e com código aberto, dentro do conceituado projeto Apache Jakarta e oficialmente endossado pela Sun como a Implementação de Referência para as tecnologias Servlet e JSP. É robusto e eficiente, podendo ser utilizado mesmo em um ambiente de produção. O Tomcat tem a capacidade de atuar 54 também como servidor web/HTTP, ou pode funcionar integrado a um servidor web dedicado como o Apache httpd ou o Microsoft IIS. Cada pacote a ser transmitido tem um campo de timestamp que representa o instante do primeiro octeto de dados no frame. Como foi dito anteriormente, ele é iniciado a partir de um valor randômico inicial que é incrementado através de uma taxa dependente da mídia. Como no nosso caso, trata-se de um arquivo multimídia pré-gravado em disco, os timestamps criados apenas representarão a seqüência de tempo de reprodução de cada quadro. Caso um quadro seja fragmentado em múltiplos arquivos RTP, cada pacote conterá o mesmo timestamp. O que é de grande valia no reordenamento de pacotes e seqüência de reprodução. Um exemplo de um pacote fragmentado pode ser visto na Figura 26. Figura 26: Fragmentação de Pacotes RTP Na figura, o quadro comprimido é divido em dois e o timestamp é duplicado. Após, é colocado o cabeçalho RTP em cada um e eles são encapsulados em dois pacotes RTP. Um dos problemas dessa técnica é a perda de fragmentos, que não possibilita a reconstrução no módulo cliente por falta de informações para tal. O servidor escolhe se irá mandar os fragmentos de uma vez ou em linhas de tempo separadas. Os pacotes são espalhados entre o intervalo de quadros, e como o protocolo RTP não faz nenhuma garantia sobre resolução, estabilidade e precisão do clock de mídia, o servidor será o responsável pela escolha do clock apropriado. Como já foi dito anteriormente, o módulo cliente é responsável por receber os pacotes RTP provenientes da rede e fazer as correções necessárias para reparar o caso de pacotes perdidos, recuperação de tempo, descumprimento da mídia e apresentação do arquivo multimídia para o usuário. Além disso, deve enviar relatórios sobre a qualidade de recepção para o servidor de streaming através de pacotes RTCP. Desta forma, o módulo servidor pode adaptar a transmissão para as características necessárias da rede em uso. O módulo cliente também mantém um banco de 55 dados sobre os demais participantes da sessão em questão para prover informações aos usuários, como pode ser visto na Figura 27, que ilustra o funcionamento do cliente. Figura 27: Funcionamento da Aplicação Cliente As operações necessárias em um cliente RTP são muito mais complexas do que as de um servidor RTP de streaming. Uma das grandes razões para esse aumento de complexidade é a característica de variabilidade inerente ao IP, pois o cliente deve estar preparado para compensar pacotes perdidos e recuperar a linha temporal de reprodução. O primeiro passo no processo de recepção, no lado cliente, é a coleta de pacotes da rede, validação e inserção dos mesmos nas filas de espera. Essa fase de processamento do módulo cliente independe do formato de mídia. Após a saída das filas de espera, os pacotes são verificados e checados antes de serem enviados ao buffer de reprodução. A partir desse buffer que é feita a suavização da reprodução. Muitas vezes, quadros são formados por vários pacotes. Quadros danificados ou perdidos são reparados e quadros são decodificados. Finalmente, o arquivo multimídia pode ser visto pelo usuário requisitante. Caso existam múltiplas entradas de áudio, o que não é o nosso caso, torna-se necessário o uso de um mixer de áudio para uniir os múltiplos fluxos em uma só saída de áudio. 56 Figura 28: Arquitetura Proposta A arquitetura descrita pode ser mais bem observada no exemplo da Figura 28. Parte-se do pressuposto de que o Cliente A não fez solicitação alguma e que o Cliente B já faz parte do grupo multicast em questão. O que é de grande valia é a seqüência de acontecimentos decorrentes do acesso do Cliente C ao link com o Applet. A partir deste momento, o Applet cria uma conexão via socket com o Servidor de Streaming que, por sua vez, dispara uma thread e adiciona o Cliente C ao grupo atual de transmissão. Outra característica do módulo cliente é que ele deve manter um bom valor dos gargalos criados entre os pacotes recebidos pela rede. E deve estar preparado para receber pacotes em rajadas. Além disso, o cliente guarda o valor exato do tempo de chegada dos pacotes RTP para que o jitter possa ser calculado. Caso seja mal calculado, existirá uma demora demasiada entre cada jorrada de pacotes, causando um delay de reprodução. Para isto, a taxa de clock de mídia deve ser somada com o offset para correção de skew entre o clock de referência e o clock de mídia. O processamento dos pacotes é processado em uma thread separada. Dentro do loop, pacotes são lidos através do socket multicast e inseridos nas filas de espera. A partir daí, os pacotes são passados quando necessário para reprodução. Caso pacotes cheguem em rajadas, alguns podem continuar nas filas de espera, dependendo da taxa de reprodução e capacidade de processamento. Sendo assim, uma thread espera dados do socket e os coloca nas filas de espera. Outra thread remove os dados das filas para a reprodução da mídia, através dos campos timestamps de cada pacote RTP. Quadros são guardados no buffer de reprodução por um tempo para suavizar as variações de tempo causadas pela rede. Além disso, permite que quadros fragmentados possam ser reagrupados. Os quadros são descomprimidos, erros são escondidos e a mídia pode ser interpretada pelo usuário. 57 O buffer de reprodução contém uma lista de nodos ordenados pelo tempo. Cada nó representa um quadro de dados de mídia, associado com informações temporais. Cada nó contém uma estrutura de dados com um ponteiro para o próximo nó, o tempo de chegada, o timestamp RTP e o tempo de reprodução para o quadro. Quando um pacote RTP chega, o último da fila de espera é removido e inserido um novo nodo no buffer de reprodução e posicionado em relação ao timestamp. Após todos os fragmentos de um quadro serem recebidos, o decodificador é invocado. Codecs de áudio não fragmentam quadros e cada pacote contém um quadro. Já codecs de vídeo, muitas vezes, geram múltiplos pacotes por quadro de vídeo com o campo Marker (M) do pacote RTP marcando o último fragmento. Mas nem sempre quando um pacote com o bit M com o valor 1 quer dizer que o quadro completo chegou completamente, pois pacotes podem chegar em ordens diferentes devido aos pacotes empacotados em datagramas. Neste caso, são utilizados o mesmo timestamp e o menor número de seqüência é utilizado para o último pacote de um quadro completo. Quando o quadro for reproduzido, o nó será removido. Uma das principais características que devem ser definidas é o delay de reprodução escolhido. O tempo de espera antes de algum erro de correção nos pacotes recebidos, o tempo de espera entre o recebimento do primeiro e o último pacote de um frame e a variação de tempo entre os pacotes, causada pelo jitter, são alguns dos fatores que influem na escolha do delay correto. O espaçamento entre pacotes de um quadro e o tempo de espera entre pacotes de dados e pacotes de correções de erros é controlado pelo servidor de streaming. Quando o cliente for escolher o tempo de reprodução para cada frame, ele irá seguir alguns passos. Primeiramente, a linha temporal do servidor será mapeada na linha temporal local, compensando o offset entre os clocks do servidor e do cliente. Então, o cliente irá compensar clock skew do servidor. O tempo de espera de reprodução da linha temporal local é calculado tendo como base um componente do servidor contendo seu próprio delay de reprodução e o jitter criado pela rede. Após isto, o tempo de espera de reprodução será ajustado caso a rota dos pacotes tenha mudado. Finalmente, o tempo de espera de reprodução é adicionado à base temporal para o cálculo do tempo de reprodução do quadro atual. É inevitável que haja variações na entrega de pacotes pela rede. Com isto, será escolhido um valor maior que o jitter da rede para que possa ser esperado antes de ser processado. Alguns segundos bastam para uma compensação apropriada. Como não é possível descobrir de antemão o jitter induzido pela rede. Será escolhido um valor randômico que poderá ser ajustado no decorrer da transmissão através de pacotes RTCP. O estimado jitter multiplicado por 3 deve ser somado ao tempo atual. Isto infere um novo tempo de reprodução. A variação do jitter irá depender tanto do caminho percorrido pelo tráfico da rede quanto o restante de dados trafegando na rede. A adaptação apenas tentará adaptar o delay do buffer de reprodução caso aconteça uma mudança significativa 58 em uma porção de pacotes descartados pela demora na chegada. Também irá adaptá-lo quando vários pacotes forem recebidos do servidor, caso ele esteja inativo por um período de tempo. Alguns eventos de mídia também poderão aumentar o tempo de espera entre pacotes consecutivos. Por exemplo, o silêncio suprimido no áudio pode causar um gargalo entre o último pacote e o primeiro pacote do próximo frame. Outro exemplo é uma variação da taxa do quadro de vídeo pode causar variações nos tempos de chegada entre os pacotes. O processo de reprodução de áudio é assíncrono. Por isso, enquanto um quadro está sendo reproduzido, outro estará sendo processado em outra thread. Esta característica é essencial para reprodução em sistemas operacionais com suporte limitados para aplicações multimídia. Isto é de extrema importância para este trabalho devido ao fato de que o cliente será acessado via Internet e será feito em Java. Ou seja, não existe um conhecimento prévio sobre qual sistema operacional os clientes estarão executando o módulo cliente. Já o processo de reprodução de vídeo é dedicado à atualização da taxa de exibição, o que determina o tempo máximo entre o cliente escrever no buffer de saída e a imagem ser exibida ao usuário. Frames (quadros) não são representados instantaneamente. O Applet será disponibilizado através de um link na página da FURG TV (www.furgtv.furg.br). Após fazer a solicitação de conexão por uma porta em um socket, é utilizado uma primitiva receive(), esperando assim a transmissão dos dados. Após o final da transmissão, o servidor fechará a conexão através da primitiva stop(), terminando assim a sessão. Desta maneira, será disponibilizado o canal de televisão da Universidade Fundação Universidade Federal do Rio Grande via streaming na Internet. 5.1 Módulo de configuração O módulo Configura TV é um arquivo chamado Configura_TV.jar composto de duas classes, cujo conteúdo encontra-se listado no ANEXO 1. A classe Main.java corresponde ao programa principal e é responsável por invocar a classe Parametros.java. Esta, por sua vez, cria o arquivo de configuração (parametros.txt) no diretório raiz, contendo as seguintes informações: Protocolo de Transporte: A aplicação necessita do envio de streams via Internet. Para isto, deve ser utilizado um protocolo de transporte como ponto de acesso. Tanto pode ser UDP quanto TCP, dependendo da necessidade. No nosso caso, será UDP. Porta: É um valor inteiro que representa a ligação entre a camada de aplicação (interface com o usuário) e a camada de transporte (comunicação). IP Multicast: O endereçamento Multicast permite enviar pacotes IP para um 59 determinado grupo de usuários. Pode ir de 224.0.2.0 até 238.255.255.255. Time To Live (TTL): Parâmetro responsável pelo controle do tempo de vida de um datagrama, evitando assim a existência de um loop eterno devido a algum erro de roteamento. O arquivo parametros.txt, após ser criado pela execução do Módulo de Configuração, é aberto pelos módulos Transmissor e Receptor, no momento em que é realizada a conexão via socket entre eles. Os dados contidos no arquivo são utilizados para possibilitar a realização de uma conexão consistente. Dessa forma, o número de IP, a porta, e o TTL podem ser modificados para um maior dinamismo na aplicação devido a restrições dos endereços multicast. O protocolo de transporte não é editável, devido a natureza da comunicação ser sem confirmação (Datagramas). Figura 29: Aplicação de configuração A Figura 29 apresenta a interface do Módulo de Configuração oferecida aos usuários. Como pode ser visto, existem quatro campos de texto (JTextField), nos quais os três últimos são editáveis. Para que possam ser feitas modificações nos parâmetros, o campo de texto em questão deve ser ativado através de um clique no JTextField correspondente. Para que as modificações sejam aplicadas pelos outros módulos, é necessário que elas sejam salvas. Para isso, deve-se clicar no botão Salvar ou através do sub-menu Salvar Configurações. Além disso, o Módulo Servidor deve ser reiniciado. Existe um sub-menu chamado Sobre contendo informações para que o usuário do Servidor de Streaming possa configurar a conexão entre o servidor e os clientes, através da modificação dos parâmetros contidos no arquivo texto. Também existem outro sub-menu que fecha a aplicação e o botão Fechar que tem o mesmo objetivo. Todos os componentes são colocados dentro de um JFrame (container). Para cada campo, existe um texto pop-up de ajuda, descrevendo-o para que possa haver um entendimento sobre quais valores podem preencher os campos. 60 Para o desenvolvimento da aplicação foram utilizados os seguintes pacotes: javax.swing, java.awt e java.io. Os dois primeiros pacotes permitem a criação da interface gráfica. Já o pacote java.io permite operações de entrada e saída (por exemplo: criar, editar e destruir arquivos de texto). 5.2 Módulo Transmissor Trata-se de um arquivo chamado compactado e executável chamado Transmissor_FURGTV.jar que é composto pelas bibliotecas JMF (jmf.jar, customizer.jar, sound.jar, mediaplayer.jar e multiplayer.jar) e pelos arquivos listados no ANEXO 2. O arquivo principal do módulo é o Main.java que instancia um objeto Janela. Este será o responsável por criar a janela principal do servidor de streaming. A interface criada pela classe Janela.java pode ser visto na Figura 30. Figura 30: Transmissor FURGTV Para iniciar o processo de streaming, deve-se clicar no botão Adicionar Vídeo para abrir o arquivo multimídia em questão para ser enviado pela Internet para os clientes que acessarem o link na página www.furgtv.furg.br para assistir a FURG TV via rede. Após o clique, é aberta uma janela do tipo javax.swing.JFileChooser, como pode ser observado na Figura 31. A janela principal não pode ser acessada enquanto não for escolhido um arquivo ou for cancelada a operação. 61 Figura 31: Selecionando o arquivo através do javax.swing.JFileChooser Após a escolha do arquivo em questão, ele selecionado é passado para uma variável URL chamada de mediaURL que é utilizada para criar o MediaLocator que será passado como parâmetro, junto aos dados contidos no arquivo parâmetros.txt (TTL, IP e Porta), para o objeto tempo durante sua criação. A instanciação do objeto tempo se faz necessário para definir o tempo de duração do arquivo de vídeo que será transmitido. Isto é útil para a definição, tanto no transmissor quanto no receptor, da temporização de streaming. A partir deste momento, uma nova janela é criada para a especificação do tempo de duração através de três campos: horas, minutos e segundos. Após o preenchimento destes, deve-se clicar no botão OK como é mostrado na Figura 32. Isto fará com que seja criado um arquivo chamado tempo.txt com a duração do vídeo. Este arquivo será acessado mais tarde pelo módulo Receptor, durante sua execução. Com isto, é finalizada a janela de temporização contida na figura e é criada a janela contido na Figura 35. Figura 32: Janela de Temporização Após a confirmação sobre a duração do vídeo, através do clique no botão OK, é criado um novo objeto Transmissor (proveniente da classe Transmissor.java). Esta classe é a que faz todo o 62 processamento de dados e transmissão via rede. Primeiramente, é configurada a interface visual e os atributos (MediaLocator, IP, TTL e Porta) são preenchidos pelos parâmetros passados pelo objeto tempo. Após é executado o procedimento start(), o que inicia a transmissão de verdade. Caso aconteça um erro durante a transmissão, este método retornará uma String com o erro especificado. Do caso contrário, retornará nulo. O procedimento é bastante simples: cria-se o processor através do procedimento createProcessor() para o arquivo especificado pelo MediaLocator. Após, é utilizado o método createTransmiter() para criar as sessões RTP através dos RTPManagers. Finalmente, é utilizado a primitiva start() contida na Classe Processor. Os atributos e métodos da classe Transmissor.java podem ser vistos em maiores detalhes na Figura 33. Figura 33: Classe Transmissor O procesimento createProcessor() cria um DataSource através do MediaLocator que contém o arquivo que será transmitido. Então é criado um Processor para tratar a entrada de dados do DataSource. Em seguida, o Processor é configurado e as trilhas são extraídas do mesmo e guardadas em um vetor chamado tracks[] do tipo TrackControl. Caso exista pelo menos uma trilha válida (vídeo ou áudio), é criado um ContentDescriptor que será setado para raw.rtp para restringir as trilhas para apenas formatos válidos RTP. Para cada trilha extraída, é verificada sua correspondente nos formatos disponibilizados pela API JMF. Caso a trilha seja de vídeo, o tamanho da tela de reprodução é testado porque os formatos JPEG e H263 funcionam apenas com determinados tamanhos de vídeo. Após a configuração das trilhas, é criado um fluxo gráfico com um DataSource de saída. 63 O procedimento createTransmiter() inicializa o vetor de RTPManagers com a instanciação de um objeto da classe Conexao.java que recebe como parâmetros o IP, a porta e o TTL. Através desta classe são criados duas sessões RTP através de duas conexões socket como pode ser visto na Figura 34. Nela estão representados a classe Conexão.java e a classe SockInputStream.java. Existem duas conexões socket entre elas (dataOutStrm e ctrlOutStrm), uma para cada trilha. Após a criação das sessões, inicia-se o envio de dados para o grupo multicast. Figura 34: Conexão via socket A janela de transmissão é o que representa o último estágio da execução do módulo Transmissor. Esta janela tem o título Servidor de Streaming – FURGTV e apresenta ao usuário qual o IP Multicast no qual foi criada a sessão RTP para transmitir os dados. Para o mesmo endereço, são usadas a porta especificada para transmitir um tipo de trilha (por exemplo: vídeo) e o mesmo número de porta mais dois para transmitir o outro tipo de trilha (por exemplo: áudio). Para cada trilha é criada uma sessão. Além disso, o status da transmissão é demonstrado. Isto pode ser visto pela Figura 35, na qual o status é transmitindo e as sessões foram criadas com o endereço IP Multicast 232.0.0.1. 64 Figura 35: Janela de Status de Transmissão Na janela principal sempre existirá uma aba ativa. Na Figura 30, a aba Servidor de Vídeos é a que é exibida por padrão no momento em que é executado o módulo Transmissor. A segunda aba, chamada de Sobre, é demonstrada na Figura 36. Para que uma aba fique ativa, deve-se apenas clicar sobre a mesma, em seu título. Já a última aba é apenas um informativo sobre os meus dados pessoais para futuro contato. Figura 36: Aba Sobre 5.3 Módulo Receptor O Módulo Receptor é um arquivo compactado e executável chamado Receptor_FURGTV.jar que é composto pelas bibliotecas JMF (jmf.jar, customizer.jar, sound.jar, mediaplayer.jar e multiplayer.jar) e pelos arquivos Main.java, PlayerApplet(contidos no ANEXO 3) e Conexao.java (contido no ANEXO 2). A classe Main é a que envoca a classe PlayerApplet, criando assim um objeto PlayerApplet. O funcionamento do applet é bastantes simples, para ser executado deve-se executar o procedimento init() contido na classe. Sua primeira tarefa é ler o arquivo parametros.txt para obter os dados configurados pelo módulo transmissor (TTL, Porta e IP Multicast). A partir disto, é inicializado o vetor de String, chamado sessão[], com os parâmetros obtidos. Com isto, é executada a função booleana initialize() que retorna true se os parâmetros forem corretos e as sessões tenham sido inicializadas. Caso contrário, retorna false. A primeira tarefa da função é criar um RTPManager para cada sessão especificada. Então os RTPManagers são configurados para adicionar o receptor ao grupo multicast (sessão). Para isto, eles são inicializados com objetos da Classe Conexão.java. O próximo passo é a criação de um buffer com o tamanho de 350. Daqui em diante, deve-se esperar receber algum dado do transmissor. Isto é verificado por 30 65 segundos no máximo. Enquanto isto está acontecendo, o label status contém o valor sintonizando para um melhor entendimento do usuário sobre o que está acontecendo durante a transmissão. Caso nenhum dado tenha sido recebido durante os 30 segundos, o label recebe o valor de canal fora do ar e os players são fechados, assim também como os RTPManagers. Para isto, existe o método close(). Outro método importante da classe PlayerApplet é o update() que, dependendo do parâmetro passado, ou adiciona participante ao grupo (SessionEvent) ou recebe streams (ReceiveStreamEvent). Caso tenha recebido um stream, o evento é mapeado para cada um dos seguintes eventos: RemotePayloadChangeEvent: Quando é necessário algum tipo de mudança de payload, o que não é possível e faz com que o Módulo Receptor seja fechado. NewReceiveStreamEvent: Evento que representa a chegada de um novo stream de dados. É criado um DataSource para o stream e através dele, descobre-se o formato da mídia. Após é criado um Player com um DataSource para o MediaManager. Cria-se uma janela para o player e o stream recebido é sincronizado. StreamMappedEvent: Caso o stream não seja nulo, é criado um novo DataSource e os formatos das trilhas são descobertas através de uma variável RTPControl. ByeEvent: Representa a chegada de uma mensagem Bye, indicando a saída de um participante da sessão. É então executada a função find() que procura por players no vetor playerWindows[] e, caso o stream passado como parâmetro seja condizendo com o do player, o objeto Player é retornado pela função. Caso contrário, retorna null e os players são fechados através do método close(). Existem duas subclasses que são utilizadas para a interface visual do Player: PlayerWindow (métodos inicialize(), close() e addNotify()) e PlayerPanel (método getPreferredSize()). O Módulo Receptor é de utilização muito simples para o usuário. Basta clicar no link respectivo ao Applet que será instantaneamente apresentado, no Player FURGTV, o programa que está sendo transmitido no exato momento da execução. 66 6 JAVA MEDIA FRAMEWORK O JMF tem como função principal à incorporação de mídias isócronas em aplicações Java ou em Applets. Além de servir como um apoio para aplicações desenvolvidas para streaming de vídeo e áudio, ele provê facilidade para a criação de plug-ins para suportar tipos de mídia que, atualmente, não são suportados pela API [18]. 6.1 Estrutura de Classes A API JMF é constituída por interfaces que definem o comportamento dos objetos e o modo como eles interagem para a captura, processamento e apresentação de multimídia. Implementações dessas interfaces operam dentro do próprio JMF através do uso de objetos chamados Managers. Dessa forma, torna-se fácil integrar novas implementações de interfaces para usá-las em conjunto com as atualmente implementadas. Quatro tipos de Managers podem ser usados: Manager: permite a construção de objetos JMF (Players, Processors, DataSources e DataSinks); PackageManager: mantêm um registro de quais pacotes contêm classes JMF; CaptureDeviceManager: mantêm um registro dos dispositivos habilitados para captura; PlugInManager: mantêm um registro dos possíveis plug-ins JMF (Multiplexers, Demultiplexers, Codecs, Effects e Renderers). A precisão da API JMF é mantida na ordem de nanosegundos. Um intervalo de tempo é representado em sua hierarquia de classes como um objeto: Time. Durante a implementação do trabalho aqui descrito foi desenvolvido o modelo Clock para manter a noção temporal de um stream de mídia. Através dele foram implementadas operações de sincronização e controle temporal, necessárias para a apresentação da mídia. A classe Clock usa um objeto TimeBase para realizar a cronometragem durante a apresentação da mídia. Um objeto TimeBase provê o tempo corrente. Essa marcação não pode ser resetada ou zerada, e é baseada no relógio do sistema. A variável MediaTime representa a posição corrente, no tempo, de uma mídia de streaming. Inicialmente, a variável é zerada. Já a duração de um stream de mídia é representado pela classe Duration. Ela contém o tempo necessário para que a mídia seja totalmente transmitida. No início da transmissão, o MediaTime é mapeado no TempoBase e este é usado para medir a passagem do tempo da transmissão. Durante a transmissão, o tempo corrente de mídia ou MediaTime é calculado pela fórmula a seguir: 67 MediaTime = TempoInicial + TAXA(TempoBaseAtual – TempoBaseInicial) No final da transmissão, o MediaTime é zerado e o TempoBase continua sua contagem. Caso a transmissão seja reiniciada, o MediaTime é remapeado para o TempoBase corrente. Por isso ele é denominado tempo base. Sendo sempre a base para a tomada de tempo. A inter-relação entre as classes do modelo temporal JMF pode ser visto em maiores detalhes na Figura 41. Figura 41: Modelo de Eventos JMF Foi imprescindível utilizar o modelo de eventos da API JMF. O modelo baseia-se na manutenção do estado atual do sistema multimídia e permite que programas respondam a condições de erros como, por exemplo, falta de dados. Quando um objeto JMF precisa atualizar as condições, ela envia um MediaEvent que nada mais é do que uma subclasse de Event utilizada apenas para eventos de mídia. Para cada objeto que pode enviar eventos de mídia, existe um listener correspondente. Foi necessário implementar uma interface listener para cada tipo de evento possível e adicionar a mesma a classe do objeto através do método addListener. Os objetos que podem enviar eventos são os objetos Controller (Players ou Processors) e alguns objetos Control. Além do objeto RTPSessionManager, que também pode enviar eventos deste tipo. O diagrama de classes para o modelo de eventos especificado pela API JMF está ilustrado na Figura 42. Figura 42: Diagrama de Classes do Modelo de Eventos JMF 68 Outro modelo definido pela API JMF é o de controle. Quase todos os objetos, e alguns plugins, precisam de algum tipo de controle. Dessa forma, devem ser criados objetos Control que implementam a interface Controls. Para isto, é usada a classe Control. Sua estrutura de classes é composta pelas seguintes subclasses: StreamWriterControl: interface que deve ser implementada para que objetos, como o DataSink, possam ler dados de um objeto DataSource e escrever em um destino. Através dela, torna-se possível limitar o tamanho de cada stream criado para transmissão; SilenceSuppressionControl: provê a retirada de silêncios que causam uma sobrecarga desnecessária no tráfego da rede; PacketSizeControl: proporciona controle sobre o tamanho dos pacotes enviados pela rede; FrameGrabbingControl: provê a facilidade para se obter um quadro isolado do stream de vídeo; FramePositionControl: usado para o posicionamento preciso dentro de objetos transmissores; QualityControl: possibilita o controle sobre a qualidade de transmissão e recepção multimídia. Muitas vezes, deve-se ajustar a qualidade ao consumo da CPU e a performance do codec escolhido; FormatControl: utiliza uma subclasse chamada TrackControl para controle de trilhas. Existirá normalmente uma trilha de áudio e outra de vídeo. BitRateControl: fornece controle sobre a taxa de bits transmitidos. Também é utilizado para controlar a taxa de compressão. Sua especificação informa as taxas em bits por segundo; BufferControl: fornece controle sobre o buffer utilizado para suavização da reprodução; FrameRateControl: ;az com que seja possível o controle sobre a taxa de transmissão de quadros. CachingControl: controle de cachê; FrameProcessingControl: faz o controle de processamento dos quadros através de parâmetros que permitem a otimização do codec; H261Control: permite controle sobre o codec de vídeo H.261; H263Control: permite controle sobre o codec de vídeo H.263; MpegAudioControl: através deste podem ser feitas modificações nos parâmetros do codec de áudio MPEG; 69 KeyFrameControl: modificações podem ser feitas entre os intervalos dos quadros chave; MonitorControl: possibilita a pré-visualização do vídeo que está sendo capturado ou codificado; PortControl: utiliza métodos para o controle de saída do dispositivo de captura. O Java Media Framework utiliza um modelo de dados intrínseco que permite o uso de objetos chamados DataSource para transmitir conteúdos de mídia. Esses objetos tem a localização e o protocolo utilizado para transmitir o vídeo. Um DataSource é definido por um MediaLocator ou por uma URL. Caso for um MediaLocator, ele pode ser construído mesmo se o protocolo especificado pelo DataSource não for suportado. Além disso, ele utiliza um vetor de bytes para transmissão. Podem existir diferentes tipos de DataSources como pode ser visto na estrutura de classes representada na Figura 37. Elas são classificadas em relação ao modo como a transferência de dados é iniciada. Figura 43: Tipos de DataSource O funcionamento JMF através de DataSources é bastante simples. O cliente inicia a recepção de dados e controle do fluxo de dados através de PullDataSources. A entrada de dados para o mesmo pode ser feita via web (HTTP) ou via arquivo (FILE). Já objetos PullBufferDataSource fazem o mesmo, mas incluem um buffer para suavizar a transmissão. Da mesma forma, o servidor inicia a transferência e o controle do fluxo de dados através de um PushDataSource que pode fazer ou broadcast ou multicast ou VOD dos dados. No caso deste trabalho, foi usado o modo de comunicação multicast. Caso o servidor queira utilizar um buffer, deve ser criado o objeto PushBufferDataSource ao invés do objeto PushDataSource. 70 6.2 Funcionamento da API JMF A API JMF versão 1.0 (Java Media Player API) possibilita desenvolver programas, na linguagem Java, capazes de reproduzir mídias. A versão 2.0 do JMF estende esse framework para prover suporte à capturação e o armazenamento de dados de mídia, controlar o tipo de processamento realizado durante o playback, padronizar o processamento de streams de dados de mídia. A partir dessa versão foi disponibilizada a extensão de plug-ins JMF para otimizar e possibilitar o uso de tipos de mídia não suportados pelo JMF. A Figura 37 ilustra o funcionamento abstrato da API JMF. Entre parênteses são descritos os nomes das classes, correspondentes aos dos dispositivos por elas abstraídos. Ali se pode deparar que a câmera de vídeo capta imagens para transmissão ao vivo. Figura 37: Abstração da API JMF O vídeo fonte pode ser um arquivo salvo previamente em disco ou captado ao vivo. Mas o seu formato deve ter a possibilidade de ser transformado em JPEG/RTP. Alguns exemplos de formatos que apresentam essa propriedade são Cinepak, RGB, YUV e JPEG. Outros formatos podem não funcionar corretamente devido a restrições da classe Processor, explicada em maiores detalhes anteriormente. Outra característica importante a ser considerada é a dimensão do vídeo que deve ser múltipla de 8x8. Um exemplo de dimensão admitida seria 320x240. Cada formato de vídeo tem características distintas, sendo que os principais formatos suportados pela API JMF são os apresentados na Tabela 8, onde também estão descritas as suas respectivas características. 71 Formato Tipo Qualidade Requisitos do CPU Requisitos de Largura de Banda Cinepack AVI, Quicktime Média Baixo Alto Alta Alto Alto MPEG-1 MPEG H.261 AVI, RTP Baixa Médio Médio H.263 AVI, RTP, Quicktime Média Médio Baixo JPEG RTP, AVI, Quicktime Alta Alto Alto Tabela 8: Formatos de vídeo suportados pelo JMF Normalmente em aplicações de conferência de vídeo, os formatos H.261 e H.263 são utilizados. Outro formato que deve também ser definido é o tipo de áudio que será utilizado. Aplicações de telefonia, por exemplo, devem utilizar taxas baixas de bits por segundo na reprodução da fala, e, por isso, normalmente é utilizado o formato G.723. Os padrões de áudio mais conhecidos e suportados pelo JMF estão descritos na Tabela 9. Requisitos de Largura de Banda Formato Tipo Qualidade Requisitos do CPU PCM AVI, WAV, Quicktime Alta Baixo Alto Baixa Baixo Alto Média Médio Médio Mu-Law ADPCM MPEG1 GSM G.723 AVI, Quicktime, WAV, RTP AVI, Quicktime, WAV, RTP MPEG Alta Alto WAV, RTP Baixa Baixo WAV, RTP Média Médio Tabela 9: Formatos de áudio suportados pelo JMF Alto Baixo Baixo O servidor de streaming aceita como entrada o local de onde serão fornecidos os dados a transmitir, indicado através de uma URL. Os seguintes passos são necessários para que a transmissão aconteça sem problemas: criar um Processor para a URL de entrada; configurar o Processor; configurar o stream de saída do Media Locator, baseado na extensão do arquivo de entrada, determinar o Content Descriptor usando o MimeManager; 72 configurar o Content Descriptor de saída contido no Processor; Verificar se cada formato de trilha de dados está em um formato suportado; Obter o DataSource de saída e usá-lo em conjunto com o Media Locator de saída para criar o DataSink; Iniciar o Processor e o DataSink; Esperar que o evento EndOfStreamEvent provido pelo DataSink ocorra, indicando o final do arquivo; Fechar o Processor e o DataSink. Para transmitir um stream RTP, conforme ilustrado na Figura 38, deve-se usar um Processor para produzir um DataSource de dados RTP codificados e construir ou um SessionManager ou outro DataSink (para controlar a transmissão). Existem duas maneiras de enviar dados RTP: Usar um Media Locator que têm os parâmetros da sessão RTP para construir um DataSink chamando a função Manager.createDataSink; Usar um Session Manager para criar e enviar streams de dados e controle. Os formatos das trilhas são configurados através da obtenção de cada trilha através de TrackControl. Chama-se então a função setFormat para especificar o formato em questão. Esta especificação é feita através da atualização de uma string. Figura 38: Transmissão JMF O Processor tenta carregar o plug-in que suporta o formato escolhido. Caso o formato seja desconhecido pela API JMF a exceção UnSupportedFormatException será disparada. O formato de saída é especificado através do método setOutputContentDescriptor. Como neste trabalho não foi necessário nenhum tipo de multiplexação, já que a aplicação em foco trata apenas uma fonte de dados (servidor de streaming), o content descriptor de saída será ContentDescriptor.RAW. Cada 73 trilha (vídeo e áudio) é transmitida em sessões RTP distintas. Ou seja, é criada uma sessão para transmitir os dados de vídeo e outra para transmitir os dados de áudio. O tempo de espera de um pacote ou intervalo de empatamento é o tempo necessário ao pacote percorrer a rede e chegar ao destino. Ele determina o mínimo intervalo de tempo fim-a-fim. Este valor deve estar no intervalo de 0 a 200 ms para dados de áudio que serão transmitidos. Essa restrição permite que o buffer do cliente possa ser redimensionado. Cada codec tem um valor específico para o delay respectivo. Já para streams de vídeo, um frame de vídeo é transmitido em múltiplos pacotes RTP. O tamanho de cada pacote enviado é limitado pelo MTU. A mídia que será enviada pela rede via socket para um grupo multicast pode ser manipulada antes do envio, ou antes de ser apresentada ao usuário. Caso seja um fluxo multiplexado, as trilhas serão extraídas individualmente por um demultiplexador. Se elas estiverem comprimidas serão decodificadas por um codec e transformadas no formato RAW (próprio para apresentação). Filtros também podem ser aplicados às trilhas decodificadas e transformá-las em tipos diferentes, dependendo das necessidades da aplicação, ou devido a restrições da rede. O próximo passo é a entrega das trilhas para o dispositivo de saída. Durante a recepção de streams de mídia, conforme ilustrado na Figura 39, o Session Manager poderá criar um DataSource para os pacotes que são providos pela rede, que por sua vez poderão criar ou um Processor ou um Player ou um DataSink. Caso o objetivo seja armazenar os dados de mídia recebidos em um arquivo, deve-se usar ou um Processor ou um DataSink. Se a escolha for um Processor, deverá ser criado outro DataSource que criará o DataSink. Este salvará os dados em um arquivo.Por outro lado, se o objetivo for reproduzir os dados recebidos pela rede, deve-se criar um Player. Figura 39: Recepção JMF 74 Os filtros podem ser aplicados em um vídeo para produzir alguns efeitos especiais. Tanto pode ser aplicado antes da mídia ser processada pelo codec ou depois. Normalmente são aplicadas a dados RAW (descomprimidos). A relação hierárquica entre os principais componentes JMF e como eles interagem na arquitetura API encontra-se ilustrada na Figura 40. Figura 40: Arquitetura JMF As aplicações Java e os Applets são desenvolvidas sobre a API JMF da mesma maneira como se usa uma biblioteca em um programa. Mas, em se tratando de uma API, a JMF contém uma série de bibliotecas. Da mesma forma, a API JMF faz uso de Plug-ins JMF. Na última camada, estão descritos os tipos de dispositivos que podem ser aplicados e que modificam e processam a mídia. 75 7 TESTES E RESULTADOS A Figura 44 mostra uma cena da saída de vídeo que foi assistida através da execução do Módulo Receptor, durante testes realizados dia 21/12/07, às 15h, no Núcleo de Computação Científica (NCC). Os testes levaram trinta minutos e vinte e dois segundos, duração do vídeo cedido pela FURGTV. Tratava-se de um programa, chamado MUSIURG 19 – dia do rock 1, que tinha como foco principal a música regional e da cidade. Figura 44: Player FURGTV Durante a realização do experimento foi utilizado um Notebook Dell Latitude D520 (Intel Core 2 Duo, 1.66GHz com 1014 MB de ram) executando o Módulo Transmissor no Windows Vista Home Basic. Ou seja, representando o Servidor de Streaming. Além disso, foram utilizados três PCs executando o Módulo Receptor, na condição de telespectadores da FURGTV, utilizando o Ubuntu 7.10 como Sistema Operacional. O tamanho padrão do vídeo foi de 320 pixels de largura e 240 pixels de altura. O valor de jitter (RTPtime) foi escolhido como 77. Foi utilizado o software Wireshark 0.99.6a (www.wireshark.org) para avaliar os pacotes que transitaram pela rede através do filtro UDP para pacotes enviados para o endereço multicast 230.0.0.1. Através da análise estatística mostrada na Figura 45 pode-se perceber que a maior porcentagem de pacotes enviados para o endereço em questão tinha o tamanho entre 640 e 1279 bytes, totalizando 93,33% do total de pacotes criados e enviados. Também foi observado que existiram várias faixas de tamanho de pacotes que não foram utilizados, ou seja, não foram criados pacotes desses tamanhos durante a transmissão. 76 Figura 45: Tamanho dos pacotes Outro dado importante a ser relatado é que foi utilizado o tipo RAW para transmissão, ou seja, o vídeo foi decodificado para esse padrão. Além disso, foi utilizado o tipo jpeg/rtp para codificação. A taxa média de bits por segundo foi de 780.58 kbps, enquanto a taxa média de quadros por segundo foi de 16.6 fps. Foram capturados vários gráficos indicando quantos pacotes por segundo eram recebidos pelos receptores. A Figura 46 mostra a variação dos pacotes para o primeiro receptor, decorridos 950 segundos após a sua execução. Figura 46: Pacotes Recebidos pelo Primeiro Receptor Como pode ser visto na figura, o Módulo receptor estabilizou a transmissão após o tempo de 950 segundos. Com isso, houve uma suavização na tramitação dos pacotes e um melhor fluxo de transmissão por parte do Servidor de Streaming. Para a leitura do gráfico, cabe ressaltar que foi utilizado 1 segundo por tick e foram utilizados 5 pixels por tick (eixo x do gráfico). Já o eixo y indica a quantidade de pacotes por tick. 77 No teste, os dois primeiros receptores foram iniciados quase que simultaneamente com o início da transmissão. O terceiro receptor foi iniciado e o gráfico mostrado na Figura 47 apresenta o comportamento da recepção dos pacotes nele ocorrido. Figura 47: Pacotes Recebidos pelo Terceiro Receptor Em relação ao terceiro Receptor, houve momentos de extrema alternância. Muitas vezes a suavização dos pacotes era regular e constante como do segundo 250 até o 300, devido a pouca interação da própria aplicação no envio e recebimento de pacotes de controle (protocolo RTCP). Já no período entre 300 e 350 segundos, houve várias mudanças bruscas entre a quantidade de pacotes recebidos pois haviam mais trocas de dados inerentes a transmissão multimídia. Outro fato relevante foi não ter ocorrido taxas de recepção maiores do que 250 pacotes em nenhum dos Receptores. 78 8 CONCLUSÃO Esta monografia descreveu o trabalho de pesquisa realizado sobre o tópico TV na Internet. Puderam ser verificadas as facilidades disponibilizadas pelo uso de transmissões multicast para na comunicação entre grupos de hosts, especialmente para a troca de dados, de áudio e vídeo. Os trabalhos foram complementados com a construção de um framework para diponibilizar a FURGTV na Internet. Foram implementados três módulos em Java: um Servidor de Streaming, um Módulo Receptor e um Módulo de Configuração. O Módulo Receptor reproduz um arquivo multimídia em questão em tempo-real. Para que isso tenha sido possível, foi utilizado o Protocolo RTP por motivo de eficiência. Além disso, foi utilizado a API JMF que apresenta muitas facilidades para a criação de aplicações multimídia. As aplicações multimídia disputam os recursos de rede com outras aplicações que estão sendo executadas simultaneamente. Quando se tratam de redes por datagramas, a partir de uma certa carga, passam a ocorrer atrasos e perdas de pacotes. Uma solução para essa situação é o gerenciamento da qualidade de serviço. Ou seja, devem-se utilizar novos mecanismos de gerenciamento que permitam a alocação adequada dos recursos. Muitas vezes, são adotados algoritmos de escalonamento de processos em tempo real, como rate-monotonic (RM), para que seja utilizado ao máximo a capacidade do processador, durante o processamento dos fluxos. Segundo [21], a largura de banda necessária para transmitir um fluxo de vídeo em tempo real é de 120 Mbps. Caso o vídeo seja compactado, a banda necessária passa a ser de 1,5 Mbps. O trabalho focalizou uma aplicação real, envolvendo streaming de áudio e vídeo: disponibilização da FURGTV na Internet. Por isso, alguns fatores ligados à segurança precisaram ser abordados como privacidade, autentificação, DOS, proteção quanto a replay, confiabilidade, entre outros. As sessões devem ser robustas e consistentes, para que não sejam hackeada com facilidade para atividades maliciosas. Para isso, provou-se importante o uso do protocolo RTP devido a suas restrições de segurança. Outra faceta importante nesse sentido é o protocolo de controle RTCP que fornece várias informações sobre a transmissão e sobre seus participantes. Apesar de todos esses cuidados conseguiu-se estabilidade e taxas de transmissão razoáveis. Através dos testes realizados, pôde-se verificar uma aplicabilidade bastante razoável, mantendo-se a taxa de transmissão entre 20 e 30 fps. 79 9 REFERÊNCIAS BIBLIOGRÁFICAS [1] FINZSCH, P., ROESLER, V. "Análise do mecanismo de pares de pacotes para inferência de banda máxima em redes de computadores". UNISINOS - Universidade do Vale do Rio dos Sinos, Centro de Ciências Exatas e Tecnológicas, junho de 2003. [2] KARSTEN, M., VENTER, R."MBone: The Multicasting Backbone". HSN-Team, Department of Computer Science, University of Pretoria, Pretoria, 2002, South Africa. [3] VENAAS S., CHOWN T. "Source Specific Multicast (SSM) with IPv6". School of Electronics and Computer Science, University of Southampton, United Kingdom. [4] ROESLER, V., GIULIANO G., VALDENI J. "ALM: Adaptive layering Multicast". UNISINOS -Universidade do Vale do Rio dos Sinos, Centro de Ciências Exatas e Tecnológicas. [5] NOGUCHI T., YAMAMOTO M. "Construction of a Robust Multicast Tree for ApplicationLevel Multicast". EICE TRANS. COMMUN., VOL.E88–B, NO.12 DECEMBER 2005. [6] FRANCIS P. "Yoid: Extending the Internet Multicast Architecture". Em: www.aciri.org. 2 de Abril, 2000. [7] BANEJEE S., BHATTACHARJEE B., KOMMAREDDY C. "Scalable Application Layer Multicast". Department of Computer Science,University of Maryland, USA. [8] SHULZIRINNE, CASNER, FREDERICK, JACOBSON. "RTP: A Transport Protocol for RealTime Applications". Columbia U. Internet Engeneering Task Force, maio de 2002. [9] http://www.protocols.com/pbook/h323.htm [10] TANENBAUM A. (2003). “Redes de Computadores”. Campus, Edição 4. [11] http://www.planet-lab.org [12] http://www.itu.org [13] Protocol Independent Multicast-Sparse Mode (RFC 2362). Em: http://www.ietf.org/rfc/rfc2362.txt [14] Internet Group Management Protocol (RFC 2236). Em: http://www.ietf.org/rfc/rfc2236.txt [15] RTP: A Transport Protocol http://www.ietf.org/rfc/rfc3550.txt for Real-Time Applications (RFC 3550). Em: [16] IANA: Órgão responsável por estabelecer numerações padrão para a Internet. Em: http://www.iana.org [17] Resource ReSerVation Protocol (RFC 2205 - RSVP). Em: http://www.ietf.org/rfc/rfc2205.txt [18] Java Media Framework (JMF). Em: http://java.sun.com/products/java-media/jmf/ 80 [19] F. Yergeau. "UTF-8, a Transformation Format of ISO 10646," Internet Engineering Task Force, RFC 2279, January 1998. [20] D. Crocker. "Standard for the Format of ARPA Internet Text Messages," Network Working Group, RFC 822, August 1982. [21] Coulouris G., Dolimore J., Kindberg T. "SISTEMAS DISTRIBUÍDOS – CONCEITOS E PROJETOS". Quarta Edição. Bookman Editora, 2007. [22] IP Multicast Technology Overview – http://www.cisco.com/uniivercd/cc/td/doc/cisint5wk/intsolns/mcst_sol/mcst_ovr.htm [23] REYNOLDS, J. RFC 1700 Internet Multicast Addresses. 2002. 81 ANEXOS ANEXO 1: MÓDULO DE CONFIGURAÇÃO Main.java /** Pacote Principal */ package configuratv; /** Programa Principal */ public class Main { public Main() {} /** Programa Principal */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new Parametros().setVisible(true); } }); } } Parametros.java /** Pacote Principal */ package configuratv; /** Pacote Gráfico awt */ import java.awt.Color; import java.awt.Font; import java.awt.Frame; import java.awt.Image; import java.awt.Label; import java.awt.Panel; import java.awt.Toolkit; /** Pacote de E/S */ import java.io.BufferedWriter; import java.io.FileWriter; /** Pacote Gráfico swing */ import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JTextPane; /** configura os parâmetros da conexão via socket entre o transmissor e os receptores*/ public class Parametros extends javax.swing.JFrame { public Parametros() //configura Aplicação no Inicio { initComponents(); Toolkit tk = Toolkit.getDefaultToolkit(); img = tk.getImage("furg.gif"); this.setIconImage(img); /** configura sub-menu */ jMenuItem1.setText("Salvar configurações"); jMenuItem3.setText("Sobre"); jMenuItem2.setText("Sair"); 82 /** Insere textos de ajuda */ jLabel1.setToolTipText("Uma aplicação que necessita da comunicação via Internet deve utilizar os protocolos de transporte como pontos de acesso (udp ou tcp)."); jLabel2.setToolTipText("Uma porta é um valor inteiro positivo que representa a ligação entre a camada de aplicação (usuário) e a camada de transporte (comunicação)"); jLabel3.setToolTipText("O endereçamento Multicast permite enviar pacotes IP para um determinado grupo de usuários. De 224.0.2.0 até 238.255.255.255 (os valores entre pontos vão de 0 até 255)."); jLabel4.setToolTipText("Time To Live = controla o tempo de vida de um datagrama, evitando assim que este fique \n" + "em um loop eterno devido a algum erro de roteamento."); /** inicializa variaveis */ protocolo = jTextField1.getText(); porta = jTextField2.getText(); ip = jTextField3.getText(); ttl = jTextField4.getText(); try { /** abre arquivo parametros.txt */ arq = new BufferedWriter(new FileWriter("C:/parametros.txt")); arq.write(protocolo+" "); arq.write(porta+" "); arq.write(ip+" "); arq.write(ttl); arq.close(); } catch(Exception e){e.printStackTrace();} } private void initComponents() { jPanel1 = new javax.swing.JPanel(); jLabel4 = new javax.swing.JLabel(); jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jTextField3 = new javax.swing.JTextField(); jTextField2 = new javax.swing.JTextField(); jTextField1 = new javax.swing.JTextField(); jTextField4 = new javax.swing.JTextField(); jButton2 = new javax.swing.JButton(); jButton1 = new javax.swing.JButton(); jMenuBar1 = new javax.swing.JMenuBar(); jMenu1 = new javax.swing.JMenu(); jMenuItem1 = new javax.swing.JMenuItem(); jMenuItem3 = new javax.swing.JMenuItem(); jMenuItem2 = new javax.swing.JMenuItem(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setTitle("Par\u00e2metros - FURGTV"); setBackground(new java.awt.Color(82, 109, 166)); setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); setForeground(java.awt.Color.lightGray); setResizable(false); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosed(java.awt.event.WindowEvent evt) { formWindowClosed(evt); } }); jPanel1.setBackground(new java.awt.Color(0, 51, 102)); jPanel1.setAutoscrolls(true); 83 jLabel4.setForeground(new java.awt.Color(255, 255, 255)); jLabel4.setText("TTL"); jLabel1.setForeground(new java.awt.Color(255, 255, 255)); jLabel1.setText("Protocolo"); jLabel2.setForeground(new java.awt.Color(255, 255, 255)); jLabel2.setText("Porta"); jLabel3.setForeground(new java.awt.Color(255, 255, 255)); jLabel3.setText("IP Multicast"); jTextField3.setText("232.0.0.1"); jTextField3.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jTextField3ActionPerformed(evt); } }); jTextField2.setText("5000"); jTextField2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jTextField2ActionPerformed(evt); } }); jTextField1.setEditable(false); jTextField1.setText("udp"); jTextField1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jTextField1ActionPerformed(evt); } }); jTextField4.setText("1"); jTextField4.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jTextField4ActionPerformed(evt); } }); jButton2.setText("Fechar"); jButton2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton2ActionPerformed(evt); } }); jButton1.setText("Salvar"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } }); org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(jPanel1Layout.createSequentialGroup() .add(18, 18, 18) .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) .add(jLabel4) 84 .add(jLabel2) .add(jLabel3) .add(jLabel1) .add(jButton2)) .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(jPanel1Layout.createSequentialGroup() .add(17, 17, 17) .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(jTextField2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 54, Short.MAX_VALUE) .add(jTextField1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 54, Short.MAX_VALUE) .add(jTextField4, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 54, Short.MAX_VALUE) .add(jTextField3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(52, 52, 52)) .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel1Layout.createSequentialGroup() .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) .add(jButton1) .add(29, 29, 29)))) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel1Layout.createSequentialGroup() .addContainerGap() .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jLabel1) .add(jTextField1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(16, 16, 16) .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jLabel2) .add(jTextField2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(15, 15, 15) .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jLabel3) .add(jTextField3, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(14, 14, 14) .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jLabel4) .add(jTextField4, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)) .add(26, 26, 26) .add(jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) .add(jButton2) .add(jButton1)) .addContainerGap()) ); jMenuBar1.setBackground(new java.awt.Color(51, 51, 51)); jMenu1.setBackground(new java.awt.Color(51, 51, 51)); jMenu1.setForeground(new java.awt.Color(204, 204, 204)); jMenu1.setIcon(new javax.swing.ImageIcon("")); jMenu1.setText("Menu"); jMenu1.setDisabledIcon(new javax.swing.ImageIcon("C:\\Users\\rafael\\Desktop\\ProjetoFURGTV\\FURGTV\\furg.gif")); jMenu1.setDisabledSelectedIcon(new javax.swing.ImageIcon("C:\\Users\\rafael\\Desktop\\ProjetoFURGTV\\FURGTV\\furg.gif")); jMenuItem1.setText("Item"); jMenuItem1.setBorder(javax.swing.BorderFactory.createEtchedBorder()); jMenuItem1.setContentAreaFilled(false); jMenuItem1.setRolloverEnabled(true); jMenuItem1.addActionListener(new java.awt.event.ActionListener() { 85 public void actionPerformed(java.awt.event.ActionEvent evt) { jMenuItem1ActionPerformed(evt); } }); jMenu1.add(jMenuItem1); jMenuItem1.getAccessibleContext().setAccessibleParent(jMenu1); jMenuItem3.setText("Item"); jMenuItem3.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jMenuItem3ActionPerformed(evt); } }); jMenu1.add(jMenuItem3); jMenuItem2.setText("Item"); jMenuItem2.setBorder(javax.swing.BorderFactory.createEtchedBorder()); jMenuItem2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jMenuItem2ActionPerformed(evt); } }); jMenuItem2.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { jMenuItem2MouseClicked(evt); } }); jMenu1.add(jMenuItem2); jMenuBar1.add(jMenu1); setJMenuBar(jMenuBar1); org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(jPanel1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) ); layout.setVerticalGroup( layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 200, Short.MAX_VALUE) ); pack(); } private void jMenuItem3ActionPerformed(java.awt.event.ActionEvent evt) { /** Cria Frame Sobre */ status = new JTextPane(); status.setEditable(false); status.setBackground(Color.BLACK); status.setText("INSTRUÇÃO: Preencha os valores correspondentes aos parâmetros de conexão. \n\n"+ " DESENVOLVEDOR: Rafael Vieira Coelho ([email protected]). \n\n" + " OBSERVAÇÃO: Através deste software é feita a padronização entre o Servidor \n" + " de Streaming e o Receptor da FURG TV via Web."); status.setForeground(Color.WHITE); Font fo = new Font ("Arial", Font.BOLD, 12); status.setFont(fo); 86 painel = new Panel(); f = new JFrame(); painel.add(status); painel.setBackground(Color.BLACK); f.setResizable(false); f.setSize(480,140); f.add(painel); f.setTitle("Sobre"); f.setEnabled(true); f.show(true); f.setDefaultCloseOperation(f.HIDE_ON_CLOSE); jPanel1.setEnabled(false); f.setIconImage(img); } private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { /** salva novos valores nas variaveis */ protocolo = jTextField1.getText(); porta = jTextField2.getText(); ip = jTextField3.getText(); ttl = jTextField4.getText(); try { /** abre arquivo parametros.txt */ arq = new BufferedWriter(new FileWriter("parametros.txt")); /** salva novos parametros */ arq.write(protocolo+" "); arq.write(porta+" "); arq.write(ip+" "); arq.write(ttl); /** fecha arquivo */ arq.close(); } catch(Exception e){e.printStackTrace();} }//GEN-LAST:event_jButton1ActionPerformed private void jTextField4ActionPerformed(java.awt.event.ActionEvent evt) {} private void jTextField3ActionPerformed(java.awt.event.ActionEvent evt) {} private void jTextField2ActionPerformed(java.awt.event.ActionEvent evt) {} private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) {} private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {System.exit(2);} private void jMenuItem2ActionPerformed(java.awt.event.ActionEvent evt) {System.exit(1);} private void jMenuItem2MouseClicked(java.awt.event.MouseEvent evt) {} private void formWindowClosed(java.awt.event.WindowEvent evt) {System.exit(0);} private void jMenuItem1ActionPerformed(java.awt.event.ActionEvent evt) { /** salva novos valores nas variaveis */ protocolo = jTextField1.getText(); porta = jTextField2.getText(); ip = jTextField3.getText(); ttl = jTextField4.getText(); try { /** abre arquivo parametros.txt */ arq = new BufferedWriter(new FileWriter("C:/parametros.txt")); /** salva novos parametros */ 87 arq.write(protocolo+" "); arq.write(porta+" "); arq.write(ip+" "); arq.write(ttl); /** fecha arquivo */ arq.close(); } catch(Exception e){e.printStackTrace();} }//GEN-LAST:event_jMenuItem1ActionPerformed // Declaração de variáveis private javax.swing.JButton jButton1; private javax.swing.JButton jButton2; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JMenu jMenu1; private javax.swing.JMenuBar jMenuBar1; private javax.swing.JMenuItem jMenuItem1; private javax.swing.JMenuItem jMenuItem2; private javax.swing.JMenuItem jMenuItem3; private javax.swing.JPanel jPanel1; private javax.swing.JTextField jTextField1; private javax.swing.JTextField jTextField2; private javax.swing.JTextField jTextField3; private javax.swing.JTextField jTextField4; private String protocolo; private String porta; private String ip; private String ttl; private BufferedWriter arq; private javax.swing.JTextPane status; private Panel painel; private JFrame f; private Image img; } ANEXO 2: MÓDULO TRANSMISSOR Main.java /** Pacote principal */ package transmissor_furgtv; /** Bibliotecas auxiliares */ import javax.swing.JFrame; /** Classe Principal */ public class Main { /** Construtor Abstrato */ public Main(){} /** Cria Instancia do Programa Principal */ public static void main(String[] args) { Janela ProgramaPrincipal = new Janela(); ProgramaPrincipal.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } Janela.java 88 /** PACOTE PRINCIPAL*/ package transmissor_furgtv; /** BIBLIOTECAS */ /** Bibliotecas auxiliares */ import javax.media.Format; /** Pacote lang */ import java.lang.*; /** Pacote IO */ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.File; import java.io.FileReader; /** Pacote awt */ import java.awt.*; import java.awt.Dimension; import java.awt.Event.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.FlowLayout; import java.awt.BorderLayout; /** Pacote net */ import java.net.URISyntaxException; import java.net.URL; import java.net.MalformedURLException; import javax.media.MediaLocator; /** Pacote swing */ import javax.swing.JFrame; /** fornece recursos básicos de janela */ import javax.swing.JLabel; /** exibe texto e imagens */ import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.SwingConstants; /** constantes comuns utilizadas com Swing */ import javax.swing.Icon; /** interface utilizada para manipular imagens */ import javax.swing.ImageIcon; /** carrega imagens */ import javax.swing.JFrame; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JFileChooser; /** Interface Gráfica */ public class Janela extends JFrame implements ActionListener { JFrame f = new JFrame(); /** cria um JTabbedPane = permite painéis paralelos*/ JTabbedPane tabs = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); /** cria Botões */ JButton botao2 = new JButton(" Sair "); JButton botao1 = new JButton(" Adicionar Video "); /** cria Painéis */ 89 JPanel panel_sobre = new JPanel(); JPanel panel = new JPanel(); JPanel panel_texto = new JPanel(); JPanel panel_menu = new JPanel(); JPanel panel_equipe = new JPanel(); /** cria atributo cor */ Color cor = Color.lightGray; /** cria icone */ Toolkit tk = Toolkit.getDefaultToolkit(); Image icone = tk.getImage("furg.gif"); /** cria imagens */ Icon img = new ImageIcon("tv.jpg"); Icon img2 = new ImageIcon("furg.gif"); /** cria Label com as imagens */ JLabel label_imagem = new JLabel(img); JLabel label_imagem_furg = new JLabel(img2); /** Cria Texto */ JTextPane texto = new JTextPane(); /** Cria Texto_sobre */ JTextPane texto_sobre = new JTextPane(); /** Cria Texto_equipe */ JTextPane texto_equipe = new JTextPane(); /** Arquivo com parâmetros da conexão */ private BufferedReader arq; private String horas, min, sec; /****************************** Trata Eventos ********************************/ /** Botao clicado! */ public void actionPerformed(ActionEvent event) { Object source = event.getSource(); if (source == botao2) /** Botao "Sair" pressionado */ { System.exit(0); } else { //*************** Botao "Adicionar Video" pressionado *******************/ /** desabilita botão "Adicionar Video" */ botao1.setEnabled(false); /** desabilita abas */ tabs.setEnabled(false); /** desabilita imagem e texto */ label_imagem_furg.setEnabled(false); texto.setEnabled(false); /** cria um filechooser para procurar o arquivo que será enviado */ JFileChooser fileChooser = new JFileChooser(); int result = fileChooser.showOpenDialog( null ); 90 if ( result == JFileChooser.APPROVE_OPTION ) /** se usuario escolheu um arquivo */ { URL mediaURL = null; Format fmt = null; try { /** obtém o arquivo como URL */ mediaURL = fileChooser.getSelectedFile().toURL(); final MediaLocator m = new MediaLocator(mediaURL); /** abre o arquivo parametros.txt */ arq = new BufferedReader(new FileReader("C:/parametros.txt")); char[] linha = new char[100]; char[] protocolo = new char[15]; char[] porta = new char[15]; char[] ip = new char[15]; char[] ttl = new char[15]; /** le parametros do arquivo parametros.txt */ arq.read(linha); int x = 0, y = 0; y = 0; while ((linha[x] != ' ')&&(y!=15)) { protocolo[y] = linha[x]; x++; y++; } x++; y = 0; while ((linha[x] != ' ')&&(y!=15)) { porta[y] = linha[x]; x++; y++; } x++; y = 0; while ((linha[x] != ' ')&&(y!=15)) { ip[y] = linha[x]; x++; y++; } x++; y = 0; while ((linha[x] != ' ')&&(y!=15)) { ttl[y] = linha[x]; x++; y++; } /** fecha arquivo */ arq.close(); final String IP = String.valueOf(ip).trim(); final String TTL = String.valueOf(ttl).trim(); 91 final String PORTA = String.valueOf(porta).trim(); f.setVisible(false); /** cria objeto tempo com os parametros lidos */ java.awt.EventQueue.invokeLater(new Runnable() { public void run() { new tempo(m, IP, PORTA, TTL).setVisible(true); } }); } catch (IOException e) {System.err.println( "Erro: Arquivo não encontrado!" ); e.printStackTrace();} if (mediaURL != null) /** exibe somente se houver um URL valido */ { byte[] b = new byte[256]; /** Converte a URL em um objeto file */ File arquivo = new File(mediaURL.getFile()); if (arquivo.exists()) { arquivo.deleteOnExit(); System.out.println("Tamanho do Arquivo: " + arquivo.getAbsoluteFile().length() + " bytes."); } else {System.out.println("ERRO: Arquivo inválido!");} } } /** habilita botão "Adicionar Video" */ botao1.setEnabled(true); /** habilita abas */ tabs.setEnabled(true); /** habilita imagem e texto */ label_imagem_furg.setEnabled(true); texto.setEnabled(true); } } /** Janela Principal */ public Janela() { /** Adiciona Icone */ f.setIconImage(icone); /** Cria layouts */ BorderLayout layout = new BorderLayout(3,3); BorderLayout layout2 = new BorderLayout(2,2); BorderLayout layout3 = new BorderLayout(2,2); BorderLayout layout1 = new BorderLayout(2,2); /** Adiciona Layouts */ panel.setLayout(layout); panel_sobre.setLayout(layout2); panel_menu.setLayout(layout3); panel_equipe.setLayout(layout1); /** Formata Texto */ texto.setText("\n A Fundação Universidade Federal do Rio Grande, através " + "\n da Pró-Reitoria de Assuntos Comunitários e Estudantis " + "\n apresenta a sua TV Universitária em nova fase." ); 92 texto.setFont(new Font("sansserif", Font.BOLD, 17)); texto.setEditable(false); cor = Color.black; texto.setBackground(cor); cor = Color.white; texto.setForeground(cor); /** Formata Texto_sobre */ texto_sobre.setText(" \nA FURG TV oferece nove programas de TV distribuídos em sua grade de programação semanal, com "+ " \numa hora e meia inédita por dia e mais três horas e meia de horários alternativos de seus programas. " +" \nTais programas têm por objetivo, além da divulgação institucional, a disseminação dos conhecimentos " +" \nproduzidos na Instituição, a discussão democrática de temas polêmicos e a promoção da educação e da cultura."); texto_sobre.setEditable(false); texto_sobre.setFont(new Font("sansserif", Font.BOLD, 12)); /** Formata Texto_sobre */ texto_equipe.setText("DADOS PESSOAIS" +"\nNome: Rafael vieira Coelho " +"\nEmail: [email protected] " +"\nTelefone: (53)91158533 " +"\n\nFORMAÇÃO ACADÊMICA " +"\nEnsino Fundamental 1991 - 1998: Instituto de Educação Juvenal Miller. Rio Grande-RS. " +"\nEnsino Médio 1999 - 2001: CTI - Colégio Técnico Industrial Prof. Mario Alquati, Rio GrandeRS. " +"\n Curso: Informática " +"\nGraduação 2003 - 2007: Fundação Universidade Federal do Rio Grande (FURG). Rio Grande RS. " +"\n Curso: Engenharia de Computação. "); texto_equipe.setFont(new Font("sansserif", Font.BOLD, 12)); texto_equipe.setEditable(false); cor = Color.black; texto_equipe.setBackground(cor); cor = Color.white; texto_equipe.setForeground(cor); texto_equipe.setToolTipText("http://www.ncc.furg.br/~rcoelho/"); /** Adiciona Componentes aos Paineis */ /** painel menu */ label_imagem_furg.setToolTipText("www.furg.br"); cor = Color.white; panel_menu.setBackground(cor); panel_menu.add(label_imagem_furg, BorderLayout.CENTER); panel_menu.add(botao1, BorderLayout.SOUTH); /** painel principal */ panel.add(botao2, BorderLayout.SOUTH); panel.add(panel_menu,BorderLayout.WEST); panel.add(texto,BorderLayout.CENTER); /** painel sobre */ label_imagem.setToolTipText("www.furgtv.furg.br"); panel_sobre.add(label_imagem,BorderLayout.NORTH); panel_sobre.add(texto_sobre,BorderLayout.CENTER); /** painel equipe */ panel_equipe.add(texto_equipe,BorderLayout.CENTER); 93 /** Textos de Ajuda dos Botões */ botao1.setToolTipText("Clique neste botão para inserir um novo vídeo de streaming"); botao2.setToolTipText("Clique neste botão para fechar o programa"); /** Adiciona Listeners nos Botões */ botao2.addActionListener(this); botao1.addActionListener(this); /** coloca cada container no painel com guias */ tabs.addTab( "Servidor de Videos", panel); tabs.addTab( "Sobre", panel_sobre); tabs.addTab( "Equipe", panel_equipe); /** cores de background */ tabs.setBackground(cor); panel.setBackground(cor); cor = Color.white; panel_sobre.setBackground(cor); /** coloca o painel com guias no quadro */ f.add(tabs); f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); f.setSize(700,250); /** configura o tamanho do frame */ f.setVisible( true ); /** exibe o frame */ f.setResizable(false); f.setTitle("FURG TV na Internet"); } } Transmissor.java /** PACOTE PRINCIPAL*/ package transmissor_furgtv; /** BIBLIOTECAS */ /** Grafica awt */ import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.ImageObserver; import java.awt.image.ImageProducer; import javax.swing.JButton; import javax.swing.JComponent; /** Grafica swing */ import javax.swing.JFrame; import javax.swing.JLabel; /** E/S */ import java.io.*; /** Midia (JMF) */ import javax.media.*; import javax.media.protocol.*; import javax.media.protocol.DataSource; import javax.media.format.*; import javax.media.control.TrackControl; import javax.media.control.QualityControl; /** Rede */ import java.net.InetAddress; 94 /** RTP */ import javax.media.rtp.*; import javax.media.rtp.rtcp.*; /** CLASSE TRANSMISSOR RTP */ public class Transmissor extends JFrame { private Panel painel; private JFrame f; /** MediaLocator => ENTRADA DE ARQUIVO */ private MediaLocator locator; /** Endereço IP Multicast */ private String IP; /** PORTA */ private int porta; /** TTL */ private int t; /** PROCESSOR */ private Processor processor = null; /** VETOR DE MANAGERS RTP */ private RTPManager rtpMgrs[]; /** DATASOURCE = DADOS DE SAIDA */ private DataSource dataOutput = null; private Label status; private java.awt.Label status2; /** CONSTRUTOR DO TRANSMISSOR (localizador de video, IP, porta, ttl,formato) */ public Transmissor(String horas, String minutos, String segundos, MediaLocator locator, String ip, String pb, String TTL,Format format) { Toolkit tk = Toolkit.getDefaultToolkit(); Image icone = tk.getImage("furg.gif"); status = new Label(); status.setText("FURG TV - Servidor de Streaming"); status2 = new Label(); status2.setText("STATUS: sintonizando..."); status.setForeground(Color.WHITE); status.setBackground(Color.DARK_GRAY); status2.setForeground(Color.LIGHT_GRAY); painel = new Panel(); painel.add(status); painel.add(status2); painel.setBackground(Color.DARK_GRAY); painel.setForeground(Color.WHITE); f = new JFrame(); f.setBackground(Color.DARK_GRAY); f.setForeground(Color.WHITE); //f.setIconImage(img); f.setSize(300,100); 95 f.add(painel); f.setTitle("Servidor de Streaming - FURG TV"); f.setDefaultCloseOperation(f.EXIT_ON_CLOSE); f.setIconImage(icone); f.show(); this.locator = locator; this.IP = ip; this.t = Integer.valueOf(TTL).intValue(); this.porta = Integer.valueOf(pb).intValue(); /** Inicia a transmissão*/ String result = start(); /** result será não-nulo se aconteceu um erro.*/ /** result será uma string caso tenha ocorrido um erro.*/ if (result != null) { System.err.println(result); System.exit(0); } System.out.println("============ INÍCIO DE TRANSMISSÃO =============="); try { Float h = new Float(String.valueOf(horas)); Float min = new Float(String.valueOf(minutos)); Float sec = new Float(String.valueOf(segundos)); min = min + h*60 + sec/60; System.out.println("Duração do Vídeo: "+ min + " minutos ("); min = 60000*min; //transformando minutos em milisegundos Integer t = new Integer(min.intValue()); //passando float para inteiro int tempo = t.intValue(); System.out.print(tempo + "milisegundos)"); Thread.currentThread().sleep(tempo); } catch (InterruptedException ie) {ie.printStackTrace();} //DADOS ESTATISTICOS System.out.println("Bytes Recebidos - " + rtpMgrs[0].getGlobalReceptionStats().getBytesRecd()); System.out.println("Bytes Transmitidos - " + rtpMgrs[0].getGlobalTransmissionStats().getBytesSent()); System.out.println("Pacotes RTCP recebidos - " + rtpMgrs[0].getGlobalReceptionStats().getRTCPRecd()); System.out.println("Pacotes RTCP transmitidos - " + rtpMgrs[0].getGlobalTransmissionStats().getRTCPSent()); System.out.println("Pacotes SR recebidos - " + rtpMgrs[0].getGlobalReceptionStats().getSRRecd()); System.out.println("Pacotes RTP transmitidos - " + rtpMgrs[0].getGlobalTransmissionStats().getRTPSent()); System.out.println("Pacotes em loop - " + rtpMgrs[0].getGlobalReceptionStats().getPacketsLooped()); System.out.println("Falhas na Transmissão - " + rtpMgrs[0].getGlobalReceptionStats().getTransmitFailed()); System.out.println("Pacotes Bye mal formados - " + rtpMgrs[0].getGlobalReceptionStats().getMalformedBye()); System.out.println("Pacotes RR mal formados - " + rtpMgrs[0].getGlobalReceptionStats().getMalformedRR()); System.out.println("Pacotes SDES mal formados - " + rtpMgrs[0].getGlobalReceptionStats().getMalformedSDES()); System.out.println("Pacotes SR mal formados - " + rtpMgrs[0].getGlobalReceptionStats().getMalformedSR()); /** Finaliza Transmissão */ stop(); 96 System.out.println("============= FIM DE TRANSMISSÃO ==============="); System.exit(0); } /** INICIA A TRANSMISSAO */ /** RETORNA NULL SE NÃO HOUVERAM ERROS, SENÃO RETORNA UMA STRING COM ERRO.*/ public synchronized String start() { /** result => SAIDA DE ERRO */ String result; /** CRIA UM processor PARA O ARQUIVO ESPECIFICADO NO media locator * E SETA A SAIDA PARA JPEG/RTP */ result = createProcessor(); if (result != null) {return result;} /** CRIA UMA SESSAO RTP PARA TRANSMITIR A SAIDA DO processor PARA O IP E PORTA */ result = createTransmitter(); if (result != null) { processor.close(); processor = null; return result; } /** INICIA A TRANSMISSAO = start()*/ processor.start(); status2.setText("STATUS: Transmitindo...."); /** SE TRANSMISSAO BEM SUSCEDIDA, ENTAO RETORNA NULL */ return null; } /** FINALIZA A TRANSMISSAO */ public void stop() { status2.setText("STATUS: Fim de transmissão!"); status2.repaint(); repaint(); try { Thread.sleep(10000); } catch (InterruptedException ex) { ex.printStackTrace(); } synchronized (this) { if (processor != null) { processor.stop(); processor.close(); processor = null; for (int i = 0; i < rtpMgrs.length; i++) { rtpMgrs[i].removeTargets("STATUS: Sessão terminada."); rtpMgrs[i].dispose(); } } } } /** CRIA PROCESSADOR */ 97 private String createProcessor() { if (locator == null) return "ERRO: locator é null!"; DataSource ds; DataSource clone; try {ds = javax.media.Manager.createDataSource(locator);} catch (Exception e) {return "ERRO: Não foi possível criar o DataSource";} /** CRIA UM processor PARA TRATAR A ENTRADA DO media locator */ try {processor = javax.media.Manager.createProcessor(ds);} catch (NoProcessorException npe) {return "ERRO: Não foi possível criar o processor";} catch (IOException ioe) {return "ERRO: Ocorreu um IOException durante a criação do processor";} /** ESPERA QUE TERMINE A CONFIGURAÇÃO */ boolean result = waitForState(processor, Processor.Configured); if (result == false) return "ERRO: Não foi possível configurar o processor"; /** PEGA AS TRILHAS DO processor */ TrackControl [] tracks = processor.getTrackControls(); /** TEM PELO MENOS UMA TRILHA? */ if (tracks == null || tracks.length < 1) return "ERRO: Não foram encontradas trilhas no processor"; /** SETA A SAIDA DO descriptor PARA RAW_RTP ISTO IRÁ LIMITAR OS FORMATOS SUPORTADOS QUE SERÃO REPORTADOS PELO Track.getSupportedFormats PARA APENAS FORMATOS VALIDOS RTP.*/ ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP); processor.setContentDescriptor(cd); Format supported[]; Format chosen; boolean atLeastOneTrack = false; /** PROGRAMA AS TRILHAS.*/ for (int i = 0; i < tracks.length; i++) { Format format = tracks[i].getFormat(); if (tracks[i].isEnabled()) { supported = tracks[i].getSupportedFormats(); if (supported.length > 0) { if (supported[0] instanceof VideoFormat) { /** CHECA O TAMANHO PORQUE NEM TODOS OS FORMATOS FUNCIONAM COM TODOS OS TAMANHOS*/ chosen = checkForVideoSizes(tracks[i].getFormat(), supported[0]); } else chosen = supported[0]; tracks[i].setFormat(chosen); System.out.println("Trilha " + i + " é setado para transmitir: " + chosen); atLeastOneTrack = true; } else tracks[i].setEnabled(false); 98 } else tracks[i].setEnabled(false); } if (!atLeastOneTrack) { //status2.setText("ERRO: Nenhuma trilha pôde ser setada para um formato RTP válido"); return "ERRO: Nenhuma trilha pôde ser setada para um formato RTP válido"; } /** CRIA UM FLUXO GRAFICO E TENTA CRIAR UM datasource DE SAIDA JPEG/RTP */ result = waitForState(processor, Controller.Realized); if (result == false) return "ERRO: Não foi possivel criar saida no processor"; /** seta a qualidade JPEG Set the JPEG quality to 0.5. */ setJPEGQuality(processor, 0.5f); /** pega a saida do datasource do processor */ dataOutput = processor.getDataOutput(); //System.out.println(dataOutput.getContentType()); return null; } /** USA O RTPManager PARA CRIAR UMA SESSAO PARA CADA MÍDIA (AUDIO E VIDEO) */ private String createTransmitter() { PushBufferDataSource pbds = (PushBufferDataSource)dataOutput; PushBufferStream pbss[] = pbds.getStreams(); rtpMgrs = new RTPManager[pbss.length]; SendStream sendStream; int port; SourceDescription srcDesList[]; for (int i = 0; i < pbss.length; i++) { try { rtpMgrs[i] = RTPManager.newInstance(); port = porta + 2*i; IP = IP.trim(); /** INICIALIZA O RTPManager COM O RTPSocketAdapter */ rtpMgrs[i].initialize(new Conexao(IP,port,t)); status.setText("Sessão RTP criada: IP - " + IP); System.out.println("Sessão RTP criada: IP - " + IP + " , Porta - " + port); sendStream = rtpMgrs[i].createSendStream(dataOutput, i); sendStream.start(); } catch (Exception e) {return e.getMessage();} } return null; } /** JPEG e H263, apenas funcionam para determinados tamanhos (verifica se tamanhos estão corretos novamente) */ Format checkForVideoSizes(Format original, Format supported) { 99 int width, height; Dimension size = ((VideoFormat)original).getSize(); Format jpegFmt = new Format(VideoFormat.JPEG_RTP); Format h263Fmt = new Format(VideoFormat.H263_RTP); if (supported.matches(jpegFmt)) { /** Para JPEG, a largura e a altura devem ser divisiveis por 8 */ width = (size.width % 8 == 0 ? size.width : (int)(size.width / 8) * 8); height = (size.height % 8 == 0 ? size.height : (int)(size.height / 8) * 8); } else /** Para H.263, apenas alguns tamanhos são suportados */ if (supported.matches(h263Fmt)) { if (size.width < 128) { width = 128; height = 96; } else if (size.width < 176) { width = 176; height = 144; } else if (size.width > 400) { width = 800; height = 600; } else { width = 352; height = 288; } } else { return supported; } return (new VideoFormat(null, new Dimension(width, height), Format.NOT_SPECIFIED, null, Format.NOT_SPECIFIED)).intersects(supported); } /** SETANDO A QUALIDADE DE CODIFICAÇÃO PARA 0.5 OU OUTRO VALOR (JPEG encoder) */ void setJPEGQuality(Player p, float val) { Control cs[] = p.getControls(); QualityControl qc = null; VideoFormat jpegFmt = new VideoFormat(VideoFormat.JPEG); /** PROCURA NOS CONTROLES A QUALIDADE PARA O JPEG encoder */ for (int i = 0; i < cs.length; i++) { if (cs[i] instanceof QualityControl && cs[i] instanceof Owned) { Object owner = ((Owned)cs[i]).getOwner(); /** CHECA O CODEC E O FORMATO DE SAIDA */ if (owner instanceof Codec) { 100 Format fmts[] = ((Codec)owner).getSupportedOutputFormats(null); for (int j = 0; j < fmts.length; j++) { if (fmts[j].matches(jpegFmt)) { qc = (QualityControl)cs[i]; qc.setQuality(val); System.out.println("Setando qualidade para " + val + " no " + qc); break; } } } if (qc != null) break; } } } /** METODOS PARA TRATAR OS ESTADOS DO PROCESSADOR */ private Integer stateLock = new Integer(0); private boolean failed = false; Integer getStateLock() {return stateLock;} void setFailed() {failed = true;} private synchronized boolean waitForState(Processor p, int state) { p.addControllerListener(new StateListener()); failed = false; /** CHAMA O METODO PEDIDO NO processor*/ if (state == Processor.Configured) {p.configure();} else if (state == Processor.Realized) {p.realize();} /** ESPERA ATE QUE CHEGUE UM EVENTO CONFIRMANDO O STATUS DO METODO (olhar classe StateListener inner)*/ while (p.getState() < state && !failed) { synchronized (getStateLock()) { try {getStateLock().wait();} catch (InterruptedException ie) {return false;} } } if (failed) return false; else return true; } class StateListener implements ControllerListener { public void controllerUpdate(ControllerEvent ce) { /** SE OCORREU UM ERRO DURANTE A CONFIGURAÇÃO OU REALIZAÇÃO, O processor É FECHADO.*/ if (ce instanceof ControllerClosedEvent) setFailed(); /** TODOS OS EVENTOS DO controller ENVIAM UMA NOTIFICAÇÃO PARA A THREAD QUE ESPERA NO METODO waitForState*/ if (ce instanceof ControllerEvent) { synchronized (getStateLock()) { getStateLock().notifyAll(); } 101 } } } } Conexao.java /** PACOTE PRINCIPAL*/ package transmissor_furgtv; //=================== BIBLIOTECAS =============================\\ /** E/S */ import java.io.IOException; /** REDE */ import java.net.*; /** RTP */ import javax.media.protocol.DataSource; import javax.media.protocol.PushSourceStream; import javax.media.protocol.ContentDescriptor; import javax.media.protocol.SourceTransferHandler; import javax.media.rtp.RTPConnector; import javax.media.rtp.OutputDataStream; /** IMPLEMENTAÇÃO DE UM RTPConnector BASEADO EM SOCKETS UDP */ public class Conexao implements RTPConnector { /** Socket Multicast de Dados (RTP) para trilha 0*/ MulticastSocket dataSock0; /** Socket Multicast de Dados (RTP) para trilha 1*/ MulticastSocket dataSock1; /** Endereço do Grupo Multicast */ InetAddress addr; /** Porta */ int port; /** Sockets de Entrada */ SockInputStream dataInStrm0 = null, dataInStrm1 = null; /** Sockets de Saida */ SockOutputStream dataOutStrm0 = null, dataOutStrm1 = null; /** Construtor da Classe Conexao */ public Conexao(String endereco, int porta, int TTL) throws IOException { try { /** cria socket multicast de dados*/ dataSock0 = new MulticastSocket(porta); /** cria socket multicast de dados*/ dataSock1 = new MulticastSocket(porta+2); /** cria grupo multicast através do endereço passado como parâmetro*/ this.addr = InetAddress.getByName(endereco); 102 /** se inscreve no grupo multicast da trilha 0*/ dataSock0.joinGroup(this.addr); dataSock0.setTimeToLive(TTL); /** se inscreve no grupo multicast da trilha 1*/ dataSock1.joinGroup(this.addr); dataSock1.setTimeToLive(TTL); } catch (SocketException e) {e.printStackTrace();System.exit(-1);} this.port = porta; } /** RETORNA UM input stream PARA RECEBER DADOS RTP da trilha 0*/ public PushSourceStream getDataInputStream() throws IOException { if (dataInStrm0 == null) { dataInStrm0 = new SockInputStream(dataSock0, addr, port); dataInStrm0.start(); } return dataInStrm0; } /** RETORNA UM output stream PARA ENVIAR DADOS RTP da trilha 0*/ public OutputDataStream getDataOutputStream() throws IOException { if (dataOutStrm0 == null) dataOutStrm0 = new SockOutputStream(dataSock0, addr, port); return dataOutStrm0; } /** RETORNA UM input stream PARA RECEBER DADOS RTP da trilha 1*/ public PushSourceStream getControlInputStream() throws IOException { if (dataInStrm1 == null) { dataInStrm1 = new SockInputStream(dataSock1, addr, port+2); dataInStrm1.start(); } return dataInStrm1; } /** RETORNA UM output stream PARA ENVIAR DADOS RTP da trilha 1 */ public OutputDataStream getControlOutputStream() throws IOException { if (dataOutStrm1 == null) dataOutStrm1 = new SockOutputStream(dataSock1, addr, port+2); return dataOutStrm1; } /** FECHA TODOS OS STREAMS RTP da trilha 0 e da trilha 1 */ public void close() { if (dataInStrm0 != null) dataInStrm0.kill(); if (dataInStrm1 != null) dataInStrm1.kill(); dataSock0.close(); dataSock1.close(); } 103 /** SETA O TAMANHO DO BUFFER DE RECEPÇÃO DO CANAL DE DADOS RTP. */ public void setReceiveBufferSize( int size) throws IOException {dataSock0.setReceiveBufferSize(size); } /** RETORNA O TAMANHO DO BUFFER DE RECEPÇÃO DO CANAL DE DADOS */ public int getReceiveBufferSize() { try {return dataSock0.getReceiveBufferSize();} catch (Exception e) {return -1;} } /** SETA O TAMANHO DO BUFFER DE TRANSMISSÃO DO CANAL DE DADOS RTP. */ public void setSendBufferSize( int size) throws IOException {dataSock0.setSendBufferSize(size);} /** RETORNA O TAMANHO DO BUFFER DE TRANSMISSÃO DO CANAL DE DADOS */ public int getSendBufferSize() { try {return dataSock0.getSendBufferSize();} catch (Exception e) {return -1;} } /** RETORNA A LARGURA DE BANDA DESTINADA AO RTCP. UTILIZADO PARA INICIALIZAR O RTPManager. */ public double getRTCPBandwidthFraction() {return -1;} /** RETORNA A LARGURA DE BANDA DESTINADA AO TRANSMISSOR RTCP. UTILIZADO PARA INICIALIZAR O RTPManager. */ public double getRTCPSenderBandwidthFraction() {return -1;} /** Uma classe inner para implementar um OutputDataStream baseado em Sockets UDP */ class SockOutputStream implements OutputDataStream { DatagramSocket sock; InetAddress addr; int port; /** cria socket de saída */ public SockOutputStream(DatagramSocket sock, InetAddress addr, int port) { this.sock = sock; this.addr = addr; this.port = port; } /**envia pacote*/ public int write(byte data[], int offset, int len) { try {sock.send(new DatagramPacket(data, offset, len, addr, port));} catch (Exception e) {return -1;} return len; } } /** uma classe inner para implementar um PushSourceStream baseado em Sockets UDP.*/ class SockInputStream extends Thread implements PushSourceStream { DatagramSocket sock; InetAddress addr; int port; boolean done = false; boolean dataRead = false; SourceTransferHandler sth = null; 104 /** cria socket de entrada */ public SockInputStream(DatagramSocket sock, InetAddress addr, int port) { this.sock = sock; this.addr = addr; this.port = port; } /** Lê pacote */ public int read(byte buffer[], int offset, int length) { DatagramPacket p = new DatagramPacket(buffer, offset, length, addr, port); try {sock.receive(p);} catch (IOException e) {return -1;} synchronized (this) { dataRead = true; notify(); } return p.getLength(); } public synchronized void start() { super.start(); if (sth != null) { dataRead = true; notify(); } } public synchronized void kill() { done = true; notify(); } public int getMinimumTransferSize() { return 2 * 1024; } public synchronized void setTransferHandler(SourceTransferHandler sth) { this.sth = sth; dataRead = true; notify(); } public ContentDescriptor getContentDescriptor() {return null;} public long getContentLength() {return LENGTH_UNKNOWN;} public boolean endOfStream() {return false;} public Object[] getControls() {return new Object[0];} public Object getControl(String type) {return null;} /** Procura e notifica o tratador de transferência de novos dados*/ public void run() { while (!done) { synchronized (this) { while (!dataRead && !done) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}} dataRead = false; 105 } if (sth != null && !done) {sth.transferData(this);} } } } } tempo.java /** PACOTE PRINCIPAL*/ package transmissor_furgtv; import java.awt.Image; import java.awt.Toolkit; import javax.media.MediaLocator; public class tempo extends javax.swing.JFrame { private MediaLocator video; private String IP; private String Porta; private String TTL; public tempo(MediaLocator v, String i, String p, String t) { //Inicia componentes gráficos (janela, botão, etc..) initComponents(); //Seta o icone da furg na aplicação Toolkit tk = Toolkit.getDefaultToolkit(); img = tk.getImage("furg.gif"); this.setIconImage(img); //Inicializa atributos video = v; IP = i; Porta = p; TTL = t; } private void initComponents() //Inicia componentes gráficos { jPanel1 = new javax.swing.JPanel(); jTextField1 = new javax.swing.JTextField(); jTextField2 = new javax.swing.JTextField(); jTextField3 = new javax.swing.JTextField(); jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jButton1 = new javax.swing.JButton(); jButton2 = new javax.swing.JButton(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setTitle("Temporiza\u00e7\u00e3o"); jPanel1.setBackground(new java.awt.Color(0, 51, 102)); jPanel1.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0))); jPanel1.setToolTipText("Insira a dura\u00e7\u00e3o do v\u00eddeo em quest\u00e3o"); jLabel1.setFont(new java.awt.Font("Tahoma", 1, 11)); jLabel1.setForeground(new java.awt.Color(255, 255, 255)); jLabel1.setText("Horas"); jLabel2.setFont(new java.awt.Font("Tahoma", 1, 11)); jLabel2.setForeground(new java.awt.Color(255, 255, 255)); 106 jLabel2.setText("Minutos"); jLabel3.setFont(new java.awt.Font("Tahoma", 1, 11)); jLabel3.setForeground(new java.awt.Color(255, 255, 255)); jLabel3.setText("Segundos"); jButton1.setText("OK"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } }); jButton2.setText("Cancelar"); jButton2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton2ActionPerformed(evt); } }); javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addContainerGap(28, Short.MAX_VALUE) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jLabel1) .addComponent(jLabel2) .addComponent(jLabel3)) .addGap(29, 29, 29) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(jTextField2) .addComponent(jTextField1) .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, 62, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(48, 48, 48)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() .addComponent(jButton2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jButton1) .addGap(31, 31, 31)))) ); jPanel1Layout.setVerticalGroup( jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addGap(26, 26, 26) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel1)) .addGap(18, 18, 18) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel2)) .addGap(18, 18, 18) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) 107 .addComponent(jLabel3)) .addGap(49, 49, 49) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jButton1) .addComponent(jButton2)) .addContainerGap(21, Short.MAX_VALUE)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) ); pack(); } private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {System.exit(0);} private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { String horas, minutos, segundos; horas = jTextField1.getText(); minutos = jTextField2.getText(); segundos = jTextField3.getText(); this.setVisible(false); // Cria objeto Transmissor para começar a transmissão Transmissor at = new Transmissor(horas, minutos, segundos, video, IP, Porta, TTL,null); } private javax.swing.JButton jButton1; private javax.swing.JButton jButton2; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JPanel jPanel1; private javax.swing.JTextField jTextField1; private javax.swing.JTextField jTextField2; private javax.swing.JTextField jTextField3; private Image img; } ANEXO 3: MÓDULO RECEPTOR Main.java /** PACOTE PRINCIPAL*/ package receptor_furgtv; /** Pacote Applet */ import java.applet.Applet; /** PROGRAMA PRINCIPAL */ public class Main { public Main() {} /** APPLET FURGTV */ 108 public static void main(String argv[]) { /** cria o applet */ Applet principal = new PlayerApplet(); /** inicializa o applet através de init() e start(): nessa ordem. */ principal.init(); } } Receptor.java /** PACOTE PRINCIPAL */ package receptor_furgtv; /** BIBLIOTECAS */ /** E/S */ import java.io.*; import java.io.FileReader; /** GRAFICO */ import java.awt.*; import java.awt.event.*; /** REDE */ import java.net.*; /** UTILIDADES */ import java.util.Vector; import java.util.Properties; /** MIDIA E RTP */ import javax.media.*; import javax.media.rtp.*; import javax.media.rtp.event.*; import javax.media.rtp.rtcp.*; import javax.media.protocol.*; import javax.media.protocol.DataSource; import javax.media.format.AudioFormat; import javax.media.format.VideoFormat; import javax.media.Format; import javax.media.format.FormatChangeEvent; import javax.media.control.BufferControl; /** Applet */ import java.applet.Applet; /** Lang */ import java.lang.String; /** Swing */ import javax.swing.*; /** Applet Player Multimidia */ public class PlayerApplet extends Applet implements ReceiveStreamListener, SessionListener, ControllerListener { Player p = null; PlayerWindow pw = null; String sessions[] = null; RTPManager mgrs[] = null; Vector playerWindows = null; boolean dataReceived = false; Object dataSync = new Object(); 109 Label status; protected boolean initialize() { try { mgrs = new RTPManager[sessions.length]; playerWindows = new Vector(); SessionLabel session; /** cria sessões RTP*/ for (int i = 0; i < sessions.length; i++) { /** trata os endereços da sessão*/ try {session = new SessionLabel(sessions[i]);} catch (IllegalArgumentException e) { System.out.println("ERRO: Endereço de sessão " + sessions[i] + " é inválido!"); return false; } System.out.println(" Adicionado a Sessão RTP: " + session.addr + ", que escuta na porta: " + session.port + ", com TTL: " + session.ttl); mgrs[i] = (RTPManager) RTPManager.newInstance(); mgrs[i].addSessionListener(this); mgrs[i].addReceiveStreamListener(this); /** Inicializa o RTPManager com o RTPSocketAdapter*/ mgrs[i].initialize(new Conexao(session.addr, session.port, session.ttl)); BufferControl bc = (BufferControl)mgrs[i].getControl("javax.media.control.BufferControl"); if (bc != null)bc.setBufferLength(350); } } catch (Exception e) { status.setText("ERRO: Sintonização mal sucedida!!"); System.err.println("ERRO: Não foi possível criar a sessão RTP: ");e.printStackTrace(); return false; } /** Espera receber dados antes de continuar */ long then = System.currentTimeMillis(); long waitingPeriod = 30000; /** espera no máximo por 30 segundos */ try { synchronized (dataSync) { while (!dataReceived && System.currentTimeMillis() - then < waitingPeriod) { if (!dataReceived) { System.out.println(" Esperando dados RTP..."); status.setText("Sintonizando canal FURG TV...."); } dataSync.wait(1000); } } } catch (Exception e) {} 110 if (!dataReceived) { status.setText("ERRO: Canal fora do ar!"); System.err.println("Nenhum dado RTP foi recebido."); close(); return false; } return true; } public boolean isDone() {return playerWindows.size() == 0;} /** FECHA OS PLAYES E OS GERENTES DE SESSÃO*/ protected void close() { for (int i = 0; i < playerWindows.size(); i++) { try { ((PlayerWindow)playerWindows.elementAt(i)).close(); } catch (Exception e) {} } playerWindows.removeAllElements(); /** Fecha sessão RTP*/ for (int i = 0; i < mgrs.length; i++) { if (mgrs[i] != null) { mgrs[i].removeTargets( "Fechando sessão"); mgrs[i].dispose(); mgrs[i] = null; } } } PlayerWindow find(Player p) { for (int i = 0; i < playerWindows.size(); i++) { pw = (PlayerWindow)playerWindows.elementAt(i); pw.setTitle("FURG TV"); pw.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); status.setText("FURG TV"); Toolkit tk = Toolkit.getDefaultToolkit(); Image icone = tk.getImage("furg.gif"); pw.setIconImage(icone); if (pw.p == p) return pw; } return null; } PlayerWindow find(ReceiveStream strm) { for (int i = 0; i < playerWindows.size(); i++) { pw = (PlayerWindow)playerWindows.elementAt(i); if (pw.stream == strm) return pw; } return null; } /** SessionListener*/ 111 public synchronized void update(SessionEvent evt) { if (evt instanceof NewParticipantEvent) { Participant pa = ((NewParticipantEvent)evt).getParticipant(); System.err.println(" Um novo participante foi adicionado ao grupo: " + pa.getCNAME()); } } /** ReceiveStreamListener*/ public synchronized void update( ReceiveStreamEvent evt) { RTPManager mgr = (RTPManager)evt.getSource(); Participant participant = evt.getParticipant(); ReceiveStream stream = evt.getReceiveStream(); if (evt instanceof RemotePayloadChangeEvent) { System.err.println("Recebido um RTP PayloadChangeEvent."); System.err.println("Desculpe, não é possível tratar mudança de payload."); System.exit(0); } else if (evt instanceof NewReceiveStreamEvent) { try { stream = ((NewReceiveStreamEvent)evt).getReceiveStream(); DataSource ds = stream.getDataSource(); /** Descobre os formatos.*/ RTPControl ctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl"); if (ctl != null) { System.out.println(" Foi recebido um RTP stream: " + ctl.getFormat()); } else System.out.println(" Foi recebido um RTP stream."); if (participant == null) System.out.println(" O transmissor do stream deve ser identificado."); else { System.out.println(" O stream vem de: " + participant.getCNAME()); } /** cria um player passando um datasource para o Media Manager*/ p = javax.media.Manager.createPlayer(ds); if (p == null) return; p.addControllerListener(this); p.realize(); pw = new PlayerWindow(p, stream); playerWindows.addElement(pw); /** Notifica o intialize() que um novo stream chegou*/ synchronized (dataSync) { dataReceived = true; dataSync.notifyAll(); } } 112 catch (Exception e) { System.err.println("ERRO: NewReceiveStreamEvent exception " + e.getMessage()); return; } } else if (evt instanceof StreamMappedEvent) { if (stream != null && stream.getDataSource() != null) { DataSource ds = stream.getDataSource(); /** Descobre os formatos*/ RTPControl ctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl"); if (ctl != null) System.err.println(" " + ctl.getFormat()); System.out.println(" foi enviado por: " + participant.getCNAME()); } } else if (evt instanceof ByeEvent) { System.out.println(" Mensagem \"bye\" recebida de: " + participant.getCNAME()); pw = find(stream); if (pw != null) { pw.close(); playerWindows.removeElement(pw); } } } /** ControllerListener PARA OS PLAYERS*/ public synchronized void controllerUpdate(ControllerEvent ce) { p = (Player)ce.getSourceController(); if (p == null) return; if (ce instanceof RealizeCompleteEvent) { pw = find(p); if (pw == null) { System.err.println("ERRO: problema interno!"); System.exit(-1); } status.setText("www.furgtv.furg.br"); pw.add("South",status); pw.initialize(); pw.setVisible(true); p.start(); } if (ce instanceof ControllerErrorEvent) 113 { p.removeControllerListener(this); pw = find(p); if (pw != null) { pw.close(); playerWindows.removeElement(pw); } System.err.println("ERRO: " + ce); } } /** CLASSE QUE TRATA A STRING DA SESSAO */ class SessionLabel { public String addr = null; public int port; public int ttl = 1; SessionLabel(String session) throws IllegalArgumentException { int off; String portStr = null, ttlStr = null; if (session != null && session.length() > 0) { while (session.length() > 1 && session.charAt(0) == '/') session = session.substring(1); // se tem um endereço off = session.indexOf('/'); if (off == -1) { if (!session.equals("")) addr = session; } else { addr = session.substring(0, off); session = session.substring(off + 1); // se tem uma porta off = session.indexOf('/'); if (off == -1) { if (!session.equals("")) portStr = session; } else { portStr = session.substring(0, off); session = session.substring(off + 1); // se tem um TTL off = session.indexOf('/'); if (off == -1) { if (!session.equals("")) ttlStr = session; 114 } else { ttlStr = session.substring(0, off); } } } } if (addr == null) throw new IllegalArgumentException(); if (portStr != null) { try { Integer integer = Integer.valueOf(portStr); if (integer != null) port = integer.intValue(); } catch (Throwable t) { throw new IllegalArgumentException(); } } else throw new IllegalArgumentException(); if (ttlStr != null) { try { Integer integer = Integer.valueOf(ttlStr); if (integer != null) ttl = integer.intValue(); } catch (Throwable t) { throw new IllegalArgumentException(); } } } } /** CLASSES VISUAIS GUI PARA O PLAYER*/ class PlayerWindow extends JFrame { ReceiveStream stream; private Player p; PlayerWindow(Player player, ReceiveStream strm) { p = player; stream = strm; } public void initialize() {add(new PlayerPanel(p));} public void close() { p.close(); setVisible(false); 115 dispose(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void addNotify() { super.addNotify(); pack(); } } /** CLASSES VISUAIS PARA O PLAYER*/ class PlayerPanel extends JPanel { Component vc, cc; PlayerPanel(Player p) { setLayout(new BorderLayout()); if ((vc = p.getVisualComponent()) != null) add("Center", vc); if ((cc = p.getControlPanelComponent()) != null) add("South", cc); } public Dimension getPreferredSize() { int w = 0, h = 0; if (vc != null) { Dimension size = vc.getPreferredSize(); w = size.width; h = size.height; } if (cc != null) { Dimension size = cc.getPreferredSize(); if (w == 0) w = size.width; h += size.height; } if (w < 160) w = 160; return new Dimension(w, h); } } /** Inicia o Applet */ public void init() { status = new Label("Inicialização de Cliente FURG TV", Label.CENTER); status.setForeground(Color.DARK_GRAY); add(status); setSize(300,300); show(); String sessao[] = new String[2]; BufferedReader arq; char[] linha = new char[100], protocolo = new char[15], porta = new char[15], ip = new char[15], ttl = new char[15]; try { arq = new BufferedReader(new FileReader("C:/parametros.txt")); arq.read(linha); /** le parametros do arquivo parametros.txt */ int x = 0, y ; y = 0; while ((linha[x] != ' ')&&(y!=15)) {protocolo[y] = linha[x]; x++; y++;} x++; y = 0; while ((linha[x] != ' ')&&(y!=15)) {porta[y] = linha[x]; x++; y++;} x++; y = 0; while ((linha[x] != ' ')&&(y!=15)) {ip[y] = linha[x]; x++; y++;} x++; y = 0; while ((linha[x] != ' ')&&(y!=15)) {ttl[y] = linha[x];x++; y++; } arq.close(); /** fecha arquivo */ } catch (Exception ex) {ex.printStackTrace();} String IP = String.valueOf(ip).trim(); String TTL = String.valueOf(ttl).trim(); String PORTA = String.valueOf(porta).trim(); sessao[0] = new String(IP+"/"+PORTA+"/"+TTL); 116 Integer p = Integer.valueOf(PORTA).intValue();p = p + 2;PORTA = String.valueOf(p); sessao[1] = new String(IP+"/"+PORTA+"/"+TTL); this.sessions = sessao; if (!initialize()) { System.err.println("ERRO: Sessões não inicializadas!"); System.exit(-1); } System.out.println("====== Cliente Sintonizado na FURGTV ====="); // Checa se acabou a recepção try {while (!isDone()) Thread.sleep(2000000000);} catch (Exception e) {e.printStackTrace();} System.out.println("============= FIM DE PROGRAMA ============"); System.exit(0); } } 117