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
Download

Monografia da Engenharia de Computação 2007 - NCC