Вы находитесь на странице: 1из 141

Guilherme Maciel Ferreira

Implementação de um Codificador de Vídeo H.264/AVC em Java

Florianópolis
2009
i

Universidade Federal de Santa Catarina

Bacharelado em Ciências da Computação

Implementação de um Codificador de Vídeo H.264/AVC em Java

Trabalho de Conclusão de Curso


submetido à Universidade Federal de
Santa Catarina como parte dos requisitos
para a obtenção do grau de Bacharel em
Ciências da Computação.

Guilherme Maciel Ferreira

Florianópolis, Outubro de 2009


i

Implementação de um Codificador de Vídeo H.264/AVC em Java


Guilherme Maciel Ferreira

Bacharelado em Ciências da Computação

Prof. Cristian Koliver, Dr.


Orientador

Prof. Roberto Willrich, Dr.


Prof. Responsável

Banca examinadora:

Prof. Roberto Willrich, Dr.

Prof. Cristian Koliver, Dr.

Mateus K. Ludwich, Ba.


i

Dedicatória

Dedico este trabalho em memória dos meus falecidos pais, principalmente a minha mãe,
Amélia Maciel Ferreira (1950-2007), que sempre me apoiou em meus projetos e me ensinou que
perseverança, acima de tudo, é o que nos leva a alcançar nossos objetivos.
v

Agradecimentos

Agradeço primeiramente aos meus pais, que com muito esforço deram uma educação de
qualidade aos meus irmãos e a mim.
Um agradecimento especial ao meu orientador, Prof. Cristian Koliver, que sempre foi muito
atencioso, dando feedback sobre os resultados do trabalho. Ao Prof. Jean-Marie Farines, que me
incentivou a dar continuidade ao projeto durante o primeiro ano de desenvolvimento. E ao Prof.
Willrich que por muitas vezes fez a ponte entre o Departamento de Automação e Sistemas (DAS) e o
Departamento de Informática e Estatística (INE).
Também tenho que agradecer a sociedade brasileira como um todo, pois foram verbas
públicas que me subsidiaram durante esses anos de estudo. Obrigado Brasilllll!
v

Resumo

Orientador: Prof. Cristian Koliver


Área de Concentração: Processamento Digital de Imagens
Palavras-chave: Codificação e compressão de vídeo, padrão H.264, C, Java, Java Media Framework
(JMF), CODEC, Orientação a objetos.

Este trabalho de conclusão descreve o projeto e implementação de um codificador de vídeo


(encoder) no padrão H.264 usando a linguagem de programação Java. A escolha desse padrão para
implementação deveu-se à inexistência de implementações do H.264 independentes de plataforma e
ao crescente interesse nesse padrão uma vez que ele vem sendo usado como substituto ao MPEG-
2, devido à sua maior eficiência na compactação de vídeo, em termos de taxa de compressão e
qualidade obtida.
Uma vez que o código fonte usado como base para o desenvolvimento deste projeto foi
originalmente escrito em linguagem C, este trabalho também descreve as modificações na arquitetura
do codificador original, baseado em um modelo de arquitetura estruturada, para um modelo orientado
a objetos. Tal modelo poderá ser utilizado como base para novas implementações do codificador
H.264 ou mesmo de outros padrões.
Por fim, neste trabalho é realizada uma comparação do desempenho da implementação do
codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros
como velocidade e consumo de memória, dentre outros , sendo avaliada a a viabilidade do uso deste
codificador para aplicações multimídia interativas, como videofone.
Por ser um trabalho de implementação, há um grande nível de detalhamento em relação ao
funcionamento do codificador H.264. Alguns detalhes presentes neste trabalho não estão disponíveis
em nenhum material de referência.
v

Abstract

Adviser: Prof. Cristian Koliver


Concentration area: Digital Image Processing
Key-words: Video coding and compression, H.264 standard, C, Java, Java Media Framework (JMF),
CODEC, Object orientation.

This graduation conclusion work describe the project and implementation of a video encoder
in H.264 standard using the Java programming language. The choice of this standard to implement is
given by the nonexistence of platform independent H.264 implementations and the rising interest in
this standard, once its becoming a replacement for MPEG-2 due its greater video encoding efficiency,
in terms of compression ratio and quality obtained.
Once the source code used as reference to this project development was originally written in
C language, this work also describe the architectural modifications in the original encoder, based on a
structured architecture model, to a object oriented model. This model can be used as basis to new
H.264 encoder implementations or even other standards.
Finally, in this work is performed a comparison among the C and the Java encoder
implementations performance, in parameters terms as speed and memory consumption, among
others, and assessed the feasibility of using this encoder for interactive multimedia applications, such
as videophone.
As a implementation work, there's a high level of details on the H.264 encoder operation.
Some details in this work are not available in any reference material.
v

Lista de figuras

FIGURA 2.1 – DIAGRAMA DO CODIFICADOR DE VÍDEO [1]............................................................3

FIGURA 2.2 – REPRESENTAÇÃO TRIDIMENSIONAL DA TRANSFORMAÇÃO DISCRETA DE


COSSENOS (DCT). ANTES DA TRANSFORMADA (ESQUERDA) E DEPOIS DA
TRANSFORMAÇÃO (DIREITA).............................................................................................................5

FIGURA 2.3 – MODELO DE ENCADEAMENTO DOS PROCESSOS NO CODIFICADOR PADRÃO


H.264 [4]...............................................................................................................................................12

FIGURA 3.1 – ESTRUTURA DE UM PROCESSADOR COM OS ESTÁGIOS DE


PROCESSAMENTO.............................................................................................................................16

FIGURA 4.1 – (A) ESTRUTURA E (B) EXEMPLO DE QUADRO YUV 4:2:0 PLANAR......................19

FIGURA 5.1 – PARTIÇÃO CORRENTE (E) E PARTIÇÕES VIZINHAS (A, B, C E D)........................23

FIGURA 5.2 – ORDEM DE ESCANEAMENTO DOS BLOCOS 4X4 EM UM MACROBLOCO [1].....24

FIGURA 5.3 – QUANTIZAÇÃO DOS COEFICIENTES DA DCT [48]..................................................27

FIGURA 5.4 – REORDENAÇÃO ZIG-ZAG PARA BLOCOS 4X4 LUMA (MODO FRAME)................28

FIGURA APB.1 – DIAGRAMA DE CLASSE.....................................................................................103

FIGURA APB.2 – DIAGRAMA DE CLASSE.....................................................................................104

FIGURA APB.3 – DIAGRAMA DAS CLASSES DE MODO DE CODIFICAÇÃO..............................105

FIGURA APD.1 – PLAYER REPRODUZINDO UM ARQUIVO YUV.................................................107


i

FIGURA APD.2 – ESTATÍSTICAS DO ARQUIVO SENDO REPRODUZIDO...................................107

FIGURA APD.3 – INTERFACE GRÁFICA DO CODIFICADOR DESENVOLVIDO NESTE


TRABALHO.......................................................................................................................................108

FIGURA APD.4 – JANELA DE CONFIGURAÇÕES DO CODIFICADOR.........................................108

FIGURA ANB.1 - PASSOS PARA COMPRESSÃO DE IMAGENS USANDO O ALGORITMO JPEG


COM O MODO DE OPERAÇÃO SEQUENCIAL...............................................................................115

FIGURA ANB.2 – REPRESENTAÇÃO TRIDIMENSIONAL DA TRANSFORMAÇÃO DCT: ANTES


DA TRANSFORMAÇÃO (ESQUERDA); DEPOIS DA TRANSFORMAÇÃO (DIREITA)...................116

FIGURA ANB.4 – EXPLORAÇÃO DA CORRELAÇÃO TEMPORAL USANDO O ALGORITMO


MPEG-1..............................................................................................................................................119
Lista de tabelas

TABELA 4.1 – RESUMO DAS ESPECIFICAÇÕES DO PROJETO PARA VÍDEO DE ENTRADA....18

TABELA 4.2 – RESUMO DAS ESPECIFICAÇÕES DO PROJETO PARA VÍDEO DE SAÍDA...........19

TABELA 5.1 – OITO PRIMEIROS CÓDIGOS EXP_GOLOMB PARA UE(V).....................................29

TABELA 5.2 – OITO PRIMEIROS CÓDIGOS EXP_GOLOMB PARA SE(V)......................................30

TABELA 5.3 – EXEMPLO DE PARÂMETROS A SEREM CODIFICADOS........................................30

TABELA APC.1 – MÓDULOS DO CÓDIGO DE REFERÊNCIA.......................................................106

TABELA ANA.1 – PERFIS DO MPEG-4 PARTE 10 (H.264)............................................................109

TABELA ANA.2 – NÍVEIS DO MPEG-4 PARTE 10 (H.264)..............................................................110


Sumário

ACRÔNIMOS......................................................................................................................................XV

GLOSSÁRIO......................................................................................................................................XVI

CAPÍTULO 1: INTRODUÇÃO E MOTIVAÇÃO......................................................................................1

1.1 MOTIVAÇÕES.........................................................................................................................................1
1.2 ORGANIZAÇÃO DO DOCUMENTO..................................................................................................................2

CAPÍTULO 2: CODIFICAÇÃO DE VÍDEO H.264..................................................................................3

2.1 PASSOS NA CODIFICAÇÃO DE VÍDEO PADRÃO PELO H.264.............................................................................3


2.1.1 PRÉ-PROCESSAMENTO.........................................................................................................................3
2.1.2 MODELO TEMPORAL.............................................................................................................................4
2.1.2.1 Predição Inter Quadro................................................................................................................4
2.1.3 MODELO ESPACIAL..............................................................................................................................4
2.1.3.1 Predição Intra Quadro................................................................................................................4
2.1.3.2 Transformada.............................................................................................................................4
2.1.3.3 Quantização................................................................................................................................6
2.1.4 CODIFICAÇÃO POR ENTROPIA.................................................................................................................6
2.2 MELHORIAS ADOTADAS NO PADRÃO H.264..................................................................................................6
2.2.1 MUDANÇAS ESSENCIAIS.........................................................................................................................6
2.2.1.1 Mudança na unidade espacial....................................................................................................7
2.2.2 MELHORIAS NO MODELO TEMPORAL........................................................................................................7
2.2.2.1 Compensação de Movimento com Blocos de Tamanho Variável (VBSMC)...............................7
2.2.2.2 Compensação de Movimento com amostras precisão de ¼ de pixel (Qpel)..............................7
2.2.2.3 Vetores de movimento além dos limites do quadro....................................................................7
2.2.2.4 Múltiplos Quadros de Referência................................................................................................8
2.2.2.5 Filtro Anti-Blocagem...................................................................................................................8
2.2.2.6 Predição com peso (Weighted Prediction)..................................................................................8
2.2.3 MELHORIAS NO MODELO ESPACIAL..........................................................................................................8
2.2.3.1 Predição espacial (predição intra quadro)..................................................................................8
2.2.3.2 Transformada com bloco de tamanho menor.............................................................................9
2.2.3.3 Transformada de bloco hierárquico............................................................................................9
2.2.3.4 Transformada com palavra de pequeno comprimento...............................................................9
2.2.3.5 Transformada inversa exata.......................................................................................................9
2.2.4 MELHORIAS NA CODIFICAÇÃO POR ENTROPIA.............................................................................................9
2.2.4.1 Context Adaptive Variable Length Coding (CAVLC).................................................................10
2.2.4.2 Context Adaptive Binary Arithmetic Coding (CABAC)..............................................................10
2.2.4.3 Exponecial Golomb Variable Length Coding (Exp-Golomb).....................................................10
2.2.5 MELHORIAS NA ROBUSTEZ E TRANSPORTE..............................................................................................10
2.2.5.1 Fatias SI e SP...........................................................................................................................11
2.2.5.2 Seqüência Flexível de Macrobloco (FMO)................................................................................11
2.2.5.3 Seqüência Arbitrária de Fatia (ASO)........................................................................................11
2.2.5.4 Fatias Redundantes (RS).........................................................................................................11
2.2.5.5 Particionamento de Dado (DP).................................................................................................11
2.3 FUNCIONAMENTO DO CODIFICADOR H.264.................................................................................................11
2.3.1 ESQUEMA........................................................................................................................................12
2.3.1.1 Caminho de codificação...........................................................................................................12
2.3.1.2 Caminho de reconstrução.........................................................................................................13

CAPÍTULO 3: CODIFICAÇÃO DE VÍDEO EM JAVA..........................................................................14

3.1 PLATAFORMA JAVA..............................................................................................................................14


3.1.1 MANIPULAÇÃO DE DADOS.....................................................................................................................14
3.2 JAVA MEDIA FRAMEWORK (JMF)...........................................................................................................15
3.2.1 HISTÓRICO.......................................................................................................................................15
3.2.2 COMPONENTES.................................................................................................................................15

CAPÍTULO 4: ESPECIFICAÇÕES DO CODIFICADOR H.264 EM JAVA...........................................18

4.1 VÍDEO DE ENTRADA...............................................................................................................................18


4.1.1 FORMATO DO QUADRO........................................................................................................................18
4.1.2 RESOLUÇÃO, TAXA DE QUADROS E TAXA DE BITS.......................................................................................19
4.2 VÍDEO DE SAÍDA...................................................................................................................................19
4.2.1PERFIS............................................................................................................................................20
4.2.1.1 Baseline....................................................................................................................................20
4.2.2 ORIENTAÇÃO DA PALAVRA DE DADO (ENDIANESS)....................................................................................20

CAPÍTULO 5: DESENVOLVIMENTO DO CODIFICADOR H.264 EM JAVA......................................21

5.1 LEITURA DOS QUADROS DO ARQUIVO YUV.................................................................................................21


5.1.1 FUNÇÃO ENCODE_ONE_FRAME.............................................................................................................21
5.1.2 FUNÇÃO READONEFRAME..................................................................................................................22
5.2 PREDIÇÃO INTRA QUADRO.....................................................................................................................22
5.2.1 I_PCM..........................................................................................................................................23
5.2.2 INTRA 16X16 LUMA..........................................................................................................................23
5.2.2 INTRA 8X8 CHROMA..........................................................................................................................25
5.3 PREDIÇÃO INTER QUADRO.....................................................................................................................25
5.4 TRANSFORMADA, QUANTIZAÇÃO E REORDENAÇÃO.......................................................................................26
5.4.1 TRANSFORMADA................................................................................................................................26
5.4.2 QUANTIZAÇÃO...................................................................................................................................26
5.4.3 REORDENAÇÃO.................................................................................................................................27
5.5 CODIFICAÇÃO POR ENTROPIA..................................................................................................................28
5.5.1 EXP-GOLOMB...................................................................................................................................29
5.5.1.1 Conceito....................................................................................................................................29
5.5.1.2 Aplicação..................................................................................................................................30
5.5.2 CAVLC.........................................................................................................................................30
5.5.2.1 Codificação do número de coeficientes diferentes de zero e 1s em carreira............................31
5.5.2.2 Codificação do sinal de cada 1 em carreira..............................................................................32
5.5.2.3 Codificação da magnitude dos coeficientes diferentes de zero remanescentes.......................32
5.5.2.4 Codificação do número de zeros antes do último coeficiente...................................................32
5.5.2.5 Codificação das seqüências de zeros antes de cada coeficiente diferente de zero.................33
5.6 GRAVAÇÃO DO ARQUIVO CODIFICADO........................................................................................................33
5.6.1 FORMATOS DE SAÍDA..........................................................................................................................33
5.6.1.1 Fluxo de bits puro.....................................................................................................................33
5.6.1.2 Container MP4..........................................................................................................................34

CAPÍTULO 6: CONCLUSÕES.............................................................................................................35

6.1 FATORES TÉCNICOS..............................................................................................................................35


6.1.1 DIFICULDADES DURANTE O DESENVOLVIMENTO...........................................................................................35
6.2 CONSIDERAÇÕES PARA O FUTURO.............................................................................................................35
6.2.1 DESEMPENHO DE ALGORITMOS..............................................................................................................35
6.2.2 TRANSPORTE E ARMAZENAMENTO...........................................................................................................36
6.2.3 PERFIS...........................................................................................................................................36
6.2.4 DECODIFICADOR................................................................................................................................36

APÊNDICE A: CÓDIGO-FONTE..........................................................................................................37

A1 CLASSES DE SUPORTE...........................................................................................................................37
A1.1 CLASSE REGISTRY.............................................................................................................................37
A2 CLASSES PARA LEITURA DO ARQUIVO DE VÍDEO YUV....................................................................................38
A2.1 CLASSE YUVPARSER........................................................................................................................38
A2.2 CLASSE YUVVIDEOTRACK..................................................................................................................41
A2.3 CLASSE YUVFORMATHANDLER............................................................................................................43
A2.4 CLASSE YUVFRAMEBUFFER...............................................................................................................44
A3 CLASSES PARA ESCRITA NO ARQUIVO H.264..............................................................................................47
A3.1 CLASSE H264MUX...........................................................................................................................47
A3.2 CLASSE NALUBYTESTREAM...............................................................................................................49
A3.3 CLASSE NALU................................................................................................................................50
A4 CLASSES DE CODIFICAÇÃO 1 – CONTROLE ................................................................................................52
A4.1 CLASSE H264ENCODER.....................................................................................................................52
A4.2 CLASSE BASELINEPROFILEFACTORY.......................................................................................................55
A5 CLASSES DE CODIFICAÇÃO 2 – ALGORITMOS..............................................................................................56
A5.1 CLASSE INTEGERTRANSFORM................................................................................................................56
A5.2 CLASSE ZIGZAGFRAMESCANNER..........................................................................................................59
A5.3 CLASSE INTEGERROUNDQUANTIZER.......................................................................................................60
A6 CLASSES DE CODIFICAÇÃO 3 – MODOS DE CODIFICAÇÃO...............................................................................64
A6.1 INTERFACE ENCODINGMODE.................................................................................................................64
A6.2 CLASSE ABSTRACTENCODINGMODE.......................................................................................................65
A6.3 CLASSE INTRA16X16ENCODINGMODE....................................................................................................66
A6.4 CLASSE IPCMENCODINGMODE...........................................................................................................69
A6.5 CLASSE INTRA16X16LUMAABSTRACTPREDICTOR......................................................................................70
A6.6 CLASSE INTRA8X8CHROMAABSTACTPREDICTOR.......................................................................................75
A6.7 CLASSE INTRA16X16LUMADCPREDICTOR..............................................................................................81
A6.8 CLASSE INTRA8X8CHROMADCPREDICTOR..............................................................................................82
A7 CLASSES PARA MEDIÇÃO DE DISTORÇÃO.....................................................................................................84
A7.1 INTERFACE DISTORTIONMETRIC.............................................................................................................84
A7.1 CLASSE SATD................................................................................................................................84
A8 CLASSES PARA CODIFICAÇÃO DE ENTROPIA.................................................................................................86
A8.1 CLASSE VLCTABLE...........................................................................................................................87
A8.2 CLASSE CAVLC..............................................................................................................................88
A9 CLASSES PARA CONTROLE DOS MACROBLOCOS VIZINHOS................................................................................96
A9.1 CLASSE MACROBLOCKACCESS..............................................................................................................96
A9.2 CLASSE MACROBLOCKACCESSNONMBAFF............................................................................................98
A9.3 CLASSE MACROBLOCKINFO..................................................................................................................99
A9.4 CLASSE MACROBLOCKPOSITION..........................................................................................................102

APÊNDICE B: DIAGRAMAS UML ....................................................................................................103

B1 CLASSES PARA ESCRITA NO ARQUIVO H.264............................................................................................103


B2 CLASSES DE CONTROLE DE CODIFICAÇÃO................................................................................................103
B3 CLASSES DE CODIFICAÇÃO....................................................................................................................104

APÊNDICE C: EQUIVALÊNCIA ENTRE OS MÓDULOS..................................................................106

APÊNDICE D: FERRAMENTAS DE SUPORTE ...............................................................................107


D1 YUVPLAYER....................................................................................................................................107
D2 AVCENCODER..................................................................................................................................108

ANEXO A: PARÂMETROS DO PADRÃO H.264 .............................................................................109

A1 PERFIS DO PADRÃO H.264..................................................................................................................109


A2 NÍVEIS DO PADRÃO H.264...................................................................................................................110

ANEXO B: ALGORITMOS DE COMPRESSÃO ...............................................................................111

B1 TIPOS DE COMPRESSÃO.......................................................................................................................111
B2 CATEGORIAS DE COMPRESSÃO..............................................................................................................111
B2.1 CODIFICAÇÃO DE ENTROPIA................................................................................................................111
B2.1.1 Supressão de sequências repetitivas......................................................................................112
B2.1.2 Codificação Estatística............................................................................................................112
B2.2 CODIFICAÇÃO DA FONTE....................................................................................................................113
B2.2.1 Codificação de Transformada.................................................................................................113
B2.2.2 Codificação Diferencial............................................................................................................114
B2.2.3 Quantização Vetorial...............................................................................................................114
B3 COMPRESSÃO DE IMAGEM.....................................................................................................................115
B3.1 O PADRÃO JPEG..........................................................................................................................115
B3.1.1 Passos da Codificação Progressiva........................................................................................115
B3.2 PADRÃO MPEG.............................................................................................................................117
B3.2.1 Quadros de Referência e Intracodificados..............................................................................118
B3.2.2 Compressão de Quadros I......................................................................................................120
B3.2.3 Compressão de Quadros P e B...............................................................................................120

REFERÊNCIAS BIBLIOGRÁFICAS..................................................................................................121
Acrônimos

AVC: Advanced Video CODEC.


CABAC: Context-based Adaptive Binary Arithmetic Coding.
CAVLC: Context-based Adaptive Variable Length Coding.
CBR: Constant Bit Rate.
CIF: Common Interchange Format. É uma resolução de vídeo medindo 352 por 288 pixels.
DCT: Discrete Cosine Transform.
DPB: Decoded Picture Buffer.
FPS: Frames per Second.
JMF: Java Media Framework.
MBAFF: Macroblock-Adaptive Frame-Field Coding.
QCIF: Quarter Common Interchange Format. Um quarto de um CIF, mede 176 por 144 pixels.
QP: Quantization Parameter. Ver Quantização.
RBSP: Raw Byte Sequence Payload.
RTP: Rapid Transport Protocol.
VBR: Variable Bit Rate.
VCL: Video Coding Layer.
VLC: Variable Length Coding.
Glossário

Artefato
Refere-se a algum tipo de distorção visual em uma imagem.

Artefato de blocagem
Tradução do termo blocking artifacts que se refere ao padrão do bloco em uma seqüência
comprimida devido à quantização individual de cada bloco, levando à discontinuidades entre os
blocos adjacentes. Artefato de blocagem é uma das mais perceptíveis distorções visuais.

CODEC
Um CODEC (Compression Decompression Algorithm) é um programa que codifica e
decodifica dados digitais com intuito de comprimir esses dados, reduzindo a quantidade de
espaço necessária para armazenar ou a largura de banda para transmiti-los.

Crominância (Chroma)
Corresponde à amostra dos dois sinais de cor (U e V). Geralmente possui uma freqüência de
amostragem menor em relação às amostras de luma.

Fatia Intra (I-Slice)


Uma fatia Intra codificada é comprimida sem fazer referência a nenhuma outra fatia em
nenhum outro quadro, anterior ou posterior, na seqüência. Essa fatia é comprimida usando
técnicas similares às empregadas na compressão de imagens estáticas, tal como as
utilizadas na compressão JPEG.

Fatia Predita (P-Slice)


Fatias P são preditas de amostras decodificadas de um quadro de referência anterior. Isso
significa que para decodificar uma fatia P em um dado instante de tempo, é necessário um
quadro de referência anterior. Fatias P podem servir como referência para predizer fatias em
outros quadros.

Framework
É um conjunto de classes que fornecem uma funcionalidade genérica comum a vários
projetos de software. Diferente das bibliotecas, o Framework quem dita o fluxo de controle da
aplicação, o que é chamado de Inversão de Controle.
Luma
Corresponde às amostras do sinal acromático (componente Y do espaço de cor YCbCr), ou
brilho. Segundo [4], luma é diferente de luminância, uma vez que esta é uma medida, definida
pelo CIE, puramente fotométrica e independente de dispositivo.

Partição
É uma região do macrobloco que possui seu próprio Vetor de Movimento.

Predição
Processo no qual os valores de uma amostra são estimados com base em uma amostra
previamente codificada.

Quadro
É o conjunto de amostras que formam uma imagem estática. Um vídeo é o conjunto de
quadros (figuras estáticas) exibidos em intervalos de tempo. No caso de vídeo entrelaçado,
um quadro é o conjunto dos campos superior e inferior, correspondentes às linhas pares e
ímpares, respectivamente.

Quadros por segundo (Frames per second)


Representa a quantidade de quadros exibidos ou processados a cada segundo.

Quadro Intra (I-Frame)


Nos padrões anteriores, o quadro quem determinava o tipo dos macroblocos. Contudo, no
padrão H.264 são as fatias quem determinam. Veja Fatia I.

Quadro Predito (P-Frame)


Nos padrões anteriores, o quadro quem determinava o tipo dos macroblocos. Contudo, no
padrão H.264 são as fatias quem determinam. Veja Fatia P.

Quantização
É a redução da informação, por meio do truncamento de números, para obter uma maior taxa
de compressão. O parâmetro de quantização QP seleciona o nível de truncamento dos
coeficientes. Quanto mais alto o QP, maior o truncamento e compressão dos dados, e
consequentemente menor a qualidade do vídeo decodificado.
1

Capítulo 1: Introdução e Motivação

O H.264 é um padrão para compressão de vídeo desenvolvido pela ITU-T Video Coding
Experts Group (VCEG) em conjunto com a ISO/IEC MPEG que formaram uma parceria conhecida por
Joint Video Team (JVT). O padrão H.264 foi baseado no padrão MPEG-4 Part 10 ou AVC (Advanced
Video Coding). Sua versão final, formalmente chamada por ISO/IEC 14496-10, foi lançada em 2003.
Posteriormente, foram desenvolvidas extensões da versão original do padrão, conhecidas por Fidelity
Range Extensions (FRExt).
O projeto do H.264/AVC foi norteado pela criação de um padrão de compressão de vídeo
capaz de fornecer boa qualidade a uma taxa de bits baixa em relação aos padrões já existentes,
como o MPEG-1, MPEG-2 e H.263. Outra meta do projeto foi a de criar um padrão que permitisse a
codificação de vídeos com diferentes taxas de bits/resolução..
Uma lacuna ainda existente em relação ao padrão H.264, é que, a despeito da existência de
várias implementações na forma de codificadores (encoders) e decodificadores/players (decoders),
ainda não há implementações do codificador independentes de plataforma (processador + sistema
operacional), o que exige novas implementações para novos dispositivos com plataformas muito
específicas (por exemplo, dispositivos móveis como celulares). É pertinente salientar que, devido às
melhorias introduzidas pelo padrão H.264 em relação ao seus antecessores, aumentou
razoavelmente a complexidade dos algoritmos utilizados, exigindo otimizações no código que
dificultam bastante a obtenção de implementações eficazes e, ao mesmo tempo, eficientes, condição
essencial para o seu uso em máquinas com recursos computacionais (capacidade de processamento
e memória, particularmente) restritos.
O desenvolvimento deste trabalho também tem como objetivo fornecer uma implementação e
um texto de fácil compreensão para estudo e desenvolvimento de outros codificadores.

1.1 Motivações

O objetivo deste trabalho é preencher a lacuna exposta acima através da implementação de


um codificador de vídeo H.264 independente de plataforma. A linguagem escolhida para tal é a
linguagem Java. Tal escolha foi motivada pelos seguintes aspectos:

1. Código de máquina independente de plataforma: muito mais que uma linguagem orientada a
objetos, Java é uma tecnologia na qual um programa Java é compilado gerando um bytecode
(código de máquina) que é executado por qualquer máquina virtual Java (JVM);
2. Diversidade de ambientes e recursos para programação: além de possuir uma gama enorme
de recursos para facilitar a programação, a máquina virtual Java é disponível em diversos
equipamentos e ambientes, como navegadores, mainframes, SOs, celulares, palmtops e
2

cartões inteligentes, entre outros. O que abre um grande leque de consumidores para os
produtos desenvolvidos nessa plataforma; e
3. Desempenho: ao longo dos anos, foram agregadas à plataforma Java diversas otimizações
que tornaram o desempenho de um programa Java próximo a um mesmo programa
codificado em C++, com código compilado “nativo”. Dentre essas otimizações, destaca-se a
compilação “especulativa”, que aproveita o tempo ocioso do processador para pré-compilar o
bytecode para código nativo. Outro mecanismo - o HotSpot da Sun - guarda informações
disponíveis somente em tempo de execução (por exemplo, número de usuários,
processamento usado, memória disponível), que possibilitam que a JVM vá "aprendendo" e
melhorando seu desempenho;

Não obstante a implementação descrita neste trabalho ter tido, como referência, uma
implementação pré-existente em C, nosso código não representa, de forma alguma, uma mera
transcodificação de C para Java, o que, per si, já seria demasiado trabalhoso, dada não só à
diferença entre os paradigmas de programação utilizados por essas linguagens, mas também a
aspectos como formato e tipos de dados e uso de chamadas de sistemas específicas de plataforma
(no caso do código em C). Nossa implementação teve como base a criação de modelo orientado a
objetos a partir do código de referência em C, estruturado de forma procedural. O projeto desse
modelo envolveu a aplicação de diversos conceitos de Engenharia de Software
.

1.2 Organização do documento

Este documento descreve os diversos aspectos relacionados à nossa implementação do


codificador H.264 em Java e é estruturado da seguinte forma: o Capítulo 2 fornece o embasamento
teórico necessário para entender o domínio do problema, abordando os conceitos fundamentais da
codificação de vídeo com ênfase nas diferenças introduzidas pelo padrão H.264. A compressão deste
capítulo parte da premissa que o leitor já conhece os fundamentos da compressão de vídeo; no
Capítulo 3 são abordadas as tecnologias utilizadas na implementação do trabalho, a saber, a
plataforma Java e a Java Media Framework; no Capítulo 4 são descritas algumas das premissas e
restrições impostas por nossa implementação. Como o padrão H.264 é muito abrangente em relação
às suas aplicações, esse capítulo é essencial para a compreensão do Capítulo 5, o qual detalha a
nossa implementação, mencionando pontos chave de seu código-fonte bem como do código-fonte de
referência;
Por fim, o Capítulo 6 apresenta algumas conclusões obtidas após o desenvolvimento do
trabalho e são mencionadas possíveis melhorias no projeto.
3

Capítulo 2: Codificação de Vídeo H.264

Em um primeiro momento, este capítulo aborda de maneira geral os conceitos gerais relativos
à codificação de vídeo. E em um segundo momento, trata sobre as melhorias e pontos principais da
codificação de vídeo especificada pelo padrão H.264. O Anexo B contém informações mais
detalhadas a respeito dos algoritmos de codificação de vídeo.
A maioria dos documentos de referência utiliza o termo figura (picture) para designar tanto
frames (amostras progressivas) quanto fields (amostras entrelaçadas). Entretanto, como o codificador
descrito neste trabalho se restringe a vídeos progressivos, utilizaremos apenas o termo quadro
(frame) para designar tanto figura quanto quadro.

2.1 Passos na Codificação de Vídeo padrão pelo H.264

Esta seção aborda de maneira geral o modo pelo qual funciona a compactação (ou
codificação) de vídeo para o formato H.264. Será adotada a mesma divisão apresentada por
Richardson [1], que consiste em separar a codificação de vídeo em três grandes blocos: modelo
temporal, modelo espacial e codificação por entropia. A Figura 2.1 mostra o diagrama do codificador.
Conforme esse diagrama, o codificador recebe como entrada um arquivo ou um stream de vídeo
gerado em tempo real na forma “crua” (raw video), no qual cada quadro é representado por matrizes
de pixels contendo os valores dos componentes Y, U e V; a saída é o vídeo codificado (comprimido).
Nas seções seguintes, são detalhados os papeis de cada bloco desse diagrama.

FIGURA 2.1 – DIAGRAMA DO CODIFICADOR DE VÍDEO [1].

2.1.1 Pré-Processamento

Antes de iniciar a compressão propriamente dita, o quadro de entrada é dividido em blocos


de 8x8 pixels (no H.264, 4x4, como veremos adiante). Seja, por exemplo, uma imagem de 640x480
pixels representada por três componentes: a luminância Y e as diferenças de cores U e V. Se a
4

relação entre esses componentes é 4:1:1, então o componente Y consiste de uma matriz 640x480 e
os outros dois consistem de matrizes 320x240. A preparação dos blocos irá fornecer para o passo
seguinte 4800 blocos para o componente Y, 1200 para U e 1200 para V.

2.1.2 Modelo Temporal

O modelo temporal tem como objetivo reduzir a redundância temporal, ou seja, porções da
imagem que se repetem por vários quadros.

2.1.2.1 Predição Inter Quadro

Predição inter quadro é o processo que consiste em estimar valores dos bloco de um quadro
baseado nos valores dos blocos de quadros previamente codificados.

2.1.3 Modelo Espacial

Este modelo implica em um conjunto de técnicas para redução da redundância espacial,


aquela presente em um mesmo quadro.

2.1.3.1 Predição Intra Quadro

A Compensação de Movimento é uma forma de predição, onde o codificador cria uma


predição a partir de uma área do quadro atual baseada em quadros de anteriores (ou posteriores) e
subtraí essa predição do quadro original para formar um resíduo. Da mesma forma, a predição Intra
Quadro consiste em criar valores residuais a partir de predições realizadas por meio de amostras do
mesmo quadro, ao invés de outros quadros.

2.1.3.2 Transformada

O processo de transformação consiste em converter os valores dos resíduos, que estão no


domínio espacial, para outro o domínio de frequência. Devido ao fato que amostras no domínio
espacial variam muito ao longo do tempo, elas não são comprimidas muito bem pelas técnicas de
entropia. Já quando as amostras são convertidas para o domínio da frequência, elas se tornam muito
mais adequadas para compressão por entropia.
A técnica mais comumente utilizada para transformadas em codificação de vídeo é a DCT
(discrete cosine transform). A transformada dos blocos ocorre componente por componente e, dentro
de um componente, da esquerda para a direita, do topo para a base, em um esquema chamado de
ordenamento não-entrelaçado. Os blocos são compostos de 64 valores que representam a amplitude
5

do sinal amostrado que é função de duas coordenadas espaciais, ou seja, a = f(x,y) onde x e y são as
duas dimensões. Após a transformação, obtém-se a função c= g(Fx,Fy) onde c é um coeficiente e Fx
e Fy são as frequências espaciais para cada direção. O resultado é outro bloco de 64 valores onde
cada valor representa um coeficiente DCT - isto é, uma determinada frequência - e não mais a
amplitude do sinal na posição amostrada (x,y). O coeficiente g(0,0) correspondente às frequências
zero, é chamado de coeficiente DC. Ele representa o valor médio das 64 amostras. Como em um
bloco representando uma porção da imagem os valores amostrados geralmente variam pouco de um
ponto para outro, os coeficientes de mais baixa frequência serão altos e os de média e alta frequência
terão valores baixos ou zero, podendo ser descartados. A energia do sinal é concentrada nas
frequências espaciais mais baixas. A Figura 2.2 [6] é uma representação tridimensional da
transformação DCT.

FIGURA 2.2 – REPRESENTAÇÃO TRIDIMENSIONAL DA TRANSFORMAÇÃO DISCRETA DE COSSENOS (DCT). ANTES DA


TRANSFORMADA (ESQUERDA) E DEPOIS DA TRANSFORMAÇÃO (DIREITA ).

Em uma imagem, os coeficientes de média e baixa frequência ocorrerão quando há uma


mudança brusca (em um desenho preto-e-branco, a mudança de uma zona totalmente branca para
um zona com uma linha preta representando parte da figura, por exemplo). Em uma imagem da
natureza, por outro lado, as transições entre as zonas da imagem são suaves.
A transformada do H.264/AVC introduz duas grandes novidades: transformada usando
apenas aritmética inteira e operando sobre blocos menores.
O código de referência [2] utiliza uma aproximação ortogonal da DCT que opera com
números inteiros [33] para evitar divisões, as quais acumulam erros. Os padrões anteriores utilizam a
transformada sob números de ponto-flutuante. O bloco sob o qual a transformada opera também
dimuniu, de 8x8 para 4x4.
6

2.1.3.3 Quantização

A quantização é basicamente uma redução na amplitude do sinal por meio de uma divisão
seguida por arredondamento. O objetivo por trás dessa redução de amplitude das amostras é
transmitir valores que ocupem menos bits. Uma vez recebidos (ou descompactados), esses valores
são redimensionados por meio de uma multiplicação, obtendo valores próximos aos originais.
A quantização é o processo que mais acumula erros durante a codificação de vídeo. Dessa
forma, o H.264/AVC possui algumas melhorias nesse processo com intuito de amenizar esses erros.

2.1.4 Codificação por Entropia

A codificação por entropia converte uma série de símbolos, que representam os elementos da
sequência de vídeo, em um fluxo de bits compactados apropriados para transmissão ou
armazenamento [1]. Símbolos de entrada podem incluir coeficientes quantizados de transformadas,
vetores de movimento (deslocamentos no eixo x e y para cada bloco com compensação de
movimento), marcadores (códigos indicando pontos de sincronização), cabeçalhos (de macroblocos,
quadros ou sequência de quadros) e informações suplementares (informações não essenciais para
correta decodificação da mídia).

2.2 Melhorias adotadas no padrão H.264

Em 1998, a proposta do padrão H.264/AVC era dobrar a eficiência de codificação em relação


a qualquer outro padrão de codificação de vídeo [6], o que significaria dividir pela metade a taxa de bit
necessária dado um certo nível de fidelidade.
O padrão H.264/AVC possibilita essa maior eficiência por meio de melhorias nos processos
de codificação de vídeo existentes em padrões anteriores. As próximas seções descrevem
brevemente as melhorias propostas e no Anexo A há uma tabela descrevendo quais desses
elementos técnicos estão disponíveis a cada perfil. As melhorias que fazem parte do perfil Baseline
serão abordadas novamente no capítulo 5, que descreve nosso código.

2.2.1 Mudanças essenciais

O padrão H.264 define todo um novo conjunto de divisões de cada quadro do vídeo. Fatias,
Macroblocos e Blocos continuam existindo, tal como nos padrões anteriores. Todavia, a forma como
eles funcionam foi alterada.
7

2.2.1.1 Mudança na unidade espacial

A fatia (ou slice) tem grande importância no H.264 uma vez que agora ela é o elemento
espacial independente básico [26].
No padrão H.262 (MPEG-2) há três tipos de quadros: I, P e B [15]. Esses tipos são
determinados pela forma como os macroblocos do quadro foram codificados e, consequentemente,
quais informações eles vão necessitar para serem decodificados. Quadros I são codificados sem
fazer referência a qualquer outro quadro, usando apenas informações presentes neles mesmos.
Quadros P usam informações de um outro quadro de referência preditas por meio da compensação
de movimento. Quadros B usam informações preditas de mais de um quadro de referência (um antes
e outro depois, por isso é bidirecional).
No padrão H.264 os tipos I, P e B foram deslocados do nível de Quadro para o nível de Fatia.
Dessa forma, existem fatias I, fatias P e fatias B, além de serem adicionados dois novos tipos de
fatias: switching I pictures (SI) e switching P pictures (SP). Essas novas fatias visam reduzir
significativamente a taxa de bits resultante da compressão.
Para suprir a ausência de um quadro intra codificado – decorrente, por exemplo, de peradas
de pacotes na rede – foi adicionado o quadro IDR, um quadro codificado contendo apenas fatias I ou
SI e que serve como referência primária para os outros quadros.

2.2.2 Melhorias no Modelo Temporal

Esta seção descreve brevemente os aspectos técnicos propostos pelo padrão para melhoria
na habilidade de predizer os valores do conteúdo de um quadro a ser codificado.

2.2.2.1 Compensação de Movimento com Blocos de Tamanho Variável (VBSMC)

Permite uma segmentação mais precisa das regiões de movimento de um quadro por meio
de blocos com tamanhos variando entre 16x16 e 4x4.

2.2.2.2 Compensação de Movimento com amostras precisão de ¼ de pixel (Qpel)

Possibilitam descrições mais precisas de deslocamento de áreas de movimento com precisão


de até um quarto de pixel.

2.2.2.3 Vetores de movimento além dos limites do quadro

No H.264/AVC é possível ao vetor de movimento apontar para áreas fora dos limites do
quadro usado como referência.
8

2.2.2.4 Múltiplos Quadros de Referência

Uma novidade neste padrão é a possibilidade de quadros com Compensação de Movimento


poderem fazer referência não apenas a um quadro, tal como no MPEG-2, mas que seja escolhido um
dentre uma lista. Isso possibilita um significativo ganho de compressão quando o vídeo apresenta
movimentos periódicos.
Quadros P podem fazer referência a N quadros da lista 0; Quadros B podem ser usados
como referência para outros quadros e usam quantidade arbitrária de quadros de referência da lista 0
e 1.

2.2.2.5 Filtro Anti-Blocagem

Baseado no parâmetro de quantização, no modo de compressão e no movimento em uma


cena, o Filtro Anti-Blocagem permite diferenciar artefatos de compressão (por exemplo as bordas
quadradas dos blocos) do conteúdo da cena. Ele proporciona uma melhoria substancial nas
qualidades objetiva e subjetiva do vídeo principalmente através da suavização das boradas dos
blocos, reduzindo os artefatos típicos de vídeos codificados.

2.2.2.6 Predição com peso (Weighted Prediction)

Além de especificar o deslocamento do vetor de movimento (i.e. quantos pixels uma área
deve ser deslocada), no padrão H.264/AVC é possível especificar a dimensão do vetor, aumentando
significativamente desempenho em casos especiais, tal como transições fade-to-black, fade-in e
cross-fade.
Este recurso não está presente no perfil Baseline.

2.2.3 Melhorias no Modelo Espacial

Além das melhorias propostas no método de predição, foram aprimorados os processos de


transformada, quantização e codificação de entropia.
Nesta seção serão apresentadas as melhorias na trasnformada e na quantização.

2.2.3.1 Predição espacial (predição intra quadro)

Da mesma forma como é feita a predição temporal – que utiliza amostras de quadros
anteriores previamente decodificadas para predizer os valores das amostras do quadro sendo
decodificado – a predição espacial utiliza amostras decodificadas do mesmo quadro para predizer os
valores das amostras que estão sendo codificadas.
A predição espacial é uma técnica comum na codificação de imagens estáticas, tal como
JPEG.
9

2.2.3.2 Transformada com bloco de tamanho menor

Enquanto os padrões anteriores realizam a transformada em blocos de 8x8, o H.264/AVC


baseia-se principalmente em transformadas sobre blocos de 4x4, visando acompanhar a diminuição
do tamanho dos blocos na predição. Isso permite diminuir os artefatos de blocos e também manter
maior nível de detalhes na cena.

2.2.3.3 Transformada de bloco hierárquico

Em casos especiais é possível aplicar uma transformada Hadamard 2×2, 2×4 ou 4×4 nos
coeficientes DC (de mais baixa frequência) dos blocos 4×4. Essa transformação extra estende a
transformada 4×4 para os tamanhos 8×8, 8×16 (utilizadas para as amostras croma de um MB) ou
16×16 (utilizada para as amostras de um MB Intra especial denominado Intra_16×16).

2.2.3.4 Transformada com palavra de pequeno comprimento

O padrão H.264/AVC utiliza palavras de 16 bits para aritmética, ao invés das palavras de 32
bits utilizadas nos padrões anteriores. Isso reduz a carga computacional das operações tanto no
codificador quanto no decodificador.

2.2.3.5 Transformada inversa exata

Nos padrões anteriores não era possível obter um transformada inversa exata, apenas um
limite de tolerância a erros, resultando em diferença na qualidade do vídeo decodificado entre as
várias implementações.
O padrão H.264/AVC é o primeiro padrão a obter a mesma qualidade de vídeo decodificado
entre as várias implementações de decodificadores [6], tudo graças à transformada DCT1 ser
realizada sobre números inteiros ao invés de números de ponto flutuante. Tal como é apresentado em
[33], transformação e quantização sobre números reais causam erros de precisão entre o codificador
e o decodificador, além de serem mais difíceis de implementar e custosas para processar.

2.2.4 Melhorias na Codificação por Entropia

Esta seção apresenta todos os métodos de codificação por entropia disponíveis no padrão
H.264/AVC, entretanto, no perfil Baseline (o qual é implementado pelo presente trabalho), os
coeficientes da transformada são codificados usando CAVLC2 e todos os outros elementos de sintaxe
são codificados usando códigos de largura fixa ou Exp-Golomb de largura variável.

1
A transformada é na verdade realizada por meio de uma aproximação ortogonal da DCT [33].
2
CABAC é utilizado no lugar de CAVLC em alguns perfis.
10

2.2.4.1 Context Adaptive Variable Length Coding (CAVLC)

A Codificação de Largura Variável (CAVLC) mapeia uma série de símbolos de entrada para
uma série de códigos de tamanho variável. Símbolos que ocorrem com maior frequência são
mapeados para códigos com tamanho menor e símbolos menos frequentes são mapeados para
códigos de tamanho maior.
O mapeamento entre valor real e código é feita utilizando uma tabela. No H.264/AVC foi
incluída uma forma aprimorada da CAVLC, que determina qual a melhor tabela de mapeamento usar
de acordo com o contexto. Assim, essa codificação é adaptável ao contexto.

2.2.4.2 Context Adaptive Binary Arithmetic Coding (CABAC)

Um avançado método de codificação por entropia conhecido como Codificação Aritmética


Binária Adaptável ao Contexto (CABAC) foi incluído no H.264/AVC que permite atribuir um tamanho
não-inteiro (como 2,5 bits ao invés de 2 ou 3 bits) a um código (os códigos usados pela VLC são
sempre de largura inteira).
A CABAC é superior às codificações baseadas em Huffman em diversos aspectos [38], e se
baseia na subdivisão recursiva de intervalos de números, sendo que a distribuição dos intervalos
corresponde à distribuição de probabilidade dos símbolos.
Ela é binária pelo fato de transformar qualquer valor em binário antes da codificação
aritmética e adaptável ao contexto porque seleciona o modelo de probabilidade de cada elemento de
sintaxe baseado em seu contexto, adaptando a probabilidade baseada nas estatísticas locais.
A codificação CABAC consegue, em média, reduzir a taxa de bit de 9% a 14% em relação ao
CAVLC (sem degradação na qualidade, uma vez que codificação por entropia é sem perda) [37].
Este recurso não está presente no perfil Baseline.

2.2.4.3 Exponecial Golomb Variable Length Coding (Exp-Golomb)

Exp-Golomb é uma codificação ideal quando valores pequenos têm uma grande freqüência.
Nela, o tamanho do código é proporcional ao valor a ser codificado e consiste de duas partes: uma
codificação unária de tamanho variável e uma codificação binária de tamanho fixo (dado pelo valor da
codificação unária).

2.2.5 Melhorias na Robustez e Transporte

Os itens destacados nesta seção fazem parte das melhorias para evitar perda e erros em
dados, além daquelas que possibilitam maior flexibilidade para operar em diversos ambientes de
rede. Essas melhorias não representam mudanças no codificador de vídeo propriamente dito, VCL,
mas na camada de abstração de rede, NAL.
11

2.2.5.1 Fatias SI e SP

Esses quadros permitem intercâmbio e sincronização entre quadros, sendo SI de Switching I


e SP de Switching P.
Este recurso não está presente no perfil Baseline.

2.2.5.2 Seqüência Flexível de Macrobloco (FMO)

Essa nova característica permite que cada fatia de um quadro seja decodificada
independentemente das outras fatias do mesmo quadro.

2.2.5.3 Seqüência Arbitrária de Fatia (ASO)

Devido ao fato de cada fatia de um quadro poder ser decodificada independentemente das
outras fatias do mesmo quadro, é possível enviar e receber as fatias de um quadro em qualquer
ordem relacionada as outras fatias do mesmo quadro. Isso possibilita uma menor espera fim-a-fim em
aplicações de tempo real distribuídas, particularmente quando usadas em redes com entrega de
pacotes sem ordenação, tal como os protocolos da Internet.

2.2.5.4 Fatias Redundantes (RS)

O codificador H.264/AVC possui a habilidade de enviar representações redundantes de


regiões de um quadro, fornecendo uma cópia de segurança das regiões de uma figura que foram
perdidas durante a transmissão.

2.2.5.5 Particionamento de Dado (DP)

O particionamento de dado permite que um codificador reorganize o dado codificado em um


pacote de vídeo, de modo a reduzir o impacto na transmissão de erros, colocando dados mais
importantes em partições distintas dos dados menos importantes.

2.3 Funcionamento do codificador H.264

Tal como os padrões anteriores (MPEG1, MPEG2 e MPEG4 Parte 2), o padrão H.264/AVC
não define um codificador explicitamente, mas uma sintaxe de fluxo de bits de vídeo codificado
juntamente com um método para decodificar esse fluxo de bits.
Na prática, um codificador de vídeo inclui os elementos funcionais básicos mostrados na
figura 2.3; no H.264 esses elementos são um pouco diferente dos padrões anteriores, sendo que o
12

acúmulo dessas pequenas melhorias proporciona uma grande taxa de compactação por parte desse
padrão.

2.3.1 Esquema

Conforme mostrado na figura 2.3, um codificador inclui dois caminhos, um caminho de


codificação (representado pelas linhas em azul) e outro de reconstrução (em vermelho).
No esquema mostrado nessa figura, o caminho de codificação “flui” da esquerda para a
direita e é chamado em inglês de forward path, ou caminho para à frente. Todavia, preferimos utilizar
o termo caminho de codificação pelo fato que esse é o sentido que realmente codifica o vídeo (o
caminho de reconstrução é para suporte deste).
Já o caminho de reconstrução segue o sentido oposto, da direita para a esquerda, e tem
como principal objetivo fornecer os quadros e fatias de referência para serem utilizados pelo caminho
de codificação, pois a codificação deve utilizar os quadros (e fatias) que estarão disponíveis no
decodificador, não os quadros (e fatias) originais do arquivo lido, evitando assim o acúmulo de erros.

FIGURA 2.3 – MODELO DE ENCADEAMENTO DOS PROCESSOS NO CODIFICADOR PADRÃO H.264 [4].

2.3.1.1 Caminho de codificação

Um quadro de entrada Fn é processado em unidades de macroblocos. Cada macrobloco pode


ser codificado no modo Intra ou no modo Inter e, para cada bloco no macrobloco, uma predição P é
formada com base nas amostras de figuras reconstruídas.
13

No modo Intra, P é formado a partir de amostras da fatia atual que foram previamente
codificadas, decodificadas e reconstruídas (sF’n na figura 2.3; perceba que são utilizadas amostras
não filtradas para formar P).
No modo Inter, P é formado a partir da predição de compensação de movimento de um ou
dois quadros selecionados do conjunto de quadros de referência das listas 0 ou 1. Na figura 2.3, o
quadro de referência é mostrado como um quadro já codificado F’n-1. Mas a predição de referência de
cada partição de macrobloco (no modo Inter) pode ser escolhida a partir de uma seleção de quadros
passados ou futuros (na ordem de apresentação) que tenham sido codificados, reconstruídos e
filtrados.
A predição P é subtraída do bloco atual para formar um bloco residual (diferença) Dn que é
transformado e quantizado para produzir X, um conjunto de coeficientes que são reordenados e
codificados por entropia. Os coeficientes codificados por entropia, juntamente as informações de
predição de modos, parâmetros de quantização, vetor de movimento, etc. formam o fluxo de bit
comprimido que é passado à Camada de Abstração de Rede (NAL) para transmissão ou
armazenamento.

2.3.1.2 Caminho de reconstrução

Tal como codificar e transmitir cada bloco em um macrobloco, o codificador decodifica


(reconstrói) esses blocos de modo a prover uma referência para predições posteriores. Os
coeficientes X são dimensionados (Q-1) e inversamente transformados (T-1) para produzir um bloco de
diferença D’n. O bloco de predição P é adicionado à D’n para criar um bloco reconstruído sF’n (uma
versão decodificada do bloco original; o s significa “sem filtro”). Um filtro é então aplicado para reduzir
o efeito de distorção do bloco e um quadro de referência é reconstruído a partir de uma série de
blocos F’n.
14

Capítulo 3: Codificação de Vídeo em Java

No presente momento serão explicados os aspectos relevantes a cerca das tecnologias para
o qual o código de referência foi portado. Ou seja, a plataforma para a qual o codificador proposto
pelo trabalho foi desenvolvido. Não entrarei no mérito pelo qual essas tecnologias foram escolhidas,
apenas ressalto os detalhes mais importantes.
Este capítulo está subdividido em duas seções: a primeira que trata sobre a plataforma Java,
descrevendo as características mais relevantes ao desenvolvimento do trabalho; e uma segunda que
aborda a Framework para a qual o codificador foi desenvolvido.

3.1 Plataforma Java

A plataforma para o qual o código foi portado é a Java Standard Edition 1.5.0, incluindo todos
os recursos incorporados à linguagem até essa versão. É importante ressaltar que a implementação
não foi desenvolvida para ser compatível com versões anteriores, apenas posteriores, desde de que
sejam compatíveis com a dada versão na qual o trabalho foi desenvolvido.

3.1.1 Manipulação de dados

Diferente da linguagem C, na qual o código de referência [2] foi escrito, a linguagem Java
fornece suporte de mais alto nível para a manipulação dos bytes do arquivo. Nesta seção serão
descritas quais classes da plataforma Java foram utilizadas para substituir as funções
desempenhadas por algumas estruturas implementadas no código de referência.
- OutputStream: o código de referência utiliza uma estrutura chamada Bitstream para
escrever um fluxo de bytes [2]. Todavia, a plataforma Java oferece uma classe abstrata
padrão para fluxos de dados, a OutputStream. Foi criada para este trabalho uma classe
que unia todas as funcionalidades daquela presente no código de referência com a
padronização da classe presente na plataforma Java. Essa classe foi chamada de
BitOutputStream.
15

3.2 Java Media Framework (JMF)

A Java Media Framework (JMF) é uma API destinada a incorporar dados multimídia, tal como
áudio e vídeo, em aplicações Java e Applets. Ela foi especialmente desenvolvida para tirar proveito
das características da plataforma Java.
A versão utilizada neste trabalho foi a 2.1.1e, por ser a última disponível na data de início do
desenvolvimento.

3.2.1 Histórico

A JMF 1.0, conhecida como Java Media Player API, permitia aos programadores Java
desenvolverem programas para reproduzir mídias de tempo real. A JMF 2.0 estendeu a 1.0 para
fornecer suporte para captura e armazenamento de dados multimídia, controlando o tipo de
processamento que era realizado durante a reprodução, e realizando processamento personalizado
nos fluxos de dados multimídia. Além disso, a JMF 2.0 define uma API de plug-in, que permite
desenvolvedores avançados e provedores de tecnologia uma personalização mais fácil para estender
a funcionalidade da JMF [17].

3.2.2 Componentes

Para adicionar novas funcionalidades à JMF é necessário estendê-la, implementando novos


componentes, essa arquitetura de plug-ins é uma das principais vantagens pela qual a JMF foi
escolhida.
Antes de adicionar novos componentes, se deve primeiro analisar todo fluxo de dados
multimídia, partindo de sua captura ou leitura, interpretação, processamento e, por fim, sua
apresentação ou gravação. Tendo em mente cada um desses aspectos, é preciso verificar quais das
funcionalidades estão disponíveis na JMF, para utilizá-las, ou então implementar novos componentes
que forneçam as funcionalidades indisponíveis na JMF original.
Os principais componentes presentes na JMF e que devem ser estendidos para permitir
recursos adicionais, são os seguintes:
- DataSource: representa um protocolo, tal como FILE, FTP e HTTP. Seria necessário
implementar para suportar novos protocolos onde os dados podem trafegar. Todavia, como
neste trabalho a leitura e gravação é feita por meio de arquivos locais, é utilizado o
DataSource para o protocolo FILE, que já está disponível na JMF;
16

- Demultiplexer: representa um demultiplexador, necessário para suportar novos tipos de


arquivos. No caso deste trabalho, foi necessário implementar um novo Demultiplexer para
manusear os arquivos do tipo YUV. A classe YUVParser contém um objeto membro do tipo
VideoTrack que representa a única trilha presente em um arquivo YUV, a trilha de vídeo.
Em arquivos multimídia, cada tipo de dado presente, áudio, vídeo ou legenda, representa
uma trilha distinta;
- Multiplexer: esses são os objetos que pegam todas as trilhas processadas e as colocam
em um mesmo arquivo. Neste trabalho foi implementado um multiplexador para permitir
colocar a trilha de vídeo no arquivo;
- DataSink: serve para escrever dados em um local, seja a rede ou um arquivo. Um
DataSink lê dados de um DataSource e os grava em algum destino – outro destino sem
ser o dispositivo de apresentação;
- Processor: define um módulo de processamento sobre dado multimídia. Um objeto que
implementa essa interface permite que seja definida uma cadeia de componentes que vão
processar o dado. Neste caso, os componentes são um demultiplexador, um codificador ou
decodificador, e um multiplexador. A figura 3.1 apresenta a estrutura de um objeto
Processor;

FIGURA 3.1 – ESTRUTURA DE UM PROCESSADOR COM OS ESTÁGIOS DE PROCESSAMENTO.

A figura 3.1 mostra a relação entre os principais componentes da JMF que são usados para
codificar dados multimídia. O DataSource da esquerda, e os objetos associados a ele, lidam com a
leitura da mídia de origem. Enquanto o DataSource da direita trata do esquema de gravação da
mídia processada. Os objetos dentro do quadrado tracejado realizam tarefas pré determinadas dentro
do processamento do objeto Processor.
O Demultiplexer separa a mídia em objetos trilhas (Track), cada uma destas
representando um tipo de dado multimídia (vídeo, áudio, legenda, etc.).
A codificação (ou decodificação) de cada uma dessas trilhas é feita individualmente por um
determinado Codec, esse modelo de arquitetura permite que os mesmos CODEC sejam usados por
tipos diferentes de arquivos. Por exemplo, tanto os arquivos AVI quanto MPEG podem conter áudio
em formato MP3, então, após o Demultiplexer específico de cada um desses arquivos separar a
17

trilha de áudio MP3 da trilha de vídeo, é utilizado o mesmo Codec para decodificar o MP3 de ambos
os tipos de arquivos.
Há ainda a possibilidade de aplicar efeitos especiais (como por exemplo, eco) em cada trilha
por meio de objetos Effect.
No estágio final, é possível reproduzir cada uma das trilhas por meio de um objeto
Renderer, de acordo com o tipo de mídia que elas contém, assim, reproduzindo vídeo em um
monitor e áudio em caixas de som, por exemplo. Ainda é possível mesclar as trilhas por meio de um
objeto Multiplexer e salvá-las em um novo arquivo, que pode conter as trilhas decodificadas ou
codificadas.
18

Capítulo 4: Especificações do Codificador H.264 em Java

Como todo software, o codificador implementado neste trabalho possui um nicho específico
de aplicação, um tipo de dado sobre o qual trabalha. Neste capítulo são descritas as especificações
técnicas do trabalho, ou seja, os tipos de dados processados pelo codificador, tal como seus
parâmetros de funcionamento.

4.1 Vídeo de entrada

A objetivo de todo codificador compactador é reduzir o tamanho de um determinado arquivo,


mantendo os dados utilizáveis dentro de certa tolerância. No caso de um codificador de vídeo, o
arquivo de entrada é um vídeo descompactado, um vídeo bruto, ou Raw Video YUV.
A tabela 4.1 apresenta um resumo das características que o vídeo de entrada necessita
possuir para ser utilizado pelo codificador apresentado neste trabalho.

Parâmetro Resumo

Formato do quadro YUV 4:2:0

Resolução QCIF (176x144)


CIF(352x258)

Taxa de quadros ~ 15 fps para QCIF

Taxas de bit ~ 60 kbps VBR ou CBR

Bytes por quadro 38016 (25344 de Y, 6336 de U e 6336 de V)

TABELA 4.1 – RESUMO DAS ESPECIFICAÇÕES DO PROJETO PARA VÍDEO DE ENTRADA

4.1.1 Formato do quadro

O único formato de quadro aceito pelo codificador implementado neste trabalho é o YUV 4:2:0
Planar, onde cada quadro é composto por 3 planos, ou matrizes, tal como mostrado na figura 4.1 (a).
Sendo N a largura e M a altura em pixels do vídeo, os primeiros NxM bytes de cada quadro
representam a matriz de luma Y, os próximos (N/2)x(M/2) bytes correspondem à matriz de
crominância U e os últimos (N/2)x(M/2) bytes do quadro são a matriz de crominância V.
Por exemplo, em uma resolução de 176x144 pixels (QCIF), os primeiros 25344 bytes de cada
quadro correspondem ao componente Y, os próximos 6336 bytes representam o componente de cor
U e os últimos 6336 bytes o componente V, totalizando 12672 bytes de crominância UV em cada
quadro. Podemos considerar que são 12 bits por pixel nesse formato.
19

FIGURA 4.1 – (A) ESTRUTURA E (B) EXEMPLO DE QUADRO YUV 4:2:0 PLANAR.

4.1.2 Resolução, taxa de quadros e taxa de bits.

Para cada quadro foi adotado o formato QCIF, que define as dimensões em 176 pixels de
largura por 144 pixels de altura, a uma taxa de 15 fps, o que gera uma taxa de aproximadamente 60
kbps.

4.2 Vídeo de saída

A implementação tratada neste trabalho suporta um subconjunto dos recursos presentes no


padrão. A tabela 4.2 apresenta um resumo dos recursos suportados.

Parâmetro Resumo

Perfis Baseline

Níveis 1 e 1.2

Latência de codificação 4 quadros/segundo

Formato de vídeo Fluxo de bytes H.264 (ISO/IEC 14496-15), Anexo B da


comprimido Recomendação do ITU-T.

TABELA 4.2 – RESUMO DAS ESPECIFICAÇÕES DO PROJETO PARA VÍDEO DE SAÍDA


20

4.2.1 Perfis

De acordo com [26], um perfil especifica qual sintaxe de codificação (algoritmo) é usada,
enquanto um nível especifica os vários parâmetros (resolução, taxa de quadros, taxa de bit, etc.).
Os padrões de codificação de vídeo em geral possuem uma gama enorme de recursos que,
quando aplicados em conjunto, permitem obter altas taxas de compactação de vídeo. Um perfil nada
mais é que um subconjunto desses recursos, especialmente selecionados para funcionar em
determinados equipamentos e abranger um determinado público.
O perfil implementado neste trabalho é o Baseline, que é destinado aos dispositivos de baixa
capacidade de processamento e o público de aplicações via Internet, tal como vídeo conferências,
vídeo telefonia e comunicação sem fio.

4.2.1.1 Baseline

Dos cinco tipos de fatias disponíveis no padrão H.264, o perfil Baseline suporta apenas duas,
I e P. Por meio desses dois tipos de fatias são implementadas as codificações Intra e Inter.
As fatias nesse perfil também não podem ser particionadas, ou seja, toda a fatia deverá ser
enviada em uma única unidade NAL.
As codificações por entropia presentes são Exp-Golomb para parâmetros e CAVLC para
dados.
Os recursos que não são permitidos nesse perfil são: a codificação CABAC; predição com
peso; fatias SP, SI e B; particionamento de dado (fatias);

4.2.2 Orientação da Palavra de Dado (Endianess)

A função que testa o endian foi fixada para retornar little-endian, que é o padrão da
arquitetura Intel.
21

Capítulo 5: Desenvolvimento do Codificador H.264 em Java

O comitê criador do padrão H.264 disponibilizou um código-fonte de referência [2] escrito em


C para que terceiros implementem suas próprias versões do codificador H.264, e como mencionado
anteriormente a idéia central do trabalho é implementar uma versão em Java desse codificador.
O trabalho não consiste somente em portar um código-fonte escrito em C para Java, foi feita
uma análise de orientação a objetos em cima do código estruturado fornecido como referência. Além
disso, há toda uma preocupação com a integração dele com a JMF, que por sua vez possui uma
arquitetura particular.
Seguindo a ordem como os dados são processados, os itens a seguir abordam cada aspecto
relacionado ao projeto, descrevendo como cada parte do código-fonte de referência foi implementado
em Java utilizando a Java Media Framework. E no Apêndice C há uma tabela que complementa este
capítulo, mostrando mais diretamente a equivalência entre os diversos módulos do código de
referência e as classes Java que implementam as funções desses módulos.

5.1 Leitura dos quadros do arquivo YUV

O processamento inicia pela leitura dos quadros descompactados no formato YCbCr, que
eqüivale ao quadro original Fn da figura 2.3.
No código de referência, há um laço de repetição que percorre todos os quadros do arquivo a
ser codificado. A cada iteração, é chamada a função encode_one_frame do arquivo image.c, que
por sua vez chama a função ReadOneFrame, presente no mesmo arquivo, para ler um quadro do
arquivo a ser codificado.
Neste trabalho, a leitura é realizada por meio da JMF, que lê cada quadro de vídeo do arquivo
de entrada e permite acesso aos bytes individuais desse quadro por meio de um objeto da classe
javax.media.Buffer.

5.1.1 Função encode_one_frame

No código do trabalho, o método process da classe H264Encoder eqüivale à função


encode_one_frame. Mas o método process não chama nenhum método para ler um quadro do
arquivo de origem, pois ele é chamado após a JMF ler cada quadro do arquivo. Em suma, é chamado
o método process após cada quadro ter sido lido.
22

O método process da classe H264Encoder possui dois argumentos, ambos objetos da


classe Buffer, onde o primeiro contém os dados lidos do arquivo de entrada e o segundo é
destinado a armazenar os dados codificados.

5.1.2 Função ReadOneFrame

A funcionalidade oferecida pela função ReadOneFrame do código de referência é fornecida


no trabalho por meio de um conjunto de classes implementadas por mim e controladas pela JMF.
O primeiro passo consiste em dizer à JMF que os arquivos com extensão .yuv devem ser
lidos pela classe YUVParser. Então ao abrir um arquivo desse tipo a JMF chama o método
setSource dessa classe passando um objeto DataSource que contém um fluxo com os bytes do
arquivo lido.
Então é criado um objeto YUVVideoTrack que representa a trilha de vídeo desse arquivo.
Caso o arquivo lido fosse composto por uma trilha de vídeo e uma de som, seria criada uma trilha a
mais para interpretar o fluxo de áudio do arquivo. A classe YUVVideoTrack possui um método
chamado readFrame que é chamado para ler um número determinado de bytes do arquivo (cujo
valor foi preestabelecido no construtor dessa classe por meio do argumento dataSize). Esse
número de bytes representa o tamanho de cada quadro de vídeo, dessa forma, a JMF chama o
método readFrame para ler cada quadro do arquivo de origem, interpretar os dados, e colocar o
resultado no objeto Buffer que recebe como parâmetro.
Os dados lidos pelo método readFrame da classe YUVVideoTrack e colocado no objeto
Buffer recebido como argumento, são passados então para a classe de codificação por meio da
JMF.

5.2 Predição Intra Quadro

A predição Intra é aquela que utiliza apenas as amostras contidas no mesmo quadro, sem
fazer referência nenhum outro quadro anteriormente codificado, para reduzir a redundância entre
duas amostras. A predição intra quadro visa reduzir a redundância espacial de cada quadro de um
vídeo.
23

5.2.1 I_PCM

O modo I_PCM é uma alternativa à predição intra e consiste em transmitir os valores das
amostras diretamente, sem predição, transformada ou codificação por entropia. Um macrobloco
I_PCM é gravado com os mesmos valores que possuia no arquivo descompactado.
Neste trabalho, a classe IPCMEncodingMode implementa a predição I_PCM.

5.2.2 Intra 16x16 Luma

O modo de predição Intra 16x16 consiste em predizer os valores de um macrobloco inteiro a


partir das amostras previamente codificadas de macroblocos vizinhos. Os valores preditos são
subtraídos das amostras originais para obter valores residuais. Em seguida, esses valores residuais
são transformados e quantizados. Lembrando que mesmo a predição sendo realizada no macrobloco
como um todo, a transformada é aplicada em cada bloco 4x4 individualmente.
O H.264 utiliza as letras A, B, C e D para indicar quais partições (blocos ou macroblocos)
estão disponíveis para a partição sendo codificada. A figura 5.1 mostra a posição dos macroblocos
vizinhos (A, B, C e D) em relação ao macrobloco sendo codificado (E), e em vermelho blocos vizinhos
e bloco corrente. Alguns macroblocos vizinhos não estão disponíveis próximo às bordas do quadro,
por exemplo, se o macrobloco sendo codificado (E) for o número 0, ou seja, o primeiro do quadro,
nenhum dos vizinhos estará disponível. Já o segundo macrobloco, número 1, terá apenas o vizinho A.

FIGURA 5.1 – PARTIÇÃO CORRENTE (E) E PARTIÇÕES VIZINHAS (A, B, C E D).

O padrão H.264/AVC define quatro modos de predição Intra 16x16 Luma: Vertical, Horizontal,
DC e Plano. Cada qual sendo melhor aplicado em um determinado padrão de pixels. Por exemplo, o
modo Plano se aplica bem em regiões onde há transição de tons.
No código de referência [2], a função Intra16x16_Mode_Decision calcula os quatro
modos de predição por meio da função intrapred_16x16, logo em seguida verifica qual dos modos
24

possui a menor distorção por meio da função find_sad_16x16 e por fim aplica a transformada e
quantização usando a função dct_16x16.
O código desenvolvido neste trabalho separa cada um dos quatros modos em classes:
Intra16x16LumaDCPredictor, Intra16x16LumaHorizontalPredictor,
Intra16x16LumaVerticalPredictor e Intra16x16LumaChromaPlanePredictor. Cada uma
dessas classes sobrescreve o método doIntraPrediction, o qual é invocado pela classe abstrata
Intra16x16LumaAbstractPredictor que é a classe base dos modos. Toda funcionalidade
comum aos quatro modos foi implementada nesta classe, tal como as rotinas de transformação,
reconstrução, codificação por entropia e cálculo da distorção. Assim, cada classe é responsável por
sua codificação.
Para saber qual é o melhor modo para um determinado macrobloco, basta obter a distorção
de cada um dos modos e comparar os valores entre si.

FIGURA 5.2 – ORDEM DE ESCANEAMENTO DOS BLOCOS 4X4 EM UM MACROBLOCO [1].

Quando um macrobloco é codificado no modo Intra 16x16 Luma, cada coeficiente DC de


cada um dos dezesseis blocos 4x4 luma é escaneado primeiro, formando um bloco 4x4 composto
unicamente por coeficientes DC (figura 5.2). Nesse bloco de coeficientes DC é aplicada uma
transformada adicional chamada Hadamard e, em seguida esses coeficientes são quantizados e
reordenados. De forma similar, no modo Intra 8x8 Chroma, para cada componente é formado um
bloco 2x2 de coeficientes DC, aplicada a transformada de Hadamard, uma quantização nos
coeficientes e a reordenação dos mesmos em um vetor.
Os dezesseis blocos 4x4 luma (0 a 15), os quatro blocos 4x4 Cb (18 a 21) e os quatro blocos
4x4 Cr (22 a 25) são chamados de blocos AC. Esses blocos são escaneados a partir da segunda
posição da figura 5.4, contendo quinze coeficientes cada, ao invés de dezesseis. Esses blocos são
25

então quantizados e reordenados. Os blocos –1 (contendo dezesseis coeficientes), 16 (com quatro


coeficientes) e 17 (também com quatro coeficientes) são chamados de blocos DC.

5.2.2 Intra 8x8 Chroma

Os modos de predição Intra 8x8 Chroma são bem parecidos com os Intra 16x16 Luma,
exceto pela ordem (DC, Horizontal, Vertical e Plano) e pelo fato do modo 0, Plano, realizar a predição
individualmente nos quatro blocos 4x4 que compõem o bloco 8x8, ao contrário do modo Intra 16x16
Luma Plano, que realiza uma única predição no bloco inteiro.
No código de referência [2], a função IntraChromaPrediction calcula os quatro modos de
predição Intra Chroma (DC, Horizontal, Vertical e Plana) para cada um dos componentes de cor (Cb e
Cr) do macrobloco sendo codificado. Nessa mesma função são computados todos os quatros modos
de predição. Em seguida é chamada na função ChromaResidualCoding, para cada um dos
componente de cor (Cb e Cr), é decidido o tipo de predição e realizada a transformada e quantização
do resíduo dos coeficientes. A função ChromaPrediction4x4 fica responsável por decidir o tipo de
predição, Intra ou Inter, e a função IntraChromaPrediction4x4 copia os valores da predição para
um buffer global e atribui os valores residuais à outro buffer. Por fim, a função dct_chroma aplica a
transformada e quantização no resíduo dos coeficientes.
O modo Intra 8x8 Chroma adota o mesmo esquema do modo Intra 16x16 Luma, separando
cada um dos quatros modos de predição em classes: Intra8x8ChromaDCPredictor,
Intra8x8ChromaHorizontalPredictor, Intra8x8ChromaVerticalPredictor e
Intra8x8ChromaPlanePredictor. Cada uma dessas classes sobrescreve o método
doIntraPrediction, invocado pela classe abstrata Intra8x8ChromaAbstractPredictor que
é a classe base dos modos e onde toda funcionalidade comum aos quatro modos de predição foi
implementada.

5.3 Predição Inter Quadro

Esta predição visa reduzir a redundância temporal, ou seja, aquela que existe entre dois ou
mais quadros.
26

5.4 Transformada, Quantização e Reordenação

No código desenvolvido neste trabalho os algoritmos de transformada, quantização e


reordenação foram isolados em classes específicas. Outra diferença crucial entre o código de
referência [2] e este trabalho é que, neste trabalho é codificado um quadro inteiro antes dele ser
escrito, enquanto que no código de referência cada macrobloco é codificado e escrito.

5.4.1 Transformada

A transformada é uma operação sobre matrizes que tem como objetivo homogeneizar os
valores dos coeficientes dessa matriz, possibilitando uma melhor codificação por entropia.
A transformada utilizada no H.264 é uma aproximação ortogonal da Transformada Discreta
de Cosenos, DCT. Ela não é uma implementação da fórmula da DCT, mas uma operação cujo
resultado se aproxima muito com os da DCT usando apenas números inteiros. Outro fator de grande
importância é o da transformada ser aplicada aos blocos de tamanho 4x4, permitindo grande redução
nos artefatos de blocagem característicos da codificação de vídeo.
No código do trabalho, a transformada é implementada pela classe IntegerTransform que
fornece exclusivamente serviços de transformação de matrizes.

5.4.2 Quantização

A operação de quantização tem como intuito reduzir a magnitude dos coeficientes resultantes
da transformada por meio de uma divisão, e a restituição dos valores originais por meio de uma
multiplicação. A figura 5.3 mostra graficamente o processo de quantização.
27

FIGURA 5.3 – QUANTIZAÇÃO DOS COEFICIENTES DA DCT [48].

5.4.3 Reordenação

A reordenação consiste basicamente em colocar os valores dos coeficientes de uma matriz


bidimensional NxN em um vetor unidimensional de tamanho M, onde M = N * N. O objetivo desse
processo é criar um vetor que possua em seqüência os elementos de valores parecidos, permitindo
uma maior compressão por entropia.
O código de referência [2] realiza a ordenação, ou zig-zag scan, juntamente com a
quantização dos coeficientes. Isso proporciona uma otimização no código, entretanto também o torna
mais difícil de ser compreendido. No código desenvolvido neste trabalho, a quantização e a
reordenação foram separadas em classes distintas, o que torna o código mais legível e fácil de ser
modificado. A classe ZigZagFrameScanner implementa a interface Scanner para blocos de
frames (existem dois modos, frame e field).
28

FIGURA 5.4 – REORDENAÇÃO ZIG-ZAG PARA BLOCOS 4X4 LUMA (MODO FRAME).

No codificador, cada bloco 4x4 – contendo coeficientes transformados e quantizados – é


reordenado em um vetor de acordo com a ordem mostrada na figura 5.4.

5.5 Codificação por Entropia

O padrão H.264 define dois tipos de codificação por entropia, CAVLC e CABAC. O método
utilizado neste trabalho é o CAVLC, por ser o tipo usado pelo perfil Baseline.
O padrão H.264 codifica todas as informações de fatias, macroblocos e blocos usando ou
CAVLC, ou CABAC, enquanto as informações acima da camada de fatia são codificadas usando
códigos binários de tamanho fixo ou variável.
A classe CAVLC, juntamente com BitOutputStream, são responsáveis por implementar as
funções para codificação por entropia CAVLC e Exp-Golomb. Abaixo está um exemplo que codifica o
parâmetro profile_idc usando a função u(v)1, onde stream é um objeto da classe CAVLC.

len += stream.write_u_v(8, profile_idc);

A classe CAVLC é responsável por construir os códigos da codificação por entropia, enquanto
a classe BitOutputStream é quem escreve os bits na stream de saída, que pode ser um arquivo ou
a rede.

1
Nomenclatura utilizada pelo documento padrão do ITU-T [3].
29

5.5.1 Exp-Golomb

5.5.1.1 Conceito

Exp-Golomb são códigos de largura variável com construções regulares [1], dispensando o
uso de tabelas ou árvores de código. Esse método é usado para codificação da maior parte das
informações do vídeo codificado pelo H.264 e é ideal para comprimir dados cujos valores pequenos
têm uma freqüência muito grande[4]. O código Exp-Golomb possui a seguinte forma:
[M zeros][1][INFO]
Onde:
M = floor( log2(code_num + 1) )
INFO = code_num + 1 – 2M
Sendo INFO um campo de M bits contendo a informação codificada, a largura do código Exp-
Golomb é (2M + 1) bits.
As funções Exp-Golomb recebem um número inteiro v como argumento e o mapeiam para
code_num. No padrão H.264 são usadas algumas formas distintas de mapeamento entre v e
code_num, dentre as quais estão presentes neste trabalho:
- ue(v) é a função que codifica inteiros sem sinal (unsigned) usando Exp-Golomb. Essa função
utiliza mapeamento direto, ou seja, code_num = v, e é representada no código pelo método
CAVLC.write_ue_v(int value);
- se(v) é uma variação da função ue(v) que codifica inteiros com sinal (signed). Representada
pelo método CAVLC.write_se_v(int value). Nela, code_num é mapeado da seguinte
forma:
code_num = 2 * |v| (v < 0)
code_num = 2 * |v| - 1 (v > 0)
As tabelas 5.1 e 5.2 ilustram os primeiros códigos Exp-Golomb para ue(v) e se(v),
respectivamente.

v code_num Código Exp-Golomb

0 0 1

1 1 010

2 2 011

3 3 00100

4 4 00101

5 5 00110

6 6 00111

7 7 0001000

TABELA 5.1 – OITO PRIMEIROS CÓDIGOS EXP_GOLOMB PARA UE(V).


30

v code_num Código Exp-Golomb

0 0 1

1 1 010

-1 2 011

2 3 00100

-2 4 00101

3 5 00110

-3 6 00111

4 7 0001000

TABELA 5.2 – OITO PRIMEIROS CÓDIGOS EXP_GOLOMB PARA SE(V).

No caso da codificação usando ue(v), é passado um valor v ao método public int


write_ue_v(int value) da classe CAVLC. Esse método chama outros dois métodos para (1) criar
INFO a partir do argumento v e (2) criar a string de código [M zeros][1][INFO].

5.5.1.2 Aplicação

Exp-Golomb é a técnica adotada para codificar a maioria dos parâmetros do vídeo H.264.
Dentre os quais se destacam aqueles apresentes na tabela 5.3.

Parâmetro Descrição

Conjunto de Parâmetros de Seqüência São os cabeçalhos que contém informações sobre


e de Figura (Sequence and Picture todo o vídeo ou um quadro.
parameter Sets)

Tipo do Macrobloco (mb_type) O tipo do macrobloco indica qual o método de predição


adotado naquele macrobloco.

Blocos Codificados (Coded Block Indica quais blocos de um macrobloco contém


Pattern) coeficientes codificados.

Parâmetro do Quantizador (Quantiser Transmitido como um valor delta a partir do QP


Parameter, QP) anterior.

TABELA 5.3 – EXEMPLO DE PARÂMETROS A SEREM CODIFICADOS.

Como regra, tudo que estiver acima da camada de fatia é codificado usando Exp-Golomb ou
códigos binários. Enquanto que tudo que estiver na camada de fatia e abaixo é codificado usando
CAVLC ou CABAC, dependendo do modo de codificação por entropia.

5.5.2 CAVLC

A codificação CAVLC do H.264 é usada para codificar os resíduos dos coeficientes da


transformada, por meio de modificação em algumas técnicas de codificação por entropia, tal como
Huffman e RLE (Run Length Encoding).
31

A idéia por trás do RLE é: se um símbolo X ocorre N vezes consecutivas, substitua as N


ocorrências pelo par de símbolos XN [30]. As N ocorrências consecutivas do símbolo X são
chamadas de carreira (run-length) de X. O H.264 herdou do JPEG um conceito onde o RLE é
utilizado para codificação de coeficientes consecutivos com valor zero [28][3].
A Codificação de Largura Variável Adaptável ao Contexto (CAVLC) é baseada na codificação
VLC, mas ao invés de manter uma tabela de mapeamento de símbolos fixa, como ocorre na VLC
tradicional, ela usa a comutação de tabelas explorando a redundância entre o símbolo sendo
codificado e os já codificados [36], por isso é chamada de VLC Adaptável ao Contexto.
Independente do modo de predição adotado, o H.264 codifica individualmente os blocos de
tamanho 4x4, da mesma forma como esse é o tamanho de bloco usado pela transformada e
quantização. Por exemplo, na predição Intra 16x16, as funções writeCoeff16x16 e
writeChromaCoeff do código de referência [2] são usadas para codificação por entropia dos
componentes luma e chroma, respectivamente. Cada uma dessas funções chama uma vez
writeCoeff4x4_CAVLC para codificar os coeficientes DC e n vezes para codificar os coeficientes
AC.
A CAVLC foi projetada para tirar proveito de muitas características dos blocos quantizados
4x4 [1]:
- Após a predição, transformada e quantização, os blocos contém muitos zeros. A CAVLC usa
RLE para representar seqüências de zeros de forma compacta;
- Os coeficientes diferentes de zero de mais alta freqüência (mais distantes do coeficiente DC)
após a reordenação zig-zag são geralmente ±1. A CAVLC codifica esses coeficientes (trailing
ones) de forma compacta;
- Há uma forte relação entre a quantidade de coeficientes diferentes de zero em blocos
vizinhos, dessa forma, o número de coeficientes é codificado usando uma tabela cuja escolha
depende do número de coeficientes diferentes de zero nos blocos vizinhos. Daí vem a
Context Adaptive da sigla CAVLC;
- O nível (magnitude) dos coeficientes diferentes de zero tende a ser maior no início do vetor
reordenado (próximo ao coeficiente DC) e menor em direção às freqüências mais altas.
Assim, a CAVLC tira vantagem disso escolhendo a tabela VLC para o nível de acordo com as
magnitudes recém codificadas.

No código desenvolvido neste trabalho, o método writeResidualBlock é responsável pela


codificação por entropia dos blocos 4x4 residuais e equivale ao writeCoeff4x4_CAVLC do código
de referência.

5.5.2.1 Codificação do número de coeficientes diferentes de zero e 1s em carreira

Por meio do código coeff_token, no decodificador, é possível obter o número de


coeficientes diferentes de zero (TotalCoeffs) e a quantidade de coeficientes de valor 1 no final do
vetor (TrailingOnes).
32

A quantidade de coeficientes diferentes de zero (TotalCoeffs) pode ser de 0 (indicando


que não existe nenhum coeficiente diferente de zero no bloco 4x4) a 16 (todos os coeficientes são
diferente de zero). E o número de TrailingOnes pode ir de 0 a 3.
Existem quatro tabelas VLC para codificar coeff_token, e a escolha depende do número
de coeficientes diferentes de zero nos blocos vizinhos. Cada tabela possui os códigos VLC com
menor largura em uma determinada faixa. A tabela 1 dedica seus códigos VLC com menor largura
para TotalCoeffs pequenos, ou seja, para blocos com pouco número de coeficientes diferentes de
zero, enqunato a tabela 3 dedica seus códigos VLC com menor largura aos blocos com grande
quantidade de coeficientes diferentes de zero. A tabela 4 possui códigos VLC de largura fixa.
O método writeCoeffToken da classe CAVLC escreve o coeff_token a partir do
TotalCoeffs e do TrailingOnes.

5.5.2.2 Codificação do sinal de cada 1 em carreira

O sinal de até três coeficientes com valor 1 é codificado, sendo usado um bit para cada sinal.
A ordem de codificação desses sinais é invertida, começando pelo último coeficiente 1 e terminando
no terceiro, se houver.
Dado o código binária contendo o sinal dos TrailingOnes e a quantidade deles, o método
writeTrailingOnesSignFlag da classe CAVLC escreve esse código no fluxo de saída.

5.5.2.3 Codificação da magnitude dos coeficientes diferentes de zero remanescentes

A magnitude e o sinal dos coeficientes diferentes de zero restantes são codificados em ordem
inversa, iniciando pelo coeficiente logo após o último TrailingOnes e indo em direção ao
coeficiente DC. Note que os coeficientes com valor 1 que não fazem parte dos TrailingOnes são
codificados nesta etapa.
A partir da magnitude (nível) e do tamanho do nível anterior, o método writeLevel da
classe CAVLC codifica a magnitude de um coeficiente. Em [1] é descrito o algoritmo para codificação
do nível dos coeficientes diferentes de zero remanescentes e em [36] é ilustrado um exemplo da
aplicação desse algoritmo.

5.5.2.4 Codificação do número de zeros antes do último coeficiente

A quantidade de zeros que precedem o coeficiente de mais alta freqüência (mais distante do
DC) é codificado por meio do método writeTotalZeros da classe CAVLC. O par totalZeros e
totalCoeff define uma palavra de código em uma tabela. A maioria dos blocos contém coeficientes
diferentes de zero no começo do vetor ordenado. Dessa forma, a seqüência de zeros que aparecem
no início do vetor não precisa ser codificada, sendo indicada nesta etapa.
Esse método recebe ainda um terceiro valor, que indica a quantidade de coeficientes no
vetor, pois existem tabelas distintas para codificar vetores com 4 ou 16 coeficientes.
33

5.5.2.5 Codificação das seqüências de zeros antes de cada coeficiente diferente de zero

O método writeRunBefore da classe CAVLC é chamado para escrever o número de zeros


que precedem cada coeficiente diferente de zero. Esse método aceita um par de argumentos, os
quais são os índices dentro de uma tabela VLC, que indicam qual palavra de código será usada para
codificar o run before.
Iniciando a partir do coeficiente de mais alta freqüência (mais distante do DC), o método
writeRunBefore é chamado recebendo runBefore (o número de zeros entre este e o próximo
coeficiente diferente de zero) e zerosLeft (quantos zeros ainda restam no total).

5.6 Gravação do arquivo codificado

Com o propósito de distinguir entre características específicas da codificação daquelas


específicas do armazenamento, o padrão H.264 é separado em duas camadas: a VCL (Video Coding
Layer), cuja saída é uma seqüência de bits representando o vídeo codificado; e a NAL (Network
Abstraction Layer), responsável por abstrair a maneira como os dados são escritos.
Os dados de saída da VCL são mapeados para unidades NAL antes do armazenamento ou
da transmissão. Cada unidade NAL contém uma carga útil (payload) que corresponde aos dados de
saída da VCL ou parâmetros da codificação.
Neste trabalho a NAL e a VCL foram explicitamente separadas em diferentes pacotes e
classes. Além disso, a persistência do arquivo de vídeo foi separada da NAL, assim, o
encapsulamento de dados na NAL e a gravação em arquivo são realizados por classes distintas.
Em relação à JMF, a escrita deve ser realizada por um componente Multiplexer. O
capítulo 3 descreve mais detalhadamente o processo de multiplexação.

5.6.1 Formatos de saída

Dentre os mais comuns tipos de arquivos contendo vídeo H.264 estão o container MP4 e
fluxo de bits puro 264, ou raw bitstream, que não é armazenado em container.

5.6.1.1 Fluxo de bits puro

O anexo B do documento do padrão ISO 14496-10 [3] define um formato de escrita de vídeo
H.264 chamado fluxo de bits puro.
34

5.6.1.2 Container MP4

O documento ISO 14496-14 especifica um container, que dentre outras coisas pode conter
vídeo H.264.
35

Capítulo 6: Conclusões

Neste capítulos estão descritas as conclusões obtidas durante e após o desenvolvimento do


codificador.

6.1 Fatores técnicos

Esta seção descreve os fatores técnicos relativos ao desempenho, qualidade da imagem e


dificuldades técnicas durante o desenvolvimento.

6.1.1 Dificuldades durante o desenvolvimento

Sem dúvida a maior dificuldade foi organizar o código de referência de modo a entender o
que cada módulo fazia. O código de referência [2] é um retalho de funções, mal organizado, pouco
comentado e com diversas ambiguidades. Outro fator que também dificulta é a desconexão parcial
com o documento padrão [3], sendo exigido um estudo muito detalhado de ambos para poder
estabelecer uma ligação entre eles.
O nível de detalhes no código também foi fonte de atrasos no projeto. Foram necessários
testes constantes durante todo o desenvolvimento para assegurar que o vídeo codificado estava de
acordo com aquele gerado pelo código de referência, bastando a escrita errada de apenas um bit
para invalidar toda a codificação.

6.2 Considerações para o futuro

Uma das principais preocupações durante o desenvolvimento deste trabalho foi a de permitir
que ele seja continuado, por meio de uma cuidadosa modelagem orientada a objetos e
documentação. A seguir estão algumas da sugestões para extensão deste trabalho.

6.2.1 Desempenho de algoritmos

O codificador implementado manteve os algoritmos originais do código de referência do ITU-


T. Um trabalho futuro seria utilizar algoritmos mais eficientes para estimação de movimento (Motion
36

Estimation), DCT e Quantização, para permitir uso com dispositivos móveis, os quais possuem menos
poder de processamento.
Segundo artigo [44], a estimação de movimento consome a maior parte do tempo de
processamento de um codificador. Por exemplo, ao invés de utilizar o algoritmo Full Search para
estimação de movimento, utilizar um algoritmo de busca parcial, tal como o Predictive Algorithm (PA)
ou o Diamond Search.
Utilizar uma DCT com inteiros ao invés de pontos flutuantes, permitindo utilizar somas e
deslocamentos no lugar das multiplicações.

6.2.2 Transporte e armazenamento

A gravação deste arquivo é feita de acordo com o Anexo B do documento padrão [3], que é a
forma de gravação em fluxo contínuo de bytes. Há diversas propostas para implementar uma forma
de gravação de pacotes NAL para sobre RTP, uma delas é a indicada em [43], que sugere um
formato de carga útil para pacotes RTP com H.264.
Um outra melhoria seria implementar um multiplexador MP4 (MPEG-4 Parte 14), para que a
saída do codificador fosse compatível com a maioria dos reprodutores disponíveis.

6.2.3 Perfis

Extensões futuras poderiam adicionar os outros perfis do padrão, tal como o Main, Extended,
High, etc. Com isso seria necessário implementar recursos ainda não disponíveis neste codificador,
como por exemplo, a codificação por entropia CABAC.

6.2.4 Decodificador

Um decodificador H.264 em Java seria muito útil, e sua implementação seria facilitada pelo
reuso de diversas classes já implementadas pelo codificador.
37

Apêndice A: Código-fonte

Este apêndice contém o código-fonte desenvolvido neste trabalho. Devido ao grande volume
de código desenvolvido, apenas as classes principais são exibidas. E pelo mesmo motivo, classes
semelhantes são omitidas. Por exemplo, os modos de codificação, os quais mudam pouco um do
outro.
As classes estão apresentadas em seções, de acordo com sua função dentro do codificador.

A1 Classes de suporte

A1.1 Classe Registry

package br.ufsc.inf.guiga.media;

import java.io.IOException;
import java.util.Vector;

import javax.media.Codec;
import javax.media.Demultiplexer;
import javax.media.Multiplexer;
import javax.media.PackageManager;
import javax.media.PlugInManager;

import br.ufsc.inf.guiga.media.codec.video.colorspace.JavaYUVToRGB;
import br.ufsc.inf.guiga.media.codec.video.h264.H264Encoder;
import br.ufsc.inf.guiga.media.multiplexer.video.H264Mux;
import br.ufsc.inf.guiga.media.multiplexer.video.MP4Mux;
import br.ufsc.inf.guiga.media.parser.video.YUVParser;

import com.sun.media.MimeManager;

/**
* H.264 CODEC registration routines.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class Registry {

@SuppressWarnings("unchecked")
public static void registerPackage() {
String packageName = "br.ufsc.inf.guiga";
Vector<String> contentPrefixList;
Vector<String> protocolPrefixList;

// PackageManager maintains a registry of packages that contain JMF classes,


// such as custom Players, Processors, DataSources and DataSinks. We add our
// packager first to be the first choice when creating plug-ins.
try {
contentPrefixList = PackageManager.getContentPrefixList();
if (!contentPrefixList.contains(packageName)) {
contentPrefixList.add(0, packageName);
PackageManager.setContentPrefixList(contentPrefixList);
}

protocolPrefixList = PackageManager.getProtocolPrefixList();
if (!protocolPrefixList.contains(packageName)) {
protocolPrefixList.add(0, packageName);
PackageManager.setProtocolPrefixList(protocolPrefixList);
}

PackageManager.commitContentPrefixList();
PackageManager.commitProtocolPrefixList();

} catch (SecurityException sex) {


38

System.err.println("Could not register the package " + packageName);


sex.printStackTrace();
}
}

public static void registerYUVHandler() {


// MimeManager maintains an associating between file extension and content
// descriptors, or MIME types
MimeManager.addMimeType("yuv", "video/yuv");
MimeManager.commit();

// PlugInManager maintains a registry of available JMF plug-in processing


// components, such as Multiplexers, Demultiplexers, Codecs, Effects and Renderers

// Register the YCbCr parser plugin. Allow JMF read .yuv files
Demultiplexer demuplexer = (Demultiplexer) new YUVParser();
String name = demuplexer.getClass().getName();
PlugInManager.addPlugIn(name, demuplexer.getSupportedInputContentDescriptors(),
null, PlugInManager.DEMULTIPLEXER);

// Register a plugin to convert from YCbCr to RGB color space


Codec plugin = (Codec) new JavaYUVToRGB();
name = plugin.getClass().getName();
PlugInManager.addPlugIn(name, plugin.getSupportedInputFormats(), plugin
.getSupportedOutputFormats(null), PlugInManager.CODEC);

try {
PlugInManager.commit();
} catch (IOException ex) {
ex.printStackTrace();
}
}

public static void registerH264Encoder() {


// Register a plugin to encode H.264 Video
Codec plugin = (Codec) new H264Encoder();
String name = plugin.getClass().getName();
PlugInManager.addPlugIn(name, plugin.getSupportedInputFormats(), plugin
.getSupportedOutputFormats(null), PlugInManager.CODEC);

// Register the 264 raw byte stream Multiplexer plugin. Allowing JMF write .264
// files
Multiplexer mulplexer = (Multiplexer) new H264Mux();
name = mulplexer.getClass().getName();
PlugInManager.addPlugIn(name, null, mulplexer
.getSupportedOutputContentDescriptors(null), PlugInManager.MULTIPLEXER);

// Register the MP4 File Format Multiplexer plugin. Allowing JMF write .mp4
// files
mulplexer = (Multiplexer) new MP4Mux();
name = mulplexer.getClass().getName();
PlugInManager.addPlugIn(name, null, mulplexer
.getSupportedOutputContentDescriptors(null), PlugInManager.MULTIPLEXER);

try {
PlugInManager.commit();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}

A2 Classes para leitura do arquivo de vídeo YUV

A2.1 Classe YUVParser

package br.ufsc.inf.guiga.media.parser.video;

import java.io.IOException;
39

import javax.media.BadHeaderException;
import javax.media.Control;
import javax.media.Demultiplexer;
import javax.media.IncompatibleSourceException;
import javax.media.Time;
import javax.media.Track;
import javax.media.control.FormatControl;
import javax.media.format.YUVFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.Positionable;
import javax.media.protocol.PullSourceStream;

import br.ufsc.inf.guiga.media.Global;

import com.sun.media.parser.BasicPullParser;

/**
* YCbCr raw video file parser. Extract video track from a raw YUV stream.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class YUVParser extends BasicPullParser implements Positionable {

private static final String PLUGIN_NAME = "YCbCr Parser";


protected static final int VIDEO_TRACK = 0;
protected ContentDescriptor[] supportedFormat;
protected Track[] tracks;
protected PullSourceStream stream;

/**
* Default constructor.
*/
public YUVParser() {
super();
tracks = new Track[1];
supportedFormat = new ContentDescriptor[] { new ContentDescriptor("video.yuv") };
stream = null;
controls = new Control[2];
}

/**
* Gets the duration of this media stream when played at the default rate.
* <p>
* Note that each track can have a different duration and a different start time. This
* method returns the total duration from when the first track starts and the last
* track ends.
*
* @return A Time object that represents the duration or <code>DURATION_UNKNOWN</code>
* if the duration cannot be determined.
*/
public Time getDuration() {
return tracks[VIDEO_TRACK].getDuration();
}

/**
* Gets the current media time. This is the stream position that the next readFrame
* will read.
*
* @return The current position in the media stream as a Time object.
*/
public Time getMediaTime() {
YUVVideoTrack videoTrack = (YUVVideoTrack) tracks[VIDEO_TRACK];
Time t = videoTrack.mapFrameToTime(videoTrack.getCurrentFrame());

return t;
}

/**
* Sets the stream position (media time) to the specified Time. Returns the rounded
* position that was actually set. Implementations should set the position to a key
* frame, if possible.
*
* @param where The new stream position, specified as a Time.
* @param rounding The rounding technique to be used: RoundUp, RoundDown, or
* RoundNearest.
40

* @return The actual stream position that was set as a Time object.
*/
public Time setPosition(Time where, int rounding) {
YUVVideoTrack videoTrack = (YUVVideoTrack) tracks[VIDEO_TRACK];
Time oldTime = videoTrack.mapFrameToTime(videoTrack.getCurrentFrame());
videoTrack.setCurrentFrame(videoTrack.mapTimeToFrame(where));

return oldTime;
}

/**
* Sets the media source this MediaHandler should use to obtain content.
*
* @param source The DataSource used by this MediaHandler.
* @throws java.io.IOException Thrown if there is an error using the DataSource
* @throws javax.media.IncompatibleSourceException Thrown if this MediaHandler cannot
* make use of the DataSource (cannot handle the media).
*/
public void setSource(DataSource source)
throws IOException, IncompatibleSourceException
{
super.setSource(source);

stream = (PullSourceStream) streams[0];

// Set default track properties


String controlName = FormatControl.class.getName();
FormatControl control = (FormatControl) source.getControl(controlName);
YUVFormat yuvFormat = (YUVFormat) control.getFormat();
int totalSize = (int) stream.getContentLength();
int frameSize = YUVFormatHandler.getFrameSize(yuvFormat);
int maxFrameNumber = totalSize / frameSize;
float fps = yuvFormat.getFrameRate();
Time duration = new Time((maxFrameNumber / fps));
Time startTime = new Time(0);
int numBuffer = 1;
boolean enabled = true;

// Set global attributes


Global.getInstance().setTotalFrameNumber(maxFrameNumber);

// Create the video track


YUVVideoTrack videoTrack = new YUVVideoTrack(this, yuvFormat, enabled, duration,
startTime, numBuffer, frameSize, stream);
tracks[VIDEO_TRACK] = videoTrack;
}

/**
* Gets the name of this plug-in as a human-readable string.
*
* @return A String that contains the descriptive name of the plug-in.
*/
public String getName() {
return PLUGIN_NAME;
}

/**
* @return a lists of all of the input content descriptors that this
* {@link Demultiplexer} supports.
*/
public ContentDescriptor[] getSupportedInputContentDescriptors() {
return supportedFormat;
}

/**
* Retrieves the individual tracks that the media stream contains. A stream can
* contain multiple media tracks, such as separate tracks for audio, video, and midi
* data. The information specific to a track is abstracted by an instance of a class
* that implements the {@link Track} interface. The {@link Track} interface also
* provides methods for enabling or disabling a track.
* <p>
* When getTracks is called, the stream header is read and parsed (if there is one),
* the track information is retrieved, the maximum frame size for each track is
* computed, and the play list is built (if applicable).
*
* @return An array of Track objects. The length of the array is equal to the number
41

* of tracks in the stream.


* @throws java.io.IOException If there is an error when trying to read from the
* DataSource.
* @throws javax.media.BadHeaderException If the header information is incomplete or
* inconsistent.
*/
public Track[] getTracks() throws IOException, BadHeaderException {
if (tracks[VIDEO_TRACK] != null)
return tracks;

return tracks;
}

A2.2 Classe YUVVideoTrack

package br.ufsc.inf.guiga.media.parser.video;

import javax.media.Buffer;
import javax.media.Time;
import javax.media.Track;
import javax.media.format.VideoFormat;
import javax.media.format.YUVFormat;
import javax.media.protocol.PullSourceStream;

import com.sun.media.parser.BasicTrack;

/**
* YUV Video Track.
* <p>
* Once a YUV raw video is composed only by video, this is the only track available.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class YUVVideoTrack extends BasicTrack {

protected int currentFrame;


protected int numberOfFrames;
protected int dataSize;

/**
* Create a track to handle YUV video.
*
* @param parser the parser owning this video track
* @param format the YUV Video format
* @param enabled
* @param duration total video duration
* @param startTime
* @param numBuffers
* @param dataSize frame size in bytes. Each time <code>readFrame</code> is called,
* this amount of bytes is read from input stream.
* @param stream
*/
public YUVVideoTrack(
YUVParser parser,
YUVFormat format,
boolean enabled,
Time duration,
Time startTime,
int numBuffers,
int dataSize,
PullSourceStream stream)
{
super(parser, format, enabled, duration, startTime, numBuffers, dataSize, stream);

float fps = ((VideoFormat) getFormat()).getFrameRate();


this.numberOfFrames = (int) (fps * duration.getSeconds());
this.currentFrame = 0;
this.dataSize = dataSize;
}

/**
42

* Gets the Time that corresponds to the specified frame number.


*
* @param frameNumber zero based frame index.
* @return A Time object that corresponds to the specified frame. If the mapping
* cannot be established, <code>TIME_UNKNOWN</code> is returned.
*/
public Time mapFrameToTime(int frameNumber) {
double time = 0d;
if ((frameNumber < 0) || (frameNumber >= numberOfFrames)) {
return Track.TIME_UNKNOWN;
}
time = frameNumber / ((VideoFormat) getFormat()).getFrameRate();
Time t = new Time(time);

return t;
}

/**
* Converts the given media time to the corresponding frame number.
* <p>
* The frame returned is the nearest frame that has a media time less than or equal to
* the given media time.
*
* @param mediaTime the input media time for the conversion.
* @return the converted frame number the given media time. If the conversion fails,
* <code>FRAME_UNKNOWN</code> is returned.
*/
public int mapTimeToFrame(Time mediaTime) {
double time = 0d;
int frameNumber = 0;
time = mediaTime.getSeconds();
if (time < 0.0) {
return Integer.MAX_VALUE;
}
frameNumber = (int) Math.round(time * ((VideoFormat) getFormat()).getFrameRate());
if ((frameNumber < 0) || (frameNumber >= numberOfFrames)) {
return (numberOfFrames - 1);
}
return frameNumber;
}

/**
* Reads the next frame for this Track.
*
* @param buffer The {@link Buffer} into which the data is to be read. If readFrame is
* successful, buffer.getLength returns the length of the data that was
* read.
*/
public void readFrame(Buffer buffer) {
// positioning the stream to read the current frame
setSeekLocation(currentFrame * dataSize);
currentFrame++;
// read frame data into buffer
super.readFrame(buffer);
}

/**
* @return the total number of frames this video contains. This total number is
* calculated through total content size by the frame size.
*/
public int getNumberOfFrames() {
return numberOfFrames;
}

/**
* @return the next frame to be read by this track. Starting from 0 to total number of
* frames.
*/
public int getCurrentFrame() {
return currentFrame;
}

/**
* Set the next frame to be read by this Track.
*
* @param currentFrame the next frame to be read by <code>readFrame</code>.
43

*/
public void setCurrentFrame(int currentFrame) {
this.currentFrame = currentFrame;
}

A2.3 Classe YUVFormatHandler

package br.ufsc.inf.guiga.media.parser.video;

import java.awt.Dimension;

import javax.media.format.YUVFormat;

/**
* Contains standard YUV formats and utilities.
*
* @author Guilherme
*/
public class YUVFormatHandler {

public static Dimension SQCIF = new Dimension(128, 96);


public static Dimension QCIF = new Dimension(176, 144);
public static Dimension CIF = new Dimension(352, 288);
public static Dimension _4CIF = new Dimension(704, 576);

public static YUVFormat getSQCIF(int yuvType, float frameRate) {


return new YUVFormat(SQCIF, 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0,
0, 0);
}

public static YUVFormat getQCIF(int yuvType, float frameRate) {


return new YUVFormat(QCIF, 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0,
0, 0);
}

public static YUVFormat getCIF(int yuvType, float frameRate) {


return new YUVFormat(CIF, 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0, 0,
0);
}

public static YUVFormat get4CIF(int yuvType, float frameRate) {


return new YUVFormat(_4CIF, 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0,
0, 0);
}

public static YUVFormat getSizeableCIF(Dimension size, int yuvType, float frameRate) {


return new YUVFormat(size, 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0,
0, 0);
}

/**
* Compute the amount of bytes required to store a frame in YCbCr with a given format
* (e.g. 4:2:2, 4:2:0, 4:1:1).
*
* @param videoFormat the {@link YUVFormat} of this frame.
* @return the amount of bytes required to store a YUV frame with such format.
*/
public static int getFrameSize(YUVFormat videoFormat) {
int symbolSizeInBytes = 1; // 8-bit for each Y, Cb and Cr pixel

int frameWidth = videoFormat.getSize().width;


int frameHeight = videoFormat.getSize().height;
int frameChromaWidth = videoFormat.getSize().width / 2;
int frameChromaHeight = videoFormat.getSize().height / 2;

int imgSizeY = frameWidth * frameHeight;


int imgSizeUV = frameChromaWidth * frameChromaHeight;
int bytesY = imgSizeY * symbolSizeInBytes;
int bytesUV = imgSizeUV * symbolSizeInBytes;
int frameSizeInBytes = bytesY + 2 * bytesUV;
44

return frameSizeInBytes;
}

A2.4 Classe YUVFrameBuffer

package br.ufsc.inf.guiga.media.parser.video;

import javax.media.format.YUVFormat;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.FrameBuffer;

/**
* Buffer for YUV color space video frame.
* <p>
* This class holds separate arrays for each YCbCr component, allowing to access individual
* components for each pixel.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class YUVFrameBuffer extends FrameBuffer {
private int[][] Y;
private int[][] Cr;
private int[][] Cb;
protected int uvWidth;
protected int uvHeight;

/**
* Creates a YUV frame buffer from a {@link YUVFormat}, specifying width and height for
* luma and chroma.
*
* @param format the {@link YUVFormat} of this frame.
*/
public YUVFrameBuffer(YUVFormat format) {
super(format);

int uvWidth = 0;
int uvHeight = 0;

switch (format.getYuvType()) {
case YUVFormat.YUV_420:
uvWidth = format.getSize().width / 2;
uvHeight = format.getSize().height / 2;
break;
}

init(format.getSize().width, format.getSize().height, uvWidth, uvHeight);


}

protected void init(int width, int height, int uvWidth, int uvHeight) {
this.uvWidth = uvWidth;
this.uvHeight = uvHeight;

Y = new int[height][width];
Cr = new int[uvHeight][uvWidth];
Cb = new int[uvHeight][uvWidth];
}

/**
* @return the chroma frame width.
*/
public int getUVWidth() {
return uvWidth;
}

/**
* @return the chroma frame height.
*/
public int getUVHeight() {
return uvHeight;
}
45

/**
* Initializes the Y, Cb and Cr arrays, coping the parameter to the internal buffer.
*
* @param data the YCbCr frame. The first part of data is Y array, the second part of
data
* is the Cb and then, the next is Cr. Each part size depends on YUV format
* selected. For example, in 4:2:0 format, Y has width * height size, and Cb
and
* Cr has one fourth of Y area each.
*/
public void setData(byte[] data) {
int indexY, indexUV; // YUV indexes
int cbOffset = getWidth() * getHeight();
int crOffset = cbOffset + (uvWidth * uvHeight);

// the Luma component vector to matrix copy


for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
// calculate array components indexes
indexY = x + y * getWidth();
// set the Y component
Y[y][x] = (data[indexY] & 0xFF);
}
}

// the Chroma component vector to matrix copy


for (int y = 0; y < getUVHeight(); y++) {
for (int x = 0; x < getUVWidth(); x++) {
// calculate array components indexes
indexUV = x + y * getUVWidth();
// set the Cr and Cb components
Cb[y][x] = (data[indexUV + cbOffset] & 0xFF);
Cr[y][x] = (data[indexUV + crOffset] & 0xFF);
}
}
}

/**
* Copy the internal Y, Cb and Cr arrays to the parameter.
*
* @param data The first part of data is Y array, the second part of data is the Cb and
* then, the next is Cr. Each part size depends on YUV format selected.
*/
public byte[] getData() {
return null;
}

/**
* Copy the parameter Y, Cb and Cr arrays to this object Y, Cb and Cr internal arrays.
*
* @param other another {@link YUVFrameBuffer}.
*/
public void copyData(FrameBuffer other) {
YUVFrameBuffer otherYUV = (YUVFrameBuffer) other;

// verify if luma size match


if ((other.getWidth() > getWidth()) || (other.getHeight() > getHeight())) {
Y = new int[other.getHeight()][other.getWidth()];
}
// verify if chroma size match
if ((otherYUV.getUVWidth() > getUVWidth())
|| (otherYUV.getUVHeight() > getUVHeight()))
{
Cb = new int[otherYUV.getUVHeight()][otherYUV.getUVWidth()];
Cr = new int[otherYUV.getUVHeight()][otherYUV.getUVWidth()];
}

System.arraycopy(otherYUV.Y, 0, Y, 0, Y.length);
System.arraycopy(otherYUV.Cb, 0, Cb, 0, Cb.length);
System.arraycopy(otherYUV.Cr, 0, Cr, 0, Cr.length);
}

/**
* Gets an 8-bit luma pixel value at a given position.
*
* @param x horizontal pixel position.
46

* @param y vertical pixel position.


* @return an eight bit length value of Y component at specific (x,y) position in the
* frame.
*/
public int getY8bit(int x, int y) {
return Y[y][x];
}

/**
* Sets an 8-bit luma pixel value at a given position.
*
* @param x horizontal pixel position.
* @param y vertical pixel position.
* @param value an eight bit length value of Y component at specific (x,y) position in
the
* frame.
*/
public void setY8bit(int x, int y, int value) {
Y[y][x] = value;
}

/**
* Gets an 8-bit chroma pixel value at a given position.
*
* @param x horizontal pixel position.
* @param y vertical pixel position.
* @return an eight bit length value of Cb component at specific (x,y) position in the
* frame.
*/
public int getCb8bit(int x, int y) {
return Cb[y][x];
}

/**
* Sets an 8-bit chroma pixel value at a given position.
*
* @param x horizontal pixel position.
* @param y vertical pixel position.
* @param value an eight bit length value of Cb component at specific (x,y) position in
the
* frame.
*/
public void setCb8bit(int x, int y, int value) {
Cb[y][x] = value;
}

/**
* Gets an 8-bit chroma pixel value at a given position.
*
* @param x horizontal pixel position.
* @param y vertical pixel position.
* @return an eight bit length value of Cr component at specific (x,y) position in the
* frame.
*/
public int getCr8bit(int x, int y) {
return Cr[y][x];
}

/**
* Sets an 8-bit chroma pixel value at a given position.
*
* @param x horizontal pixel position.
* @param y vertical pixel position.
* @param value an eight bit length value of Cr component at specific (x,y) position in
* the frame.
*/
public void setCr8bit(int x, int y, int value) {
Cr[y][x] = value;
}

}
47

A3 Classes para escrita no arquivo H.264

A3.1 Classe H264Mux

package br.ufsc.inf.guiga.media.multiplexer.video;

import java.io.IOException;
import java.util.List;

import javax.media.Buffer;
import javax.media.Format;
import javax.media.Multiplexer;
import javax.media.PlugIn;
import javax.media.protocol.ContentDescriptor;

import br.ufsc.inf.guiga.media.Global;
import br.ufsc.inf.guiga.media.codec.video.h264.nal.NALU;
import br.ufsc.inf.guiga.media.codec.video.h264.nal.NALUByteStream;
import br.ufsc.inf.guiga.media.codec.video.h264.nal.datavalue.NalRefIdc;
import br.ufsc.inf.guiga.media.codec.video.h264.nal.datavalue.NaluType;
import br.ufsc.inf.guiga.media.codec.video.h264.parset.PictureParameterSet;
import br.ufsc.inf.guiga.media.codec.video.h264.parset.SequenceParameterSet;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Picture;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Slice;
import br.ufsc.inf.guiga.media.control.H264Control;
import br.ufsc.inf.guiga.media.format.H264Format;
import br.ufsc.inf.guiga.media.parser.video.YUVFormatHandler;
import br.ufsc.inf.guiga.media.util.io.BitOutputStream;
import br.ufsc.inf.guiga.media.util.io.BitOutputStreamImpl;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStreamImpl;

import com.sun.media.multiplexer.BasicMux;

/**
* Multiplexes the H.264 Video Track into a Raw Bit Stream as specified by Annex B from
* H.264 International Standard.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class H264Mux extends BasicMux {

private static final String PLUGIN_NAME = "H.264/AVC Annex B Byte Stream Multiplexer";
private static final String MIME_TYPE = "video.264";

private SequenceParameterSet sps;


private PictureParameterSet pps;

/**
* Creates a H.264 Byte Stream Multiplexer.
*/
public H264Mux() {
super.supportedOutputs = new ContentDescriptor[] { new ContentDescriptor(
MIME_TYPE) };
super.supportedInputs = new Format[] {
new H264Format(YUVFormatHandler.SQCIF, 18432, 15),
new H264Format(YUVFormatHandler.QCIF, 38016, 15),
new H264Format(YUVFormatHandler.CIF, 152064, 15),
new H264Format(YUVFormatHandler._4CIF, 608256, 15) };
}

/**
* Gets the name of this plug-in as a human-readable string.
*
* @return A String that contains the descriptive name of the plug-in.
*/
public String getName() {
return PLUGIN_NAME;
}

/**
* Sets the input {@link Format} for the specified track.
*
48

* @param input The input {@link Format} of the specified track.


* @param trackID The index number of the track for which the {@link Format} is being
* set.
* @return The {@link Format} preferred by the {@link Multiplexer}. This might the
* same as the specified {@link Format}, a more well-defined {@link Format}
* than was specified, or null if the specified {@link Format} is not
* supported by the {@link Multiplexer} at all.
*/
public Format setInputFormat(Format input, int trackID) {
H264Control control = null;

if (input instanceof H264Format) {


H264Format h264Format = (H264Format) input;
int maxFrameNumber = Global.getInstance().getTotalFrameNumber();

sps = new SequenceParameterSet(h264Format, control, maxFrameNumber);


pps = new PictureParameterSet(sps);

return super.setInputFormat(input, trackID);


} else {
return null;
}
}

/**
* Processes the input {@link Buffer} and multiplexes it with data from other tracks.
* The multiplexed output is sent to an output DataSource.
* <p>
* This multiplexer simply write the raw H.264 video stream into the file.
*
* @param buffer The Buffer of data to process.
* @param trackID The index number of the track to which the input Buffer belongs.
* @return <code>BUFFER_PROCESSED_OK</code> If the processing is successful. Other
* possible return codes are defined in {@link PlugIn}.
*/
protected int doProcess(Buffer buffer, int trackID) {
try {
BitOutputStream bitstream = new BitOutputStreamImpl();
H264EntropyOutputStream stream = new H264EntropyOutputStreamImpl(bitstream,
sps, pps);

// retrieve the coded frame/picture


Picture picture = (Picture) buffer.getData();

// get the nal_unit_type and nal_ref_idc based on picture type


NaluType naluType = picture.isIDR() ? NaluType.NALU_TYPE_IDR
: NaluType.NALU_TYPE_SLICE;
NalRefIdc nalRefIdc = picture.isIDR() ? NalRefIdc.NALU_PRIORITY_HIGHEST
: NalRefIdc.NALU_PRIORITY_HIGH;

// retrieve all slices from the picture


List<Slice> slices = picture.getSlices();
for (Slice slice : slices) {
// write the slice
NALU nalu = new NALUByteStream(slice, naluType, nalRefIdc);
nalu.write(stream);
}

// write it all to the output


writeStream(bitstream);

} catch (IOException e) {
e.printStackTrace();
}
return BUFFER_PROCESSED_OK;
}

/**
* This method generates the appropriate sequence header.
*/
protected void writeHeader() {
try {
BitOutputStream bitstream = new BitOutputStreamImpl();
H264EntropyOutputStream stream = new H264EntropyOutputStreamImpl(bitstream,
sps, pps);
49

// write Sequence Parameter Set


NALU nalu = new NALUByteStream(sps, NaluType.NALU_TYPE_SPS,
NalRefIdc.NALU_PRIORITY_HIGHEST);
nalu.write(stream);

// write now the Picture Parameter Set (only one for now).
nalu = new NALUByteStream(pps, NaluType.NALU_TYPE_PPS,
NalRefIdc.NALU_PRIORITY_HIGHEST);
nalu.write(stream);

// write it all to the output


writeStream(bitstream);

} catch (IOException e) {
e.printStackTrace();
}
}

protected void writeStream(BitOutputStream stream) {


byte[] streamArray = stream.toByteArray();
write(streamArray, 0, streamArray.length);
}

A3.2 Classe NALUByteStream

package br.ufsc.inf.guiga.media.codec.video.h264.nal;

import java.io.IOException;

import br.ufsc.inf.guiga.media.codec.video.h264.nal.datavalue.NalRefIdc;
import br.ufsc.inf.guiga.media.codec.video.h264.nal.datavalue.NaluType;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* This class implements the NAL Unit from Annex B of ITU-T Recommendation
* H.264.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class NALUByteStream extends NALU {

/**
* @see br.ufsc.inf.guiga.media.codec.video.h264.nal.NALU#NALU(RBSP,
* NaluType, NalRefIdc)
*/
public NALUByteStream(
RBSP rbsp,
NaluType nalUnitType,
NalRefIdc nalReferenceIdc)
{
super(rbsp, nalUnitType, nalReferenceIdc);
}

/**
* Writes an Annex B Byte Stream NAL Unit.
*
* @param output
* the output video stream, where this NAL Unit will be write.
* @return number of bits written
*/
protected int doWrite(H264EntropyOutputStream output) throws IOException {
int bitsWritten = 0;

assert (forbidden_zero_bit == 0);


assert (startcodeprefix_len == 3 || startcodeprefix_len == 4);

// write the NALU Start Code Prefix


if (startcodeprefix_len > 3) {
output.write_u_v(8, 0);
}
output.write_u_v(8, 0);
50

output.write_u_v(8, 0);
output.write_u_v(8, 1);

// set the forbidden_zero_bit, nal_ref_idc and the nal_unit_type


output.write_u_v(8, (forbidden_zero_bit << 7)
| (nal_ref_idc.value() << 5) | nal_unit_type.value());

// write the Raw Byte Sequence Payload


writeRBSP(rbsp, output);

bitsWritten = output.size();

return bitsWritten;
}

A3.3 Classe NALU

package br.ufsc.inf.guiga.media.codec.video.h264.nal;

import java.io.IOException;
import java.io.OutputStream;

import br.ufsc.inf.guiga.media.codec.video.h264.nal.datavalue.NalRefIdc;
import br.ufsc.inf.guiga.media.codec.video.h264.nal.datavalue.NaluType;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* NAL Unit.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public abstract class NALU {

// private byte[] payloadBuffer; // used as temporary buffer to store data.

// 4 for parameter sets and first slice in picture


// 3 for everything else (suggested)
protected int startcodeprefix_len;
// Length of the NAL unit (Excluding the start code, which does not belong
// to the NALU)
protected NaluType nal_unit_type; // NALU_TYPE_xxxx (NaluType)
protected NalRefIdc nal_ref_idc; // NALU_PRIORITY_xxxx (NalRefIdc)
protected byte forbidden_zero_bit; // should be always zero
protected RBSP rbsp; // NAL Unit Payload

protected boolean useAnnexbLongStartcode;

/**
* Creates a NAL Unit containing the given {@link RBSP}.
*
* @param rbsp An object that implements the {@link RBSP} and whom the data will be
* wrap by this NAL Unit.
* @param nalUnitType The {@link NaluType} value that specify the type of payload
* within this NAL unit.
* @param nalReferenceIdc One of {@link NalRefIdc} values to specify the picture
* priority.
*/
public NALU(RBSP rbsp, NaluType nalUnitType, NalRefIdc nalReferenceIdc) {
this.rbsp = rbsp;
this.forbidden_zero_bit = 0;
this.nal_unit_type = nalUnitType;
this.nal_ref_idc = nalReferenceIdc;
this.useAnnexbLongStartcode = true;
this.startcodeprefix_len = useAnnexbLongStartcode ? 4 : 3;

// payloadBuffer = new byte[126720];


}

/**
* Writes a NALU to an {@link OutputStream}.
*
* @param output the output stream where this NAL Unit data will be write.
51

* @return number of bits written.


* @throws IOException Danger Will Robinson, Danger!
*/
public int write(H264EntropyOutputStream output) throws IOException {
output.setNalu(this);

return doWrite(output);
}

/**
* This method must be overwrite in subclasses in order to write the RBSP
*
* @param output the output stream where the NALU subclass data will be write.
* @return number of bits written.
* @throws IOException
*/
protected abstract int doWrite(H264EntropyOutputStream output) throws IOException;

/**
* Converts an RBSP to a NALU.
*
* @param rbsp byte buffer with the Raw Byte Sequence Payload (RBSP)
* @param output bit stream where the RBSP will be write.
* @return length of the NALU in bytes.
*/
protected int writeRBSP(RBSP rbsp, H264EntropyOutputStream output) {
// delegate the payload write to the RBSP object
int rbsp_size = rbsp.write(output);

// check if it isn't too big :-)


if (rbsp_size < RBSP.MAXRBSPSIZE) {
// TODO throw an exception. PayloadOverloadedException?
}

// check if there isn't any start code prefix within the RBSP
int length = 1 + preventEmulationOfStartCode(output, 0);

return length;
}

/**
* This function converts a RBSP payload to an EBSP (Encapsulated Byte Stream Payload)
* payload, preventing that no sequence of consecutive byte-aligned bytes in the NAL
* unit contains a start code prefix.
* <p>
* This is done by placing an emulation prevention byte, a byte equal to 0x03 that may
* be present within a NAL unit.
*
* @param output data bits
* @param beginBytePos The byte position after start-code, after which stuffing to
* prevent start-code emulation begins.
* @return size of <code>output</code> after stuffing.
*/
protected int preventEmulationOfStartCode(
H264EntropyOutputStream output, int beginBytePos)
{
int endBytePos = output.size() / 8;

int i, j;

// System.arraycopy(
// output.toByteArray(), beginBytePos, payloadBuffer, beginBytePos,
// (endBytePos - beginBytePos));

j = beginBytePos;
for (i = beginBytePos; i < endBytePos; i++) {
// TODO must implement, but I didn't find any file that need this
// emulation byte
j++;
}

return j;
}

/**
* @return the {@link NaluType} of this NAL Unit.
52

*/
public NaluType getNalUnitType() {
return nal_unit_type;
}

/**
* @return the {@link NalRefIdc} of this NAL Unit.
*/
public NalRefIdc getNalRefIdc() {
return nal_ref_idc;
}

A4 Classes de codificação 1 – Controle

A4.1 Classe H264Encoder

package br.ufsc.inf.guiga.media.codec.video.h264;

import java.util.logging.Handler;
import java.util.logging.Logger;

import javax.media.Buffer;
import javax.media.Control;
import javax.media.Format;
import javax.media.format.YUVFormat;

import br.ufsc.inf.guiga.media.Global;
import br.ufsc.inf.guiga.media.Registry;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Picture;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.VideoSequence;
import br.ufsc.inf.guiga.media.control.H264Adapter;
import br.ufsc.inf.guiga.media.control.H264Control;
import br.ufsc.inf.guiga.media.format.H264Format;
import br.ufsc.inf.guiga.media.parser.video.YUVFormatHandler;

import com.sun.media.BasicCodec;

/**
* H.264/AVC Encoder.
* <p>
* Process YUV video data, encoding into H.264/AVC coded video data.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class H264Encoder extends BasicCodec {

private static final String PLUGIN_NAME = "H.264/AVC Encoder";


private VideoSequence videoSequence;
private Logger logger;

// -------------------------------------------------------------------------
// DATA INITIALIZATION
// -------------------------------------------------------------------------

/**
* Register this encoder within JMF
*/
public static void register() {
Registry.registerPackage();
Registry.registerYUVHandler();
Registry.registerH264Encoder();
}

/**
* Create a H.264 Encoder with default configuration.
*/
public H264Encoder() {
init();
53

/**
* Create a H.264 Encoder with default configuration and a given {@link Handler} to
* log the messages from this encoder.
*
* @param handler a {@link Handler} to print log messages.
*/
public H264Encoder(Handler handler) {
init();

logger.addHandler(handler);
}

private void init() {


YUVFormat sqcif = YUVFormatHandler.getSQCIF(YUVFormat.YUV_420, 15);
YUVFormat qcif = YUVFormatHandler.getQCIF(YUVFormat.YUV_420, 15);
YUVFormat cif = YUVFormatHandler.getCIF(YUVFormat.YUV_420, 15);
YUVFormat _4cif = YUVFormatHandler.get4CIF(YUVFormat.YUV_420, 15);

inputFormats = new Format[] { sqcif, qcif, cif, _4cif };

outputFormats = new Format[] { H264Format.getMatchFormat(sqcif),


H264Format.getMatchFormat(qcif), H264Format.getMatchFormat(cif),
H264Format.getMatchFormat(_4cif) };

Global.getInstance().setH264Control(new H264Adapter());

logger = Logger.getLogger(getClass().getSimpleName());
}

// -------------------------------------------------------------------------
// DATA FORMAT CONFIGURATION
// -------------------------------------------------------------------------

/**
* Lists the output formats that this CODEC can generate. If input is non-null, this
* method lists the possible output formats that can be generated from input data of
* the specified {@link Format}. If input is <code>null</code>, this method lists
* all of the output formats supported by this plug-in.
*
* @param input The {@link Format} of the data to be used as input to the plug-in.
* @return An array that contains the supported output Formats.
*/
public Format[] getSupportedOutputFormats(Format input) {
Format[] supportedFormats = {};

if (input == null) {
supportedFormats = outputFormats;
} else {
if (input instanceof YUVFormat) {
supportedFormats = new Format[1];
supportedFormats[0] = H264Format.getMatchFormat((YUVFormat) input);
}
}

return supportedFormats;
}

/**
* Sets the format of the data to be input to this CODEC.
*
* @param format The {@link Format} to be set.
* @return The {@link Format} that was set, which might be the supported
* {@link Format} that most closely matches the one specified. Returns null if
* the specified {@link Format} is not supported and no reasonable match could
* be found.
*/
public Format setInputFormat(Format input) {
if (input instanceof YUVFormat) {
videoSequence = new VideoSequence((YUVFormat) input);

return super.setInputFormat(input);
} else {
return null;
}
54

/**
* Gets the name of this plug-in as a human-readable string.
*
* @return A String that contains the descriptive name of the plug-in.
*/
public String getName() {
return PLUGIN_NAME;
}

/**
* Obtain the collection of objects that control the object that implements this
* interface. If no controls are supported, a zero length array is returned.
*
* @return the collection of object controls
*/
public Object[] getControls() {
Control[] controls = { Global.getInstance().getH264Control() };

return controls;
}

/**
* Obtain the object that implements the specified <code>Class</code> or
* <code>Interface</code> The full class or interface name must be used. If the
* control is not supported then <code>null</code> is returned.
*
* @return the object that implements the control, or <code>null</code>.
*/
public Object getControl(String controlType) {
Object cls = null;

try {
cls = Class.forName(controlType);

} catch (ClassNotFoundException e) {
return null;
}

if (cls instanceof H264Control) {


return Global.getInstance().getH264Control();
}

return null;
}

// -------------------------------------------------------------------------
// DATA PROCESSING
// -------------------------------------------------------------------------

/**
* Converts a byte array Buffer of raw YUV video data into an byte array Buffer of
* H.264/AVC (MPEG-4 Part 10) video data. Always inputs one raw video frame and
* outputs one coded frame.
*
* @return <code>BUFFER_PROCESSED_OK</code> The output buffer contains a valid coded
* frame <br>
* <code>BUFFER_PROCESSED_FAILED</code> A encoding problem was encountered
*/
public int process(Buffer input, Buffer output) {
// look for a valid input buffer
if (!isInputSupported(input.getFormat())) {
return BUFFER_PROCESSED_FAILED;
}

// does this Buffer marks the End Of Media?


if (input.isEOM()) {
System.err.printf("Uncoded bytes left in input buffer: %d ", input
.getLength());
return BUFFER_PROCESSED_FAILED;
}

// code one frame, this method is called for each input frame read
encodeData(input, output);
55

logger.info("Coded Frame\n"); // TODO Required better log, it's just a test

return BUFFER_PROCESSED_OK;
}

/**
* @param inputBuffer
* @param outputBuffer
*/
private void encodeData(Buffer inputBuffer, Buffer outputBuffer) {
byte[] inputData = (byte[]) inputBuffer.getData();
Picture outputData = videoSequence.encode(inputData);
outputBuffer.setData(outputData);
}

// -------------------------------------------------------------------------
// DATA VERIFICATION AND VALIDATION
// -------------------------------------------------------------------------

/**
* Check if the input format is supported by this CODEC.
*/
private boolean isInputSupported(Format format) {
boolean supported = false;
for (int i = 0; i < inputFormats.length; i++) {
if (inputFormats[i].matches(format)) {
supported = true;
}
}
return supported;
}

A4.2 Classe BaselineProfileFactory

package br.ufsc.inf.guiga.media.codec.video.h264;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.AlgorithmFactory;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Quantizer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Scanner;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Transform;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.impl.IntegerRoundingQuantizer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.impl.IntegerTransform;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.impl.ZigZagFrameScanner;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.decision.DistortionMetric;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.decision.SATD;

public class BaselineProfileFactory implements ProfileFactory, AlgorithmFactory {

private Transform transform;


private Quantizer quantizer;
private Scanner scanner;
private DistortionMetric distortionMetric;

/**
* @param qp the Quantization Parameter used by the macroblock.
*/
public BaselineProfileFactory(int qp) {
transform = new IntegerTransform();
scanner = new ZigZagFrameScanner();
quantizer = new IntegerRoundingQuantizer(qp, scanner);
distortionMetric = new SATD(transform);
}

public Quantizer createQuantizer() {


return quantizer;
}

public Scanner createScanner() {


return scanner;
}

public Transform createTransform() {


56

return transform;
}

public DistortionMetric createDistortionMetric() {


return distortionMetric;
}

A5 Classes de codificação 2 – Algoritmos

A5.1 Classe IntegerTransform

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.impl;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Transform;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.block.Block;

/**
* This class implements the transform operations using only integer arithmetic's. The
* transforms are orthogonal approximations of DCT.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class IntegerTransform extends Transform {

private int[] tmp; // temporary transformed samples

public IntegerTransform() {
tmp = new int[64];
}

@Override
public void forward4x4(int[][] src, int[][] dst, int pos_y, int pos_x) {
int ii;
int p0, p1, p2, p3;
int t0, t1, t2, t3;
int tmpIdx = 0;

// Horizontal
for (int i = pos_y; i < pos_y + Block.BLOCK_4x4_SIZE; i++) {
p0 = src[i][pos_x + 0];
p1 = src[i][pos_x + 1];
p2 = src[i][pos_x + 2];
p3 = src[i][pos_x + 3];

t0 = p0 + p3;
t1 = p1 + p2;
t2 = p1 - p2;
t3 = p0 - p3;

tmp[tmpIdx++] = t0 + t1;
tmp[tmpIdx++] = (t3 << 1) + t2;
tmp[tmpIdx++] = t0 - t1;
tmp[tmpIdx++] = t3 - (t2 << 1);
}

// Vertical
for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
tmpIdx = i;
p0 = tmp[tmpIdx];
p1 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
p2 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
p3 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];

t0 = p0 + p3;
t1 = p1 + p2;
t2 = p1 - p2;
t3 = p0 - p3;
57

ii = pos_x + i;
dst[pos_y + 0][ii] = t0 + t1;
dst[pos_y + 1][ii] = t2 + (t3 << 1);
dst[pos_y + 2][ii] = t0 - t1;
dst[pos_y + 3][ii] = t3 - (t2 << 1);
}
}

@Override
public void inverse4x4(int[][] src, int[][] dst, int pos_y, int pos_x) {
int ii;
int p0, p1, p2, p3;
int t0, t1, t2, t3;
int tmpIdx = 0;

// Horizontal
for (int i = pos_y; i < pos_y + Block.BLOCK_4x4_SIZE; i++) {
t0 = src[i][pos_x + 0];
t1 = src[i][pos_x + 1];
t2 = src[i][pos_x + 2];
t3 = src[i][pos_x + 3];

p0 = t0 + t2;
p1 = t0 - t2;
p2 = (t1 >> 1) - t3;
p3 = t1 + (t3 >> 1);

tmp[tmpIdx++] = p0 + p3;
tmp[tmpIdx++] = p1 + p2;
tmp[tmpIdx++] = p1 - p2;
tmp[tmpIdx++] = p0 - p3;
}

// Vertical
for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
tmpIdx = i;
t0 = tmp[tmpIdx];
t1 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
t2 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
t3 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];

p0 = t0 + t2;
p1 = t0 - t2;
p2 = (t1 >> 1) - t3;
p3 = t1 + (t3 >> 1);

ii = pos_x + i;
dst[pos_y + 0][ii] = p0 + p3;
dst[pos_y + 1][ii] = p1 + p2;
dst[pos_y + 2][ii] = p1 - p2;
dst[pos_y + 3][ii] = p0 - p3;
}
}

@Override
public void hadamard4x4(int[][] src, int[][] dst) {
int p0, p1, p2, p3;
int t0, t1, t2, t3;
int tmpIdx = 0;

// Horizontal
for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
p0 = src[i][0];
p1 = src[i][1];
p2 = src[i][2];
p3 = src[i][3];

t0 = p0 + p3;
t1 = p1 + p2;
t2 = p1 - p2;
t3 = p0 - p3;

tmp[tmpIdx++] = t0 + t1;
tmp[tmpIdx++] = t3 + t2;
tmp[tmpIdx++] = t0 - t1;
tmp[tmpIdx++] = t3 - t2;
58

// Vertical
for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
tmpIdx = i;
p0 = tmp[tmpIdx];
p1 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
p2 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
p3 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];

t0 = p0 + p3;
t1 = p1 + p2;
t2 = p1 - p2;
t3 = p0 - p3;

dst[0][i] = (t0 + t1) >> 1;


dst[1][i] = (t2 + t3) >> 1;
dst[2][i] = (t0 - t1) >> 1;
dst[3][i] = (t3 - t2) >> 1;
}
}

@Override
public void ihadamard4x4(int[][] src, int[][] dst) {
int p0, p1, p2, p3;
int t0, t1, t2, t3;
int tmpIdx = 0;

// Horizontal
for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
t0 = src[i][0];
t1 = src[i][1];
t2 = src[i][2];
t3 = src[i][3];

p0 = t0 + t2;
p1 = t0 - t2;
p2 = t1 - t3;
p3 = t1 + t3;

tmp[tmpIdx++] = p0 + p3;
tmp[tmpIdx++] = p1 + p2;
tmp[tmpIdx++] = p1 - p2;
tmp[tmpIdx++] = p0 - p3;
}

// Vertical
for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
tmpIdx = i;

t0 = tmp[tmpIdx];
t1 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
t2 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];
t3 = tmp[tmpIdx += Block.BLOCK_4x4_SIZE];

p0 = t0 + t2;
p1 = t0 - t2;
p2 = t1 - t3;
p3 = t1 + t3;

dst[0][i] = p0 + p3;
dst[1][i] = p1 + p2;
dst[2][i] = p1 - p2;
dst[3][i] = p0 - p3;
}
}

@Override
public void hadamard2x2(int[][] src, int[][] dst) {
int p0, p1, p2, p3;

p0 = src[0][0] + src[0][1];
p1 = src[0][0] - src[0][1];
p2 = src[1][0] + src[1][1];
p3 = src[1][0] - src[1][1];
59

dst[0][0] = (p0 + p2);


dst[0][1] = (p1 + p3);
dst[1][0] = (p0 - p2);
dst[1][1] = (p1 - p3);
}

@Override
public void ihadamard2x2(int[][] src, int[][] dst) {
int t0, t1, t2, t3;

t0 = src[0][0] + src[0][1];
t1 = src[0][0] - src[0][1];
t2 = src[1][0] + src[1][1];
t3 = src[1][0] - src[1][1];

dst[0][0] = (t0 + t2);


dst[0][1] = (t1 + t3);
dst[1][0] = (t0 - t2);
dst[1][1] = (t1 - t3);
}

A5.2 Classe ZigZagFrameScanner

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.impl;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Scanner;

public class ZigZagFrameScanner implements Scanner {

private int[][] scanOrder = { // [coeff: one out of 16 coefficients][comp: x or y]


{ 0, 0 }, { 1, 0 }, { 0, 1 }, { 0, 2 }, { 1, 1 }, { 2, 0 }, { 3, 0 }, { 2, 1 }, { 1,
2 },
{ 0, 3 }, { 1, 3 }, { 2, 2 }, { 3, 1 }, { 3, 2 }, { 2, 3 }, { 3, 3 } };

public void reorder4x4(


int[][] srcCoeff, int[] dstLevel, int[] dstRun, int start, int length,
int yOffset, int xOffset)
{
int level;
int run = -1;
int scanPos = 0;

for (int coeff = start; coeff < length; coeff++) {


int i = positionToCoordinateX(coeff) + xOffset;
int j = positionToCoordinateY(coeff) + yOffset;

run++;
level = srcCoeff[j][i];

if (level != 0) {
dstLevel[scanPos] = level;
dstRun[scanPos] = run;

scanPos++;

run = -1;
}
}

public void reorder2x2(


int[][] srcCoeff, int[] dstLevel, int[] dstRun, int start, int length,
int yOffset, int xOffset)
{
int level;
int run = -1;
int scanPos = 0;

for (int coeff = start; coeff < length; coeff++) {


int i = (coeff % 2) + xOffset;
int j = (coeff / 2) + yOffset;
60

run++;
level = srcCoeff[j][i];

if (level != 0) {
dstLevel[scanPos] = level;
dstRun[scanPos] = run;

scanPos++;

run = -1;
}
}
}

public int positionToCoordinateX(int pos) {


return scanOrder[pos][0];
}

public int positionToCoordinateY(int pos) {


return scanOrder[pos][1];
}

A5.3 Classe IntegerRoundQuantizer

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.impl;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Quantizer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Scanner;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.block.Block;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.entropy.CAVLC;
import br.ufsc.inf.guiga.media.util.SupportMath;

public class IntegerRoundingQuantizer extends Quantizer {

// -------------------------------------------------------------------------
// Definitions
// -------------------------------------------------------------------------

private static final int MAX_QP = 51;


private static final int Q_BITS = 15;

// Multiplication Factor, quantizer coefficients [QP][j][i] <-> [6][4][4]


private static final int quantCoeff[][][] = {
{ { 13107, 8066, 13107, 8066 }, { 8066, 5243, 8066, 5243 },
{ 13107, 8066, 13107, 8066 }, { 8066, 5243, 8066, 5243 } },
{ { 11916, 7490, 11916, 7490 }, { 7490, 4660, 7490, 4660 },
{ 11916, 7490, 11916, 7490 }, { 7490, 4660, 7490, 4660 } },
{ { 10082, 6554, 10082, 6554 }, { 6554, 4194, 6554, 4194 },
{ 10082, 6554, 10082, 6554 }, { 6554, 4194, 6554, 4194 } },
{ { 9362, 5825, 9362, 5825 }, { 5825, 3647, 5825, 3647 },
{ 9362, 5825, 9362, 5825 }, { 5825, 3647, 5825, 3647 } },
{ { 8192, 5243, 8192, 5243 }, { 5243, 3355, 5243, 3355 },
{ 8192, 5243, 8192, 5243 }, { 5243, 3355, 5243, 3355 } },
{ { 7282, 4559, 7282, 4559 }, { 4559, 2893, 4559, 2893 },
{ 7282, 4559, 7282, 4559 }, { 4559, 2893, 4559, 2893 } } };

// Scaling factor, dequantizer coefficients [QP][j][i] <-> [6][4][4]


private static final int dequantCoeff[][][] = {
{ { 10, 13, 10, 13 }, { 13, 16, 13, 16 }, { 10, 13, 10, 13 },
{ 13, 16, 13, 16 } },
{ { 11, 14, 11, 14 }, { 14, 18, 14, 18 }, { 11, 14, 11, 14 },
{ 14, 18, 14, 18 } },
{ { 13, 16, 13, 16 }, { 16, 20, 16, 20 }, { 13, 16, 13, 16 },
{ 16, 20, 16, 20 } },
{ { 14, 18, 14, 18 }, { 18, 23, 18, 23 }, { 14, 18, 14, 18 },
{ 18, 23, 18, 23 } },
{ { 16, 20, 16, 20 }, { 20, 25, 20, 25 }, { 16, 20, 16, 20 },
{ 20, 25, 20, 25 } },
{ { 18, 23, 18, 23 }, { 23, 29, 23, 29 }, { 18, 23, 18, 23 },
{ 23, 29, 23, 29 } } };
61

private static final short offset4x4Intra[][] = {


{ 682, 682, 682, 682 },
{ 682, 682, 682, 682 },
{ 682, 682, 682, 682 },
{ 682, 682, 682, 682 } };

private int[] COEFF_COST4x4 = { 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

private static final int MAX_VALUE = 999999;

// -------------------------------------------------------------------------
// Attributes
// -------------------------------------------------------------------------

private int[] qp_per_matrix; // Pre-calculated floor(QP/6) for qbits


private int[] qp_rem_matrix; // Multiplication Factor indexer
private int[][][] levelOffset4x4Intra; // Pre-calculated offset for each QP
private int[][] invLevelScale4x4;
private Scanner scanner;

// -------------------------------------------------------------------------
// Methods
// -------------------------------------------------------------------------

public IntegerRoundingQuantizer(int qp, Scanner scanner) {


super(qp);

this.scanner = scanner;

initQMatrix();
calculateQuantParam();
calculateOffsetParam();
}

private void initQMatrix() {


qp_per_matrix = new int[MAX_QP + 1];
qp_rem_matrix = new int[MAX_QP + 1];

for (int i = 0; i < MAX_QP + 1; i++) {


qp_per_matrix[i] = i / 6;
qp_rem_matrix[i] = i % 6;
}
}

private void calculateQuantParam() {


invLevelScale4x4 = new int[Block.BLOCK_4x4_SIZE][Block.BLOCK_4x4_SIZE];
int qp_rem = qp_rem_matrix[qp];

for (int j = 0; j < Block.BLOCK_4x4_SIZE; j++) {


for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
invLevelScale4x4[j][i] = dequantCoeff[qp_rem][j][i] << 4;
}
}

private void calculateOffsetParam() {


levelOffset4x4Intra = new int[MAX_QP + 1][Block.BLOCK_4x4_SIZE]
[Block.BLOCK_4x4_SIZE];
int OffsetBits = 11;

int k = qp_per_matrix[qp];
int qp_per = Q_BITS + k - OffsetBits;

for (int qp = 0; qp < MAX_QP + 1; qp++) {


for (int j = 0; j < Block.BLOCK_4x4_SIZE; j++) {
for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
levelOffset4x4Intra[qp][j][i] = offset4x4Intra[j][i] << qp_per;
}
}
}
}

public QuantizerSummary quantization4x4DC(int[][] src, int[][] dst) {


int level = 0;
int run = 0;
62

int nonZeroCoeff = 0;
int coeffCost = 0;
int qp_per = qp_per_matrix[qp];
int qp_rem = qp_rem_matrix[qp];
int qbits = Q_BITS + qp_per; // qbits = 15 + floor(QP/6)
int[][] mf = quantCoeff[qp_rem]; // MF = (2^qbits * PF) / Qstep
int[][] f = levelOffset4x4Intra[qp]; // f = 2^qbits/3 for Intra

for (int j = 0; j < Block.BLOCK_4x4_SIZE; j++) {


for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
// |Z(i,j)| = (|Y(i,j)|*MF(0,0) + 2*f) >> (qbits + 1)
level = (Math.abs(src[j][i]) * mf[0][0] + (f[0][0] << 1)) >> (qbits + 1);

if (level != 0) {
nonZeroCoeff++;

if (qp < 10) {


level = Math.min(level, CAVLC.CAVLC_LEVEL_LIMIT);
}

coeffCost += (level > 1) ? MAX_VALUE : COEFF_COST4x4[run];

// sign(Z(i,j)) = sign(Y(i,j))
dst[j][i] = level * (int) (Math.signum(src[j][i]));
run = 0;
} else {
dst[j][i] = 0;
run++;
}
}
}

return new QuantizerSummary(nonZeroCoeff, coeffCost);


}

public void iquantization4x4DC(int[][] src, int[][] dst) {


int qp_per = qp_per_matrix[qp];
int[][] v = invLevelScale4x4; // V = Qstep * PF * 64

for (int j = 0; j < Block.BLOCK_4x4_SIZE; j++) {


for (int i = 0; i < Block.BLOCK_4x4_SIZE; i++) {
// W'(i,j) = Z(i,j) * V(i,j) * 2^floor(QP/6)
dst[j][i] = SupportMath.rshiftRound(((src[j][i] * v[0][0]) << qp_per), 6);
}
}
}

public QuantizerSummary quantization2x2DC(int[][] src, int[][] dst) {


int level;
int nonZeroCoeff = 0;
int coeffCost = 0;
int qp_per = qp_per_matrix[qp];
int qp_rem = qp_rem_matrix[qp];
int qbits = Q_BITS + qp_per; // qbits = 15 + floor(QP/6)
int[][] mf = quantCoeff[qp_rem]; // MF = (2^qbits * PF) / Qstep
int[][] f = levelOffset4x4Intra[qp]; // f = 2^qbits/3 for Intra

for (int j = 0; j < Block.BLOCK_2x2_SIZE; j++) {


for (int i = 0; i < Block.BLOCK_2x2_SIZE; i++) {
// |Z(i,j)| = (|Y(i,j)|*MF(0,0) + 2*f) >> (qbits + 1)
level = (Math.abs(src[j][i]) * mf[0][0] + (f[0][0] << 1)) >> (qbits + 1);

if (level != 0) {
nonZeroCoeff++;

if (qp < 4) {
level = Math.min(level, CAVLC.CAVLC_LEVEL_LIMIT);
}

// sign(Z(i,j)) = sign(Y(i,j))
dst[j][i] = level * (int) (Math.signum(src[j][i]));
} else {
dst[j][i] = 0;
}
}
}
63

return new QuantizerSummary(nonZeroCoeff, coeffCost);


}

public void iquantization2x2DC(int[][] src, int[][] dst) {


int qp_per = qp_per_matrix[qp];
int[][] v = invLevelScale4x4; // V = Qstep * PF * 64

for (int j = 0; j < Block.BLOCK_2x2_SIZE; j++) {


for (int i = 0; i < Block.BLOCK_2x2_SIZE; i++) {
// W'(i,j) = Z(i,j) * V(i,j) * 2^floor(QP/6)
dst[j][i] = ((src[j][i] * v[0][0]) << qp_per) >> 5;
}
}
}

public QuantizerSummary quantization4x4AC(int[][] src, int[][] dst, int pos_y, int


pos_x) {
int level = 0;
int run = 0;
int nonZeroCoeff = 0;
int coeffCost = 0;
int qp_per = qp_per_matrix[qp];
int qp_rem = qp_rem_matrix[qp];
int qbits = Q_BITS + qp_per; // qbits = 15 + floor(QP/6)
int[][] mf = quantCoeff[qp_rem]; // MF = (2^qbits * PF) / Qstep
int[][] f = levelOffset4x4Intra[qp]; // f = 2^qbits/3 for Intra

for (int coeff = 1; coeff < 16; coeff++) {


// The order the coefficients are quantized matters, so it's required the
scanner
// to provide the right position
int j = scanner.positionToCoordinateX(coeff);
int i = scanner.positionToCoordinateY(coeff);
int jj = pos_y + j;
int ii = pos_x + i;
// |Z(i,j)| = (|Y(i,j)|*MF(0,0) + 2*f) >> (qbits + 1)
level = (Math.abs(src[jj][ii]) * mf[j][i] + f[j][i]) >> qbits;

if (level != 0) {
nonZeroCoeff++;

if (qp < 10) {


level = Math.min(level, CAVLC.CAVLC_LEVEL_LIMIT);
}

coeffCost += (level > 1) ? MAX_VALUE : COEFF_COST4x4[run];

// sign(Z(i,j)) = sign(Y(i,j))
dst[jj][ii] = level * (int) (Math.signum(src[jj][ii]));
run = 0;
} else {
dst[jj][ii] = 0;
run++;
}

return new QuantizerSummary(nonZeroCoeff, coeffCost);


}

public void iquantization4x4AC(int[][] src, int[][] dst, int pos_y, int pos_x) {
int qp_per = qp_per_matrix[qp];
int[][] v = invLevelScale4x4; // V = Qstep * PF * 64

for (int coeff = 1; coeff < 16; coeff++) {


int j = coeff / Block.BLOCK_4x4_SIZE;
int i = coeff % Block.BLOCK_4x4_SIZE;
int jj = pos_y + j;
int ii = pos_x + i;
// inverse scale
// W'(i,j) = Z(i,j) * V(i,j) * 2^floor(QP/4)
dst[jj][ii] = SupportMath.rshiftRound((src[jj][ii] * v[j][i]) << qp_per, 4);
}

}
64

A6 Classes de codificação 3 – Modos de codificação

A6.1 Interface EncodingMode

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode;

import java.io.OutputStream;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.FrameBuffer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.MacroblockType;
import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* This class represents one {@link Macroblock} coding mode.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public interface EncodingMode {

/**
* Encode the macroblock using this {@link EncodingMode}.
*
* @param inFrameBuffer the {@link FrameBuffer} that contains the macroblock data to
* be encoded.
* @param outFrameBuffer the {@link FrameBuffer} that will hold the coded data.
*/
public void encode(YUVFrameBuffer inFrameBuffer, YUVFrameBuffer outFrameBuffer);

/**
* Call this method to make this encoding mode writes encoded data into the
* {@link FrameBuffer}.
* <p>
* Every coded macroblock in an H.264 slice is predicted from previously-encoded data.
* Samples within an intra macroblock are predicted from samples in the current slice
* that have already been encoded, decoded and reconstructed; samples in an inter
* macroblock are predicted from previously-encoded.
*
* @param outFrameBuffer the {@link FrameBuffer} that will hold the coded data. This
* data will be used by the next macroblocks.
*/
public void reconstruct(YUVFrameBuffer outFrameBuffer);

/**
* Calculate the Sum of Squared Differences (SSD):
*
* <pre>
* SSD = Sum(i=0 to M){ Sum(j=0 to N){ (Ori(i,j) - Dec(i,j))&circ;2 }}
* </pre>
*
* @return the Sum of Squared Differences of the macroblock.
*/
public int getDistortion();

/**
* Writes the macroblock coded data to the {@link OutputStream}.
*
* @param outStream the {@link OutputStream} where the macroblock encoded will be
* write.
*/
public void write(H264EntropyOutputStream outStream);

/**
* The type of the macroblock depends on the encode mode that this object represents.
*
* @return the {@link MacroblockType} of this encoding mode.
65

*/
public MacroblockType getMbType();

A6.2 Classe AbstractEncodingMode

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode;

import java.io.OutputStream;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.FrameBuffer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.MacroblockType;
import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* This superclass provides common services to encoding modes subclasses.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public abstract class AbstractEncodingMode implements EncodingMode {

protected YUVFrameBuffer inputFrameBuffer; // raw YUV read frame


protected YUVFrameBuffer outputFrameBuffer; // encoded frame
protected Macroblock macroblock;
protected MacroblockType mbType;

/**
* @param macroblock the {@link Macroblock} to be encoded by this mode.
* @param mbType subclass provide this field to identify which encoding mode it
represents.
*/
public AbstractEncodingMode(Macroblock macroblock, MacroblockType mbType) {
this.macroblock = macroblock;
this.mbType = mbType;
}

public void encode(YUVFrameBuffer inFrameBuffer, YUVFrameBuffer outFrameBuffer) {


this.inputFrameBuffer = inFrameBuffer;
this.outputFrameBuffer = outFrameBuffer;

doEncode(inFrameBuffer, outFrameBuffer);
}

public int getDistortion() {


int distortion = 0;
int x = macroblock.getPixelX();
int y = macroblock.getPixelY();
int cx = macroblock.getPixelChromaX();
int cy = macroblock.getPixelChromaY();

// LUMA
for (int j = y; j < y + Macroblock.MB_HEIGHT; j++) {
for (int i = x; i < x + Macroblock.MB_WIDTH; i++)
distortion += Math.pow(inputFrameBuffer.getY8bit(i, j)
- outputFrameBuffer.getY8bit(i, j), 2);
}

// CHROMA
for (int j = cy; j < cy + Macroblock.MB_CHROMA_HEIGHT; j++) {
for (int i = cx; i < cx + Macroblock.MB_CHROMA_WIDTH; i++) {
distortion += Math.pow(inputFrameBuffer.getCb8bit(i, j)
- outputFrameBuffer.getCb8bit(i, j), 2);
distortion += Math.pow(inputFrameBuffer.getCr8bit(i, j)
- outputFrameBuffer.getCr8bit(i, j), 2);
}
}

return distortion;
}

public void write(H264EntropyOutputStream outStream) {


66

doWrite(outStream);
}

public MacroblockType getMbType() {


return mbType;
}

/**
* @return the {@link Macroblock} which owns this encoding mode.
*/
public Macroblock getMacroblock() {
return macroblock;
}

/**
* Subclasses must implement this method to encode the macroblock data.
* <p>
* <b>Important:</b>The coded data shall not be placed on the output buffer until the
* {@link EncodingMode#reconstruct(YUVFrameBuffer)} method on this mode be called. The
* output buffer is passed here to provide previously coded data from neighbours
* macroblocks.
*
* @param inFrameBuffer the {@link FrameBuffer} that contains the macroblock data to be
* encoded.
* @param codedFrameBuffer the {@link FrameBuffer} with previously coded macroblocks
data
* for prediction use only.
*/
protected abstract void doEncode(
YUVFrameBuffer inFrameBuffer, YUVFrameBuffer codedFrameBuffer);

/**
* Subclasses must implement this method in order to write their coded data into the
* stream.
*
* @param outStream the {@link OutputStream} where the macroblock encoded will be write.
*/
protected abstract void doWrite(H264EntropyOutputStream outStream);

A6.3 Classe Intra16x16EncodingMode

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode;

import java.util.ArrayList;
import java.util.List;

import br.ufsc.inf.guiga.media.Global;
import br.ufsc.inf.guiga.media.codec.video.h264.BaselineProfileFactory;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.AlgorithmFactory;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.MacroblockType;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra16x16LumaDCPredictor;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra16x16LumaHorizontalPredict
or;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra16x16LumaPlanePredictor;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra16x16LumaVerticalPredictor
;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra8x8ChromaDCPredictor;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra8x8ChromaHorizontalPredict
or;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra8x8ChromaPlanePredictor;
import
br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.Intra8x8ChromaVerticalPredictor
;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction.IntraPredictor;
67

import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* Basically, this class selects the best luma intra 16x16 prediction mode.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class Intra16x16EncodingMode extends AbstractEncodingMode {

protected IntraPredictor[] lumaModes;


protected IntraPredictor[] chromaModes;
protected IntraPredictor bestLumaMode;
protected IntraPredictor bestChromaMode;
protected int bestLumaModeIdx;
protected int bestChromaModeIdx;
protected List<Integer> successPredLumaModes;
protected List<Integer> successPredChromaModes;

// coded_block_pattern: indicates which blocks within a macroblock contain


// non-zero transform coefficient levels
int codedBlockPattern;

public Intra16x16EncodingMode(Macroblock macroblock) {


super(macroblock, MacroblockType.I16MB);

codedBlockPattern = 0;

int x = macroblock.getPixelX();
int y = macroblock.getPixelY();
int xc = macroblock.getPixelChromaX();
int yc = macroblock.getPixelChromaY();
int qp = Global.getInstance().getH264Control().getQuantizerParameter();

AlgorithmFactory algorithms = new BaselineProfileFactory(qp);

// creates the four luma intra 16x16 prediction modes


lumaModes = new IntraPredictor[4];
lumaModes[0] = new Intra16x16LumaVerticalPredictor(x, y, macroblock, algorithms);
lumaModes[1] = new Intra16x16LumaHorizontalPredictor(x, y, macroblock, algorithms);
lumaModes[2] = new Intra16x16LumaDCPredictor(x, y, macroblock, algorithms);
lumaModes[3] = new Intra16x16LumaPlanePredictor(x, y, macroblock, algorithms);

// creates the four chroma intra 8x8 prediction modes


// FIXME: Each Intra Encoding Mode (i.e. 4x4, 8x8) compute the chroma
// prediction, which is waste of time. Although, JM14 does chroma coding
// for each intra mode too (I16MB and I4MB).
chromaModes = new IntraPredictor[4];
chromaModes[0] = new Intra8x8ChromaDCPredictor(xc, yc, macroblock, algorithms);
chromaModes[1] = new Intra8x8ChromaHorizontalPredictor(xc, yc, macroblock,
algorithms);
chromaModes[2] = new Intra8x8ChromaVerticalPredictor(xc, yc, macroblock,
algorithms);
chromaModes[3] = new Intra8x8ChromaPlanePredictor(xc, yc, macroblock, algorithms);

// this list avoids to compute distortion on unsuccessful encoding modes


successPredLumaModes = new ArrayList<Integer>();
successPredChromaModes = new ArrayList<Integer>();
}

public void reconstruct(YUVFrameBuffer outFrameBuffer) {


// Reconstruct the pixels from the transformed mode, once the next
// macroblocks use this reconstructed samples for prediction, we ensure
// that decoder and encoder use the same samples, avoiding error
// propagation.
bestLumaMode.reconstruct(outFrameBuffer);
bestChromaMode.reconstruct(outFrameBuffer);
}

protected void doEncode(YUVFrameBuffer inFrameBuffer, YUVFrameBuffer codedFrameBuffer)


{
// generate the luma intra prediction samples for all four 16x16 modes
for (int i = 0; i < lumaModes.length; i++) {
if (lumaModes[i].predict(inFrameBuffer, codedFrameBuffer)) {
successPredLumaModes.add(i);
}
68

}
// get the best luma intra 16x16 mode
int bestSAD = Integer.MAX_VALUE;
int currentSAD = 0;
for (Integer i : successPredLumaModes) {
currentSAD = lumaModes[i].getDistortion();
if (currentSAD < bestSAD) {
bestSAD = currentSAD;
bestLumaMode = lumaModes[i];
bestLumaModeIdx = i;
}
}
// encode the chosen intra luma mode
codedBlockPattern += bestLumaMode.encode(inFrameBuffer, codedFrameBuffer);

// generates the chroma intra prediction samples for all four 8x8 modes
for (int i = 0; i < chromaModes.length; i++) {
if (chromaModes[i].predict(inFrameBuffer, codedFrameBuffer)) {
successPredChromaModes.add(i);
}
}
// get the best chroma intra 8x8 mode
bestSAD = Integer.MAX_VALUE;
currentSAD = 0;
for (Integer i : successPredChromaModes) {
currentSAD = chromaModes[i].getDistortion();
if (currentSAD < bestSAD) {
bestSAD = currentSAD;
bestChromaMode = chromaModes[i];
bestChromaModeIdx = i;
}
}

// encode the chosen intra chroma mode


codedBlockPattern += bestChromaMode.encode(inFrameBuffer, codedFrameBuffer);

releaseUnusedModes();
}

protected void doWrite(H264EntropyOutputStream outStream) {


// mb_type
int cbpL = codedBlockPattern % 16;
int cbpC = codedBlockPattern / 16;
int mb_type = 1 + bestLumaModeIdx + 4 * cbpC + (((cbpL != 0) ? 1 : 0) * 12);
outStream.writeMacroblockType(mb_type);

// intra_chroma_pred_mode
int intra_chroma_pred_mode = bestChromaModeIdx;
outStream.writeIntraChromaPredMode(intra_chroma_pred_mode);

// mb_qp_delta
int mb_qp_delta = 0; // TODO this.delta_qp = this.qp - prev.qp;
outStream.writeMackoblockQpDelta(mb_qp_delta);

// residual_luma()
// -> residual_block_cavlc( i16x16DClevel, 0, 15, 16 )
// -> residual_block_cavlc( i16x16AClevel[i8x8*4+ i4x4], ...)
bestLumaMode.write(outStream, codedBlockPattern);

// residual_block_cavlc( ChromaDCLevel[ iCbCr ], ... )


// residual_block_cavlc( ChromaACLevel[ iCbCr ][ i8x8*4+i4x4 ], ...)
bestChromaMode.write(outStream, codedBlockPattern);
}

/**
* Unreferenced the modes for garbage collector save memory. Only the modes referenced
* by bestXXMode will remain in memory...at least I hope so.
*/
private void releaseUnusedModes() {
for (int i = 0; i < lumaModes.length; i++) {
lumaModes[i] = null;
}
lumaModes = null;
for (int i = 0; i < chromaModes.length; i++) {
chromaModes[i] = null;
}
69

chromaModes = null;
}

A6.4 Classe IPCMEncodingMode

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.MacroblockType;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.SliceType;
import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* I_PCM is an Intra coding mode that enables an encoder to transmit the values of the
* image samples directly (without prediction or transformation). In some special cases
* (e.g. anomalous image content and/or very low quantizer parameters), this mode may be
* more efficient than the ‘usual’ process of intra prediction, transformation,
* quantization and entropy coding.
* <p>
* The I_PCM option also makes it possible to place an absolute limit on the number of
* bits that may be contained in a coded macroblock without constraining decoded image
* quality.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class IPCMEncodingMode extends AbstractEncodingMode {

int[][] bufferY;
int[][] bufferU;
int[][] bufferV;

public IPCMEncodingMode(Macroblock macroblock) {


super(macroblock, MacroblockType.IPCM);

bufferY = new int[Macroblock.MB_WIDTH][Macroblock.MB_HEIGHT];


bufferU = new int[Macroblock.MB_CHROMA_WIDTH][Macroblock.MB_CHROMA_HEIGHT];
bufferV = new int[Macroblock.MB_CHROMA_WIDTH][Macroblock.MB_CHROMA_HEIGHT];
}

public void reconstruct(YUVFrameBuffer outFrameBuffer) {


int luma = 0, chromaU, chromaV;
int x = macroblock.getPixelX();
int y = macroblock.getPixelY();
int cx = macroblock.getPixelChromaX();
int cy = macroblock.getPixelChromaY();

// write the I_PCM luma pixels


for (int j = 0; j < Macroblock.MB_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_WIDTH; i++) {
luma = bufferY[i][j];
outFrameBuffer.setY8bit(i + x, j + y, luma);
}
}

// write the I_PCM chroma pixels


for (int j = 0; j < Macroblock.MB_CHROMA_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_CHROMA_WIDTH; i++) {
chromaU = bufferU[i][j];
outFrameBuffer.setCb8bit(i + cx, j + cy, chromaU);
}
}
for (int j = 0; j < Macroblock.MB_CHROMA_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_CHROMA_WIDTH; i++) {
chromaV = bufferV[i][j];
outFrameBuffer.setCr8bit(i + cx, j + cy, chromaV);
}
}

protected void doEncode(YUVFrameBuffer inFrameBuffer, YUVFrameBuffer codedFrameBuffer)


70

{
int luma, chromaU, chromaV;
int x = macroblock.getPixelX();
int y = macroblock.getPixelY();
int cx = macroblock.getPixelChromaX();
int cy = macroblock.getPixelChromaY();

// LUMA
for (int j = 0; j < Macroblock.MB_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_WIDTH; i++) {
luma = inputFrameBuffer.getY8bit(i + x, j + y);
bufferY[i][j] = luma;
}
}
// CHROMA
for (int j = 0; j < Macroblock.MB_CHROMA_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_CHROMA_WIDTH; i++) {
chromaU = inputFrameBuffer.getCb8bit(i + cx, j + cy);
chromaV = inputFrameBuffer.getCr8bit(i + cx, j + cy);

bufferU[i][j] = chromaU;
bufferV[i][j] = chromaV;
}
}
}

protected void doWrite(H264EntropyOutputStream outStream) {


int luma = 0, chromaU, chromaV;
int bitDepth = 8; // TODO get it automatically

// mb_type
if (macroblock.getSlice().getSliceType().equals(SliceType.I_SLICE)) {
outStream.writeMacroblockType(25);
} else {
outStream.writeMacroblockType(31);
}

// pcm_alignment_zero_bit (align the byte)


outStream.flush();

// write the I_PCM luma pixels


for (int j = 0; j < Macroblock.MB_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_WIDTH; i++) {
luma = bufferY[i][j];
outStream.write_u_v(bitDepth, luma);
}
}

// write the I_PCM chroma pixels


for (int j = 0; j < Macroblock.MB_CHROMA_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_CHROMA_WIDTH; i++) {
chromaU = bufferU[i][j];
outStream.write_u_v(bitDepth, chromaU);
}
}
for (int j = 0; j < Macroblock.MB_CHROMA_HEIGHT; j++) {
for (int i = 0; i < Macroblock.MB_CHROMA_WIDTH; i++) {
chromaV = bufferV[i][j];
outStream.write_u_v(bitDepth, chromaV);
}
}

A6.5 Classe Intra16x16LumaAbstractPredictor

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction;

import java.util.Arrays;

import br.ufsc.inf.guiga.media.Global;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.FrameBuffer;
71

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.AlgorithmFactory;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Quantizer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Scanner;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Transform;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Quantizer.QuantizerSummary;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.block.ResidualBlockInfo;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.ResidualBlockType;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock.MacroblockAccess;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock.MacroblockInfo;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.decision.DistortionMetric;
import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;
import br.ufsc.inf.guiga.media.util.SupportMath;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* This class provides common services for Intra 16x16 Luma Prediction Modes.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public abstract class Intra16x16LumaAbstractPredictor implements IntraPredictor {
protected YUVFrameBuffer inputFrameBuffer; // raw YUV frame
protected YUVFrameBuffer outputFrameBuffer; // encoded frame

protected int x; // macroblock left up corner x0


protected int y; // macroblock left up corner y0
// macroblock dimensions
protected int mbWidth = Macroblock.MB_WIDTH;
protected int mbHeight = Macroblock.MB_HEIGHT;
// number of DC coefficients (4x4)
protected int dcWidth = 4;
protected int dcHeight = 4;

protected int mOrig[][]; // macroblock original samples (16x16 samples)


protected int mResd[][]; // macroblock transformed residual samples (16x16 samples)
protected int mPred[][]; // macroblock predicted samples (16x16 samples)
// Hadamard (4x4 DC coefficients)
protected int mDc[][]; // transformed and quantized coefficients

protected MacroblockAccess access;


protected MacroblockInfo info;
protected Transform transform;
protected Quantizer quantizer;
protected DistortionMetric distortion;
protected Scanner scanner;

protected static final int bitDepthY;


protected static final int maxImagePelValue;

static {
bitDepthY = Global.getInstance().getH264Control().getBitDepthLuma();
maxImagePelValue = (1 << bitDepthY) - 1; // 2 ^ bitDepth
}

/**
* @param x the macroblock upper left corner horizontal position.
* @param y the macroblock upper left corner vertical position.
* @param macroblock the {@link Macroblock} that allow access to the macroblock
* neighbours.
* @param algorithms the {@link AlgorithmFactory} for algorithms creation.
*/
public Intra16x16LumaAbstractPredictor(
int x,
int y,
Macroblock macroblock,
AlgorithmFactory algorithms)
{
this.x = x;
this.y = y;
this.access = macroblock.getMacroblockAccess();
this.info = macroblock.getMacroblockInfo();

transform = algorithms.createTransform();
quantizer = algorithms.createQuantizer();
distortion = algorithms.createDistortionMetric();
scanner = algorithms.createScanner();
72

mOrig = new int[mbHeight][mbWidth];


mResd = new int[mbHeight][mbWidth];
mPred = new int[mbHeight][mbWidth];
mDc = new int[dcHeight][dcWidth];
}

public boolean predict(


YUVFrameBuffer origiFrameBuffer, YUVFrameBuffer codedFrameBuffer)
{
// place the input frame data into a easy access matrix.
fillOriginalMatrix(origiFrameBuffer);

// Predict the samples.


return doIntraPrediction(codedFrameBuffer, mPred);
}

public int encode(YUVFrameBuffer inFrameBuffer, YUVFrameBuffer outFrameBuffer) {


this.inputFrameBuffer = inFrameBuffer;
this.outputFrameBuffer = outFrameBuffer;

// Fill the macroblock matrix with the residual samples, original minus
// predicted.
fillResidualMatrix();

// Apply transform and quantization on the residual samples and return


// the amount of non-zero coefficients
return forwardTransform(mResd, mDc, mResd);
}

public void reconstruct(YUVFrameBuffer outFrameBuffer) {


// In intra mode a prediction block P is formed based on previously
// encoded and reconstructed blocks.
int dq_bits = 6;
int residualRecons; // Reconstructed residual sample
int originalRecons; // Reconstructed original sample
int predicted; // Predicted sample

// Apply the inverse quantization and transform on residual coefficients


int[][] mrInv = new int[mbHeight][mbWidth];
inverseTransform(mDc, mResd, mrInv);

for (int j = 0; j < mbHeight; j++) {


int jj = y + j;

for (int i = 0; i < mbWidth; i++) {


int ii = x + i;

predicted = mPred[j][i];
residualRecons = SupportMath.rshiftRound(mrInv[j][i], dq_bits);
originalRecons = residualRecons + predicted;
originalRecons = SupportMath.clip(maxImagePelValue, originalRecons);
outFrameBuffer.setY8bit(ii, jj, originalRecons);
}
}

public void write(H264EntropyOutputStream outStream, int codedBlockPattern) {


int maxNumDcCoeff = 16;
int maxNumAcCoeff = 15; // the first level of each AC block is the DC
int[] coeffLevel = new int[maxNumDcCoeff];
int[] coeffRun = new int[maxNumDcCoeff];

// Get the information about this DC block


ResidualBlockInfo blockInfo = info.getBlockInfo(0, 0,
ResidualBlockType.Intra16x16LumaDCLevel);

// DC coefficients: Reorder the matrix


scanner.reorder4x4(mDc, coeffLevel, coeffRun, 0, maxNumDcCoeff, 0, 0);
// DC coefficients: Entropy code
outStream.writeResidualBlock(coeffLevel, coeffRun,
ResidualBlockType.Intra16x16LumaDCLevel, blockInfo);

// AC coefficients
if ((codedBlockPattern & 15) != 0) {
73

// write the four AC 4x4 sub-blocks of each 8x8 block


for (int i8x8 = 0; i8x8 < 4; i8x8++) {
for (int i4x4 = 0; i4x4 < 4; i4x4++) {
int block_y = 4 * (2 * (i8x8 >> 1) + (i4x4 >> 1));
int block_x = 4 * (2 * (i8x8 & 0x01) + (i4x4 & 0x01));

// Clean up the destination vectors


Arrays.fill(coeffLevel, 0);
Arrays.fill(coeffRun, 0);
// Reorder the matrix
scanner.reorder4x4(mResd, coeffLevel, coeffRun, 1, maxNumAcCoeff,
block_y, block_x);
// Get the information about this AC block
blockInfo = info.getBlockInfo(block_x >> 2, block_y >> 2,
ResidualBlockType.Intra16x16LumaACLevel);

// Entropy code
outStream.writeResidualBlock(coeffLevel, coeffRun,
ResidualBlockType.Intra16x16LumaACLevel, blockInfo);
}
}
}

public int getDistortion() {


return distortion.getDistortion16x16(mOrig, mPred);
}

/**
* Subclasses must implement this method in order to predict samples.
*
* @param codedFrameBuffer the {@link FrameBuffer} containing previously coded samples
* necessary for prediction.
* @param mPredCb the matrix where the predicted samples must be placed.
* @return <code>true</code> if the prediction was successfully made, or
* <code>false</code> if there were anything that prevented the prediction
* complete.
*/
protected abstract boolean doIntraPrediction(
YUVFrameBuffer codedFrameBuffer, int[][] mp);

/**
* Fill the matrix with the original frame samples.
*
* @param origiFrameBuffer
*/
private void fillOriginalMatrix(YUVFrameBuffer origiFrameBuffer) {
for (int j = 0; j < mbHeight; j++) {
int jj = y + j;
for (int i = 0; i < mbWidth; i++) {
int ii = x + i;
mOrig[j][i] = origiFrameBuffer.getY8bit(ii, jj);
}
}
}

/**
* Fill the matrix with the predicted residual samples.
*/
private void fillResidualMatrix() {
for (int j = 0; j < mbHeight; j++) {
for (int i = 0; i < mbWidth; i++) {
mResd[j][i] = mOrig[j][i] - mPred[j][i];
}
}
}

/**
* Applies a 16x16 luma transform and quantization on the residual samples.
*
* @param mrSrc residual coefficients
* @param m4Dst transformed hadamard coefficients
* @param mrDst transformed residual coefficients
* @return the amount of non-zero coefficients.
*/
74

private int forwardTransform(int[][] mrSrc, int[][] m4Dst, int[][] mrDst) {


int acCoeff = 0;

// Do a forward transform on each 4x4 residual block of this macroblock


for (int block_y = 0; block_y < mbHeight; block_y += 4) {
for (int block_x = 0; block_x < mbWidth; block_x += 4) {
transform.forward4x4(mrSrc, mrDst, block_y, block_x);
}
}

// Build a DC matrix with the DC coefficients from each 4x4 block


for (int j = 0; j < dcHeight; j++)
for (int i = 0; i < dcWidth; i++)
m4Dst[j][i] = mrDst[j << 2][i << 2];

// The DC coefficient of each 4x4 block is transformed again using a 4x4 Hadamard
// transform
transform.hadamard4x4(m4Dst, m4Dst);

// Quantize the DC matrix to produce a block of quantized DC coefficients


QuantizerSummary qs = quantizer.quantization4x4DC(m4Dst, m4Dst);

// Fulfill the info of this residual DC block


ResidualBlockInfo blockInfo = new ResidualBlockInfo(qs.nonZeroCoeff);
info.setBlockInfo(0, 0, ResidualBlockType.Intra16x16LumaDCLevel, blockInfo);

// Quantize AC coefficients
for (int block_y = 0; block_y < mbHeight; block_y += 4) {
for (int block_x = 0; block_x < mbHeight; block_x += 4) {
qs = quantizer.quantization4x4AC(mrDst, mrDst, block_y, block_x);

// Fulfill the info of this residual AC block


blockInfo = new ResidualBlockInfo(qs.nonZeroCoeff);
info.setBlockInfo(block_x >> 2, block_y >> 2,
ResidualBlockType.Intra16x16LumaACLevel, blockInfo);

if (qs.nonZeroCoeff > 0) {
acCoeff = 15;
}
}
}

return acCoeff;
}

/**
* Applies a 16x16 luma inverse transform and quantization on the residual samples.
*
* @param m4Src transformed hadamard coefficients
* @param mrSrc transformed residual coefficients
* @param mrDst inverse transformed residual coefficients
*/
private void inverseTransform(int[][] m4Src, int[][] mrSrc, int[][] mrDst) {
int[][] m4Inv = new int[dcHeight][dcWidth];

// Copy the source into destination matrix


for (int j = 0; j < mbHeight; j++)
for (int i = 0; i < mbWidth; i++)
mrDst[j][i] = mrSrc[j][i];

// Apply an inverse Hadamard transform on the quantized DC coefficients


transform.ihadamard4x4(m4Src, m4Inv);

// Restore DC coefficients through inverse quantization


quantizer.iquantization4x4DC(m4Inv, m4Inv);

// Restore DC coefficients into the transformed matrix


for (int j = 0; j < dcHeight; j++)
for (int i = 0; i < dcWidth; i++)
mrDst[j << 2][i << 2] = m4Inv[j][i];

// Apply inverse quantization and transform on AC coefficients


for (int block_y = 0; block_y < mbHeight; block_y += 4) {
for (int block_x = 0; block_x < mbWidth; block_x += 4) {
quantizer.iquantization4x4AC(mrDst, mrDst, block_y, block_x);
transform.inverse4x4(mrDst, mrDst, block_y, block_x);
75

}
}

A6.6 Classe Intra8x8ChromaAbstactPredictor

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction;

import java.util.Arrays;

import br.ufsc.inf.guiga.media.Global;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.FrameBuffer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.AlgorithmFactory;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Quantizer;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Scanner;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Transform;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Quantizer.QuantizerSummary;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.block.ResidualBlockInfo;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.ResidualBlockType;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock.MacroblockAccess;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock.MacroblockInfo;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.decision.DistortionMetric;
import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;
import br.ufsc.inf.guiga.media.util.SupportMath;
import br.ufsc.inf.guiga.media.util.io.H264EntropyOutputStream;

/**
* This class provides common services for Intra 8x8 Chroma Prediction Modes.
* <p>
* Both chroma blocks (Cb and Cr) of the macroblock use the same prediction mode. The
* prediction mode is applied to each of the chroma blocks separately.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public abstract class Intra8x8ChromaAbstractPredictor implements IntraPredictor {
private enum ChromaType {
CB, CR
};

protected YUVFrameBuffer inputFrameBuffer; // raw YUV frame


protected YUVFrameBuffer outputFrameBuffer; // encoded frame

protected int x; // macroblock left up corner x0


protected int y; // macroblock left up corner y0

// macroblock original samples (8x8 samples) for U and V


protected int mOrigCb[][];
protected int mOrigCr[][];
// macroblock residual coded samples (8x8 samples) for U and V
protected int mResdCb[][];
protected int mResdCr[][];
// macroblock predicted samples (8x8 samples) for U and V
protected int mPredCb[][];
protected int mPredCr[][];
// coefficients for Hadamard (2x2 DC coefficients) for U and V
protected int mDcCb[][];
protected int mDcCr[][];

protected MacroblockAccess access;


protected MacroblockInfo info;
protected Transform transform;
protected Quantizer quantizer;
protected DistortionMetric distortion;
protected Scanner scanner;

// MbWidthC and MbHeightC specify the width and height, respectively, of the chroma
// arrays
// for each macroblock
protected int mbWidthC = 8;
protected int mbHeightC = 8;
76

// 2x2 DC coefficients for chroma blocks


protected int dcWidthC = 2;
protected int dcHeightC = 2;

protected static final int bitDepthC;


protected static final int maxImagePelValue;

static {
bitDepthC = Global.getInstance().getH264Control().getBitDepthChroma();
maxImagePelValue = (1 << bitDepthC) - 1; // 2 ^ bitDepth
}

// TODO find a more appropriate place to this constant


// threshold for chroma coefficients
protected static final int CHROMA_COEFF_COST = 4;

/**
* @param x the macroblock upper left corner horizontal position for chroma component.
* @param y the macroblock upper left corner vertical position for chroma component.
* @param access the {@link Macroblock} that allow access to the macroblock
* neighbours.
* @param algorithms the {@link AlgorithmFactory} for algorithms creation.
*/
public Intra8x8ChromaAbstractPredictor(
int x,
int y,
Macroblock macroblock,
AlgorithmFactory algorithms)
{
this.x = x;
this.y = y;
this.access = macroblock.getMacroblockAccess();
this.info = macroblock.getMacroblockInfo();

transform = algorithms.createTransform();
quantizer = algorithms.createQuantizer();
distortion = algorithms.createDistortionMetric();
scanner = algorithms.createScanner();

mOrigCb = new int[mbHeightC][mbWidthC];


mOrigCr = new int[mbHeightC][mbWidthC];
mResdCb = new int[mbHeightC][mbWidthC];
mResdCr = new int[mbHeightC][mbWidthC];
mPredCb = new int[mbHeightC][mbWidthC];
mPredCr = new int[mbHeightC][mbWidthC];
mDcCb = new int[dcHeightC][dcWidthC];
mDcCr = new int[dcHeightC][dcWidthC];
}

public boolean predict(


YUVFrameBuffer origiFrameBuffer, YUVFrameBuffer codedFrameBuffer)
{
// place the input frame data into a easy access matrix.
fillOriginalMatrix(origiFrameBuffer);

// Predict the samples.


return doIntraPrediction(codedFrameBuffer, mPredCb, mPredCr);
}

public int encode(YUVFrameBuffer inFrameBuffer, YUVFrameBuffer outFrameBuffer) {


// Fill the macroblock matrix with the residual samples, original minus
// predicted.
fillResidualMatrix();

// Apply transform and quantization on the residual samples and return


// the amount of non-zero coefficients
int nonZeroCoeffCb = forwardTransform(mResdCb, mDcCb, mResdCb, ChromaType.CB);
int nonZeroCoeffCr = forwardTransform(mResdCr, mDcCr, mResdCr, ChromaType.CR);

return (Math.max(nonZeroCoeffCb, nonZeroCoeffCr) << 4);


}

public void reconstruct(YUVFrameBuffer outFrameBuffer) {


int dq_bits = 6;
// Reconstructed residual sample
int residualReconsCb;
77

int residualReconsCr;
// Reconstructed original sample
int originalReconsCb;
int originalReconsCr;
// Predicted sample
int predictedCb;
int predictedCr;

// Apply the inverse quantization and transform on residual coefficients


int[][] mrInvCb = new int[mbHeightC][mbWidthC];
int[][] mrInvCr = new int[mbHeightC][mbWidthC];
inverseTransform(mDcCb, mResdCb, mrInvCb);
inverseTransform(mDcCr, mResdCr, mrInvCr);

for (int j = 0; j < mbHeightC; j++) {


int jj = y + j;

for (int i = 0; i < mbWidthC; i++) {


int ii = x + i;

predictedCb = mPredCb[j][i];
residualReconsCb = SupportMath.rshiftRound(mrInvCb[j][i], dq_bits);
originalReconsCb = residualReconsCb + predictedCb;
originalReconsCb = SupportMath.clip(maxImagePelValue, originalReconsCb);
outFrameBuffer.setCb8bit(ii, jj, originalReconsCb);

predictedCr = mPredCr[j][i];
residualReconsCr = SupportMath.rshiftRound(mrInvCr[j][i], dq_bits);
originalReconsCr = residualReconsCr + predictedCr;
originalReconsCr = SupportMath.clip(maxImagePelValue, originalReconsCr);
outFrameBuffer.setCr8bit(ii, jj, originalReconsCr);
}
}

public void write(H264EntropyOutputStream outStream, int codedBlockPattern) {


int maxNumDcCoeff = 4;
int maxNumAcCoeff = 15; // the first level of each AC block is the DC
int[] coeffLevelCb = new int[maxNumAcCoeff];
int[] coeffLevelCr = new int[maxNumAcCoeff];
int[] coeffRunCb = new int[maxNumAcCoeff];
int[] coeffRunCr = new int[maxNumAcCoeff];
ResidualBlockInfo blockInfoCb;
ResidualBlockInfo blockInfoCr;

// Check if any chroma bits in coded block pattern is set


if (codedBlockPattern > 15) {
// Get the information about this DC block
// Note: Chroma DC block has its own VLC table, which doesn't require to
// predict from other blocks
blockInfoCb = info.getBlockInfo(0, 0,
ResidualBlockType.CbIntra8x8ChromaDCLevel);
blockInfoCr = info.getBlockInfo(0, 0,
ResidualBlockType.CrIntra8x8ChromaDCLevel);
// DC coefficients: Reorder the matrix
scanner.reorder2x2(mDcCb, coeffLevelCb, coeffRunCb, 0, maxNumDcCoeff, 0, 0);
scanner.reorder2x2(mDcCr, coeffLevelCr, coeffRunCr, 0, maxNumDcCoeff, 0, 0);
// DC coefficients: Entropy code
outStream.writeResidualBlock(coeffLevelCb, coeffRunCb,
ResidualBlockType.CbIntra8x8ChromaDCLevel, blockInfoCb);
outStream.writeResidualBlock(coeffLevelCr, coeffRunCr,
ResidualBlockType.CrIntra8x8ChromaDCLevel, blockInfoCr);
}

// AC coefficients: check if chroma bits in coded block pattern = 10b


if (codedBlockPattern >> 4 == 2) {
// Write AC blocks of Cb
for (int block_y = 0; block_y < mbHeightC; block_y += 4) {
for (int block_x = 0; block_x < mbWidthC; block_x += 4) {
// Clean up the destination vectors
Arrays.fill(coeffLevelCb, 0);
Arrays.fill(coeffRunCb, 0);
// AC coefficients: Reorder the matrix
scanner.reorder4x4(mResdCb, coeffLevelCb, coeffRunCb, 1,
maxNumAcCoeff, block_y, block_x);
78

// Get the information about this AC block


blockInfoCb = info.getBlockInfo(block_x >> 2, block_y >> 2,
ResidualBlockType.CbIntra8x8ChromaACLevel);
// Entropy code
outStream.writeResidualBlock(coeffLevelCb, coeffRunCb,
ResidualBlockType.CbIntra8x8ChromaACLevel, blockInfoCb);
}
}
// Write AC blocks of Cr
for (int block_y = 0; block_y < mbHeightC; block_y += 4) {
for (int block_x = 0; block_x < mbWidthC; block_x += 4) {
// Clean up the destination vectors
Arrays.fill(coeffLevelCr, 0);
Arrays.fill(coeffRunCr, 0);
// AC coefficients: Reorder the matrix
scanner.reorder4x4(mResdCr, coeffLevelCr, coeffRunCr, 1,
maxNumAcCoeff, block_y, block_x);
// Get the information about this AC block
blockInfoCr = info.getBlockInfo(block_x >> 2, block_y >> 2,
ResidualBlockType.CrIntra8x8ChromaACLevel);
// Entropy code
outStream.writeResidualBlock(coeffLevelCr, coeffRunCr,
ResidualBlockType.CrIntra8x8ChromaACLevel, blockInfoCr);
}
}
}

public int getDistortion() {


int cost = 0;
int distortionCb = 0;
int distortionCr = 0;

for (int block_y = 0; block_y < mbHeightC; block_y += 4) {


for (int block_x = 0; block_x < mbWidthC; block_x += 4) {
distortionCb += distortion.getDistortion4x4(mOrigCb, mPredCb, block_y,
block_x);
distortionCr += distortion.getDistortion4x4(mOrigCr, mPredCr, block_y,
block_x);
}
}

// TODO why cost += (int) (enc_mb.lambda_me[Q_PEL] * mvbits[ mode ])?


// why does it need to exp golomb coding cost for mode signaling?
cost = distortionCb + distortionCr;

return cost;
}

/**
* Subclasses must implement this method in order to predict samples.
*
* @param codedFrameBuffer the {@link FrameBuffer} containing previously coded samples
* necessary for prediction.
* @param mPredCb the matrix where the predicted U samples must be placed.
* @param mPredCr the matrix where the predicted V samples must be placed.
* @return <code>true</code> if the prediction was successfully performed, or
* <code>false</code> if there were anything that prevented the prediction
* complete.
*/
protected abstract boolean doIntraPrediction(
YUVFrameBuffer codedFrameBuffer, int[][] mpCb, int[][] mpCr);

/**
* Fill the matrix with the original frame samples.
*
* @param origiFrameBuffer
*/
private void fillOriginalMatrix(YUVFrameBuffer origiFrameBuffer) {
for (int j = 0; j < mbHeightC; j++) {
int jj = y + j;
for (int i = 0; i < mbWidthC; i++) {
int ii = x + i;
mOrigCb[j][i] = origiFrameBuffer.getCb8bit(ii, jj);
mOrigCr[j][i] = origiFrameBuffer.getCr8bit(ii, jj);
79

}
}
}

/**
* Fill the matrix with the predicted residual samples.
*/
private void fillResidualMatrix() {
for (int j = 0; j < mbHeightC; j++) {
for (int i = 0; i < mbWidthC; i++) {
mResdCb[j][i] = mOrigCb[j][i] - mPredCb[j][i];
mResdCr[j][i] = mOrigCr[j][i] - mPredCr[j][i];
}
}
}

/**
* Applies a 8x8 4:2:0 chroma transform and quantization on the residual samples.
*
* @param mrSrc residual coefficients.
* @param m2Dst transformed hadamard coefficients.
* @param mrDst transformed residual coefficients.
* @param chromaType the {@link ChromaType} of this block.
* @return the amount of non-zero coefficients.
*/
private int forwardTransform(
int[][] mrSrc, int[][] m2Dst, int[][] mrDst, ChromaType chromaType)
{
ResidualBlockType typeDC, typeAC;
if (chromaType == ChromaType.CB) {
typeDC = ResidualBlockType.CbIntra8x8ChromaDCLevel;
typeAC = ResidualBlockType.CbIntra8x8ChromaACLevel;
} else {
typeDC = ResidualBlockType.CrIntra8x8ChromaDCLevel;
typeAC = ResidualBlockType.CrIntra8x8ChromaACLevel;
}

long coeffCost = 0;
int nonZeroCoeff = 0;
int dcCoeff = 0;
int acCoeff = 0;

// Do a forward transform on each 4x4 residual block of this macroblock


for (int block_y = 0; block_y < mbHeightC; block_y += 4) {
for (int block_x = 0; block_x < mbWidthC; block_x += 4) {
transform.forward4x4(mrSrc, mrDst, block_y, block_x);
}
}

// Build a 2x2 DC matrix with the DC coefficients from each 4x4 block
for (int j = 0; j < dcHeightC; j++) {
for (int i = 0; i < dcWidthC; i++) {
m2Dst[j][i] = mrDst[j << 2][i << 2];
}
}

// The DC coefficient of each block is transformed again using a 2x2


// Hadamard transform
transform.hadamard2x2(m2Dst, m2Dst);

// Quantize the DC matrix to produce a block of quantized DC coefficients


QuantizerSummary qs = quantizer.quantization2x2DC(m2Dst, m2Dst);
if (qs.nonZeroCoeff > 0) {
dcCoeff = 1;
}

// Fulfill the info of this residual block


ResidualBlockInfo blockInfo = new ResidualBlockInfo(qs.nonZeroCoeff);
info.setBlockInfo(0, 0, typeDC, blockInfo);

// Quantize AC coefficients
for (int block_y = 0; block_y < mbHeightC; block_y += 4) {
for (int block_x = 0; block_x < mbWidthC; block_x += 4) {

qs = quantizer.quantization4x4AC(mrDst, mrDst, block_y, block_x);


80

coeffCost += qs.coeffCost;

if (qs.nonZeroCoeff > 0) {
nonZeroCoeff = qs.nonZeroCoeff;
acCoeff = 1;
}

// Fulfill the info of this residual AC block


blockInfo = new ResidualBlockInfo(qs.nonZeroCoeff);
info.setBlockInfo(block_x >> 2, block_y >> 2, typeAC, blockInfo);
}
}

// Perform thresholding
if ((nonZeroCoeff > 0) && (coeffCost < CHROMA_COEFF_COST)) {
// If there's any AC block with coefficient different than zero, reset all
// chroma coefficients.
for (int block_y = 0; block_y < mbHeightC; block_y += 4) {
for (int block_x = 0; block_x < mbWidthC; block_x += 4) {

for (int jj = block_y; jj < (block_y + 4); jj++)


for (int ii = block_x; ii < (block_x + 4); ii++)
mrDst[jj][ii] = 0;
}
}

acCoeff = 0;
}

// If AC coefficients are enabled, DC coefficients must be set


if (acCoeff > 0)
dcCoeff = 1;

return (dcCoeff + acCoeff);


}

/**
* Applies a 8x8 4:2:0 chroma inverse transform and quantization on the residual
* samples.
*
* @param m2Src transformed hadamard coefficients
* @param mrSrc transformed residual coefficients
* @param mrDst inverse transformed residual coefficients
*/
private void inverseTransform(int[][] m2Src, int[][] mrSrc, int[][] mrDst) {
int[][] m2Inv = new int[dcHeightC][dcWidthC];

// Copy the source into destination matrix


for (int j = 0; j < mbHeightC; j++)
for (int i = 0; i < mbWidthC; i++)
mrDst[j][i] = mrSrc[j][i];

// Apply an inverse Hadamard transform on the quantized DC coefficients


transform.ihadamard2x2(m2Src, m2Inv);

// Restore DC coefficients through inverse quantization


quantizer.iquantization2x2DC(m2Inv, m2Inv);

// Restore DC coefficients into the transformed matrix


for (int j = 0; j < dcHeightC; j++)
for (int i = 0; i < dcWidthC; i++)
mrDst[j << 2][i << 2] = m2Inv[j][i];

// Apply inverse quantization and transform on AC coefficients


for (int block_y = 0; block_y < mbHeightC; block_y += 4) {
for (int block_x = 0; block_x < mbWidthC; block_x += 4) {
quantizer.iquantization4x4AC(mrDst, mrDst, block_y, block_x);
transform.inverse4x4(mrDst, mrDst, block_y, block_x);
}
}

}
81

A6.7 Classe Intra16x16LumaDCPredictor

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction;

import java.awt.Point;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.AlgorithmFactory;
import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;
import br.ufsc.inf.guiga.media.util.SupportMath;

/**
* Intra 16x16 Luma Prediction Mode 2 (DC): Mean of upper and left-hand samples
* (H + V).
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class Intra16x16LumaDCPredictor extends Intra16x16LumaAbstractPredictor {

public Intra16x16LumaDCPredictor(
int x,
int y,
Macroblock macroblock,
AlgorithmFactory algorithms)
{
super(x, y, macroblock, algorithms);
}

protected boolean doIntraPrediction(


YUVFrameBuffer codedFrameBuffer, int[][] mp)
{
Point p = new Point();
int predL;
int sumUp = 0;
int sumLeft = 0;
int maxW = Macroblock.MB_WIDTH;
int maxH = Macroblock.MB_HEIGHT;
boolean upAvail = access.isUpAvailable(maxW);
boolean leftAvail = access.isLeftAvailable(maxH);

// sum(x' = 0 to 15) { p[x', -1] }


if (upAvail) {
for (int x = 0; x < Macroblock.MB_WIDTH; x++) {
access.getNeighbour(x, -1, maxW, maxH, p);
sumUp += codedFrameBuffer.getY8bit(p.x, p.y);
}
}
// sum(y' = 0 to 15) { p[-1, y'] }
if (leftAvail) {
for (int y = 0; y < Macroblock.MB_HEIGHT; y++) {
access.getNeighbour(-1, y, maxW, maxH, p);
sumLeft += codedFrameBuffer.getY8bit(p.x, p.y);
}
}

if (upAvail && leftAvail) { // no edge


// predL[x, y] = (sumUp + sumLeft + 16) >> 5 , with x, y = 0..15
predL = SupportMath.rshiftRound((sumUp + sumLeft), 5);

} else if (!upAvail && leftAvail) { // upper edge


// predL[x, y] = (sumLeft + 8) >> 4, with x, y = 0..15
predL = SupportMath.rshiftRound(sumLeft, 4);

} else if (upAvail && !leftAvail) { // left edge


// predL[x, y] = (sumUp + 8) >> 4, with x, y = 0..15
predL = SupportMath.rshiftRound(sumUp, 4);

} else { // top left corner


// predL[x, y] = (1 << (BitDepthY – 1)), with x, y = 0..15
predL = 1 << (bitDepthY - 1);

// store DC prediction
for (int j = 0; j < Macroblock.MB_HEIGHT; j++) {
82

for (int i = 0; i < Macroblock.MB_WIDTH; i++) {


mp[j][i] = predL;
}
}

return true;
}
}

A6.8 Classe Intra8x8ChromaDCPredictor

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.prediction;

import java.awt.Point;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.AlgorithmFactory;
import br.ufsc.inf.guiga.media.parser.video.YUVFrameBuffer;

/**
* Intra 8x8 Chroma Prediction Mode 0 (DC).
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class Intra8x8ChromaDCPredictor extends Intra8x8ChromaAbstractPredictor {

private static final int CHROMA_BLK_WIDTH = 4;


private static final int CHROMA_BLK_HEIGHT = 4;
private static final int CHROMA_BLK_QUANTITY = 2;

public Intra8x8ChromaDCPredictor(
int x,
int y,
Macroblock macroblock,
AlgorithmFactory algorithms)
{
super(x, y, macroblock, algorithms);
}

protected boolean doIntraPrediction(


YUVFrameBuffer codedFrameBuffer, int[][] mpCb, int[][] mpCr)
{
Point p = new Point();
int maxW = Macroblock.MB_CHROMA_WIDTH;
int maxH = Macroblock.MB_CHROMA_HEIGHT;
boolean upAvail = access.isUpAvailable(maxW);
boolean leftAvail = access.isLeftAvailable(maxH);

// For each chroma block of 4x4 samples indexed by


// chroma4x4BlkIdx = 0..( 1 << ( ChromaArrayType + 1 ) ) – 1
for (int chroma4x4BlkIdx = 0; chroma4x4BlkIdx < 4; chroma4x4BlkIdx++) {
int predCb = 0;
int predCr = 0;
int sumUpCb = 0;
int sumUpCr = 0;
int sumLeftCb = 0;
int sumLeftCr = 0;

int xO = posX(chroma4x4BlkIdx);
int yO = posY(chroma4x4BlkIdx);

// sum(x' = 0 to 3) { p[x' + xO, -1] }


if (upAvail) {
for (int x = 0; x < CHROMA_BLK_WIDTH; x++) {
access.getNeighbour(x, -1, maxW, maxH, p);
sumUpCb += codedFrameBuffer.getCb8bit(p.x + xO, p.y);
sumUpCr += codedFrameBuffer.getCr8bit(p.x + xO, p.y);
}
}
// sum(y' = 0 to 3) { p[-1, y' + yO] }
if (leftAvail) {
for (int y = 0; y < CHROMA_BLK_HEIGHT; y++) {
access.getNeighbour(-1, y, maxW, maxH, p);
sumLeftCb += codedFrameBuffer.getCb8bit(p.x, p.y + yO);
83

sumLeftCr += codedFrameBuffer.getCr8bit(p.x, p.y + yO);


}
}

// TOP-LEFT and BOTTOM-RIGHT


if (((xO == 0) && (yO == 0)) || ((xO > 0) && (yO > 0))) {

if (upAvail && leftAvail) {


// predC[x+xO, y+yO] = (sumUp + sumLeft + 4) >> 3
predCb = (sumUpCb + sumLeftCb + 4) >> 3;
predCr = (sumUpCr + sumLeftCr + 4) >> 3;
} else if (leftAvail) {
// predC[x+xO, y+yO] = (sumLeft + 2) >> 2
predCb = (sumLeftCb + 2) >> 2;
predCr = (sumLeftCr + 2) >> 2;
} else if (upAvail) {
// predC[x+xO, y+yO] = (sumUp + 2) >> 2
predCb = (sumUpCb + 2) >> 2;
predCr = (sumUpCr + 2) >> 2;
} else {
// predC[x+xO, y+yO] = (1 << ( BitDepthC – 1 ))
predCb = 1 << (bitDepthC - 1);
predCr = 1 << (bitDepthC - 1);
}

} // TOP-RIGHT
else if ((xO > 0) && (yO == 0)) {

if (upAvail) {
// predC[x+xO, y+yO] = (sumUp + 2) >> 2
predCb = (sumUpCb + 2) >> 2;
predCr = (sumUpCr + 2) >> 2;
} else if (leftAvail) {
// predC[x+xO, y+yO] = (sumLeft + 2) >> 2
predCb = (sumLeftCb + 2) >> 2;
predCr = (sumLeftCr + 2) >> 2;
} else {
// predC[x+xO, y+yO] = (1 << ( BitDepthC – 1 ))
predCb = 1 << (bitDepthC - 1);
predCr = 1 << (bitDepthC - 1);
}

} // BOTTOM-LEFT
else if ((xO == 0) && (yO > 0)) {

if (leftAvail) {
// predC[x+xO, y+yO] = (sumLeft + 2) >> 2
predCb = (sumLeftCb + 2) >> 2;
predCr = (sumLeftCr + 2) >> 2;
} else if (upAvail) {
// predC[x+xO, y+yO] = (sumUp + 2) >> 2
predCb = (sumUpCb + 2) >> 2;
predCr = (sumUpCr + 2) >> 2;
} else {
// predC[x+xO, y+yO] = (1 << ( BitDepthC – 1 ))
predCb = 1 << (bitDepthC - 1);
predCr = 1 << (bitDepthC - 1);
}

// store DC prediction
for (int j = yO; j < CHROMA_BLK_HEIGHT + yO; j++) {
for (int i = xO; i < CHROMA_BLK_WIDTH + xO; i++) {
mpCb[j][i] = predCb;
mpCr[j][i] = predCr;
}
}
}

return true;
}

private int posX(int blkIdx) {


return (blkIdx % CHROMA_BLK_QUANTITY) * CHROMA_BLK_WIDTH;
}
84

private int posY(int blkIdx) {


return (blkIdx / CHROMA_BLK_QUANTITY) * CHROMA_BLK_HEIGHT;
}

A7 Classes para medição de distorção

A7.1 Interface DistortionMetric

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.decision;

/**
* Error Metric measures the energy of the residual transform coefficients after
* quantization.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public interface DistortionMetric {

/**
* Computes the distortion between two 16x16 blocks.
*
* @param orig the original block.
* @param pred the predicted block.
* @return an integer number representing the distortion between the two
* blocks. The number magnitude tends to be lower as the two blocks
* match closer.
*/
public int getDistortion16x16(int[][] orig, int[][] pred);

/**
* Computes the distortion between two 4x4 blocks.
*
* @param orig the original block.
* @param pred the predicted block.
* @param pos_y a vertical offset shall be provide if the blocks height is
* wider than 4 elements.
* @param pos_x a horizontal offset shall be provide if the blocks width is
* wider than 4 elements.
* @return an integer number representing the distortion between the two
* blocks. The number magnitude tends to be lower as the two blocks
* match closer.
*/
public int getDistortion4x4(int[][] orig, int[][] pred, int pos_y, int pos_x);

A7.1 Classe SATD

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.mode.decision;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.algorithm.Transform;

/**
* SA(T)D, the Sum of Absolute Differences of the Transformed residual data.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class SATD implements DistortionMetric {

private Transform transform;

public SATD(Transform transform) {


this.transform = transform;
}
85

public int getDistortion16x16(int[][] orig, int[][] pred) {


int satd = 0;
int ii, jj, i, j;
int[][][][] M0 = new int[4][4][4][4];
int[][] M4 = new int[4][4];
int[][] M7 = new int[4][4];

// calculate the difference (residual)


for (j = 0; j < 16; j++) {
for (i = 0; i < 16; i++) {
M0[j >> 2][i >> 2][j & 0x03][i & 0x03] = orig[j][i] - pred[j][i];
}
}

for (jj = 0; jj < 4; jj++) {


for (ii = 0; ii < 4; ii++) {
M7 = M0[jj][ii];
transform.hadamard4x4(M7, M7);
for (j = 0; j < 4; j++) {
for (i = 0; i < 4; i++) {
if ((i + j) != 0)
satd += Math.abs(M7[j][i]);
}
}
}
}

for (j = 0; j < 4; j++) {


for (i = 0; i < 4; i++)
M4[j][i] = (M0[j][i][0][0] >> 1);
}
// Hadamard of DC coeff
transform.hadamard4x4(M4, M4);

// sum SATD
for (j = 0; j < 4; j++) {
for (i = 0; i < 4; i++) {
satd += Math.abs(M4[j][i]);
}
}

return satd;
}

// Calculate 4x4 Hadamard-Transformed SAD


public int getDistortion4x4(int[][] orig, int[][] pred, int pos_y, int pos_x) {

int satd = 0;
int[][] diff = new int[4][4];
int[] d = new int[16];
int[] m = new int[16];

// calculate the difference (residual)


for (int j = 0; j < 4; j++) {
int jj = pos_y + j;
for (int i = 0; i < 4; i++) {
int ii = pos_x + i;
diff[j][i] = orig[jj][ii] - pred[jj][ii];
}
}

// TODO why the code bellow isn't equivalent to


// transform.hadamard4x4(diff, diff)?

// hadamard transform
m[0] = diff[0][0] + diff[3][0];
m[1] = diff[0][1] + diff[3][1];
m[2] = diff[0][2] + diff[3][2];
m[3] = diff[0][3] + diff[3][3];
m[4] = diff[1][0] + diff[2][0];
m[5] = diff[1][1] + diff[2][1];
m[6] = diff[1][2] + diff[2][2];
m[7] = diff[1][3] + diff[2][3];
m[8] = diff[1][0] - diff[2][0];
m[9] = diff[1][1] - diff[2][1];
86

m[10] = diff[1][2] - diff[2][2];


m[11] = diff[1][3] - diff[2][3];
m[12] = diff[0][0] - diff[3][0];
m[13] = diff[0][1] - diff[3][1];
m[14] = diff[0][2] - diff[3][2];
m[15] = diff[0][3] - diff[3][3];

d[0] = m[0] + m[4];


d[1] = m[1] + m[5];
d[2] = m[2] + m[6];
d[3] = m[3] + m[7];
d[4] = m[8] + m[12];
d[5] = m[9] + m[13];
d[6] = m[10] + m[14];
d[7] = m[11] + m[15];
d[8] = m[0] - m[4];
d[9] = m[1] - m[5];
d[10] = m[2] - m[6];
d[11] = m[3] - m[7];
d[12] = m[12] - m[8];
d[13] = m[13] - m[9];
d[14] = m[14] - m[10];
d[15] = m[15] - m[11];

m[0] = d[0] + d[3];


m[1] = d[1] + d[2];
m[2] = d[1] - d[2];
m[3] = d[0] - d[3];
m[4] = d[4] + d[7];
m[5] = d[5] + d[6];
m[6] = d[5] - d[6];
m[7] = d[4] - d[7];
m[8] = d[8] + d[11];
m[9] = d[9] + d[10];
m[10] = d[9] - d[10];
m[11] = d[8] - d[11];
m[12] = d[12] + d[15];
m[13] = d[13] + d[14];
m[14] = d[13] - d[14];
m[15] = d[12] - d[15];

d[0] = m[0] + m[1];


d[1] = m[0] - m[1];
d[2] = m[2] + m[3];
d[3] = m[3] - m[2];
d[4] = m[4] + m[5];
d[5] = m[4] - m[5];
d[6] = m[6] + m[7];
d[7] = m[7] - m[6];
d[8] = m[8] + m[9];
d[9] = m[8] - m[9];
d[10] = m[10] + m[11];
d[11] = m[11] - m[10];
d[12] = m[12] + m[13];
d[13] = m[12] - m[13];
d[14] = m[14] + m[15];
d[15] = m[15] - m[14];

// sum SATD
for (int k = 0; k < 16; ++k) {
satd += Math.abs(d[k]);
}

return ((satd + 1) >> 1);


}

A8 Classes para codificação de entropia


87

A8.1 Classe VLCTable

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.entropy;

public class VLCTable {

// [Tables 1, 2, 3, 5 and 6][TrailingOnes][TotalCoeff]


protected static final int[][][] coeffTokenCodeLength = {
{
{ 1, 6, 8, 9,10,11,13,13,13,14,14,15,15,16,16,16,16},
{ 0, 2, 6, 8, 9,10,11,13,13,14,14,15,15,15,16,16,16},
{ 0, 0, 3, 7, 8, 9,10,11,13,13,14,14,15,15,16,16,16},
{ 0, 0, 0, 5, 6, 7, 8, 9,10,11,13,14,14,15,15,16,16},
},
{ { 2, 6, 6, 7, 8, 8, 9,11,11,12,12,12,13,13,13,14,14},
{ 0, 2, 5, 6, 6, 7, 8, 9,11,11,12,12,13,13,14,14,14},
{ 0, 0, 3, 6, 6, 7, 8, 9,11,11,12,12,13,13,13,14,14},
{ 0, 0, 0, 4, 4, 5, 6, 6, 7, 9,11,11,12,13,13,13,14},
},
{ { 4, 6, 6, 6, 7, 7, 7, 7, 8, 8, 9, 9, 9,10,10,10,10},
{ 0, 4, 5, 5, 5, 5, 6, 6, 7, 8, 8, 9, 9, 9,10,10,10},
{ 0, 0, 4, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,10,10,10},
{ 0, 0, 0, 4, 4, 4, 4, 4, 5, 6, 7, 8, 8, 9,10,10,10},
},
{//Table 4: Fixed Length
{ },
},
{//Table 5: YUV 4:2:0
{ 2, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 1, 6, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 3, 7, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
},
{//Table 6: YUV 4:2:2
{ 1, 7, 7, 9, 9,10,11,12,13, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 2, 7, 7, 9,10,11,12,12, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 3, 7, 7, 9,10,11,12, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 5, 6, 7, 7,10,11, 0, 0, 0, 0, 0, 0, 0, 0}
}
};
protected static final int[][][] coeffTokenCodeValue = {
{ { 1, 5, 7, 7, 7, 7,15,11, 8,15,11,15,11,15,11, 7,4},
{ 0, 1, 4, 6, 6, 6, 6,14,10,14,10,14,10, 1,14,10,6},
{ 0, 0, 1, 5, 5, 5, 5, 5,13, 9,13, 9,13, 9,13, 9,5},
{ 0, 0, 0, 3, 3, 4, 4, 4, 4, 4,12,12, 8,12, 8,12,8},
},
{ { 3,11, 7, 7, 7, 4, 7,15,11,15,11, 8,15,11, 7, 9,7},
{ 0, 2, 7,10, 6, 6, 6, 6,14,10,14,10,14,10,11, 8,6},
{ 0, 0, 3, 9, 5, 5, 5, 5,13, 9,13, 9,13, 9, 6,10,5},
{ 0, 0, 0, 5, 4, 6, 8, 4, 4, 4,12, 8,12,12, 8, 1,4},
},
{ {15,15,11, 8,15,11, 9, 8,15,11,15,11, 8,13, 9, 5,1},
{ 0,14,15,12,10, 8,14,10,14,14,10,14,10, 7,12, 8,4},
{ 0, 0,13,14,11, 9,13, 9,13,10,13, 9,13, 9,11, 7,3},
{ 0, 0, 0,12,11,10, 9, 8,13,12,12,12, 8,12,10, 6,2},
},
{//Table 4: Fixed Length
{ },
},
{//Table 5: YUV 4:2:0
{ 1, 7, 4, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 1, 6, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
},
{//Table 6: YUV 4:2:2
{ 1,15,14, 7, 6, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 1,13,12, 5, 6, 6, 6, 5, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 1,11,10, 4, 5, 5, 4, 0, 0, 0, 0, 0, 0, 0, 0},
{ 0, 0, 0, 1, 1, 9, 8, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0}
}
};
// [tzVlcIndex (VLC Table)][total_zeros]
protected static final int[][] totalZeros4x4CodeLength =
{ { 1,3,3,4,4,5,5,6,6,7,7,8,8,9,9,9},
88

{ 3,3,3,3,3,4,4,4,4,5,5,6,6,6,6},
{ 4,3,3,3,4,4,3,3,4,5,5,6,5,6},
{ 5,3,4,4,3,3,3,4,3,4,5,5,5},
{ 4,4,4,3,3,3,3,3,4,5,4,5},
{ 6,5,3,3,3,3,3,3,4,3,6},
{ 6,5,3,3,3,2,3,4,3,6},
{ 6,4,5,3,2,2,3,3,6},
{ 6,6,4,2,2,3,2,5},
{ 5,5,3,2,2,2,4},
{ 4,4,3,3,1,3},
{ 4,4,2,1,3},
{ 3,3,1,2},
{ 2,2,1},
{ 1,1}
};
protected static final int[][] totalZeros4x4CodeValue =
{ {1,3,2,3,2,3,2,3,2,3,2,3,2,3,2,1},
{7,6,5,4,3,5,4,3,2,3,2,3,2,1,0},
{5,7,6,5,4,3,4,3,2,3,2,1,1,0},
{3,7,5,4,6,5,4,3,3,2,2,1,0},
{5,4,3,7,6,5,4,3,2,1,1,0},
{1,1,7,6,5,4,3,2,1,1,0},
{1,1,5,4,3,3,2,1,1,0},
{1,1,1,3,3,2,2,1,0},
{1,0,1,3,2,1,1,1,},
{1,0,1,3,2,1,1,},
{0,1,1,2,1,3},
{0,1,1,1,1},
{0,1,1,1},
{0,1,1},
{0,1}
};
// [tzVlcIndex (VLC Table)][total_zeros]
protected static final int[][] totalZeros2x2CodeLength =
{ { 1,2,3,3},
{ 1,2,2},
{ 1,1}
};
protected static final int[][] totalZeros2x2CodeValue =
{ { 1,1,1,0},
{ 1,1,0},
{ 1,0}
};
// [zerosLeft][run_before]
protected static final int[][] runBeforeCodeLength =
{ {1,1},
{1,2,2},
{2,2,2,2},
{2,2,2,3,3},
{2,2,3,3,3,3},
{2,3,3,3,3,3,3},
{3,3,3,3,3,3,3,4,5,6,7,8,9,10,11},
};
protected static final int[][] runBeforeCodeValue =
{ {1,0},
{1,1,0},
{3,2,1,0},
{3,2,1,1,0},
{3,2,3,2,1,0},
{3,0,1,3,2,5,4},
{7,6,5,4,3,2,1,1,1,1,1,1,1,1,1},
};
}

A8.2 Classe CAVLC

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.entropy;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.block.ResidualBlockInfo;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.ResidualBlockType;
import br.ufsc.inf.guiga.media.util.io.BitOutputStream;

/**
* (CA)VLC coding methods.
89

*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class CAVLC extends VLCTable implements EntropyOutputStream {

public static final int CAVLC_LEVEL_LIMIT = 2063;


private static final int RUNBEFORE_NUM_M1 = 6;

private BitOutputStream bitStream;

/**
* Creates an output stream that writes CAVLC coded values.
*
* @param bitStream the target bit stream that all values should be coded into.
*/
public CAVLC(BitOutputStream bitStream) {
this.bitStream = bitStream;
}

// -------------------------------------------------------------------------
// FIXED LENGTH
// -------------------------------------------------------------------------

public int write_u_v(int n, int value) {


SyntaxElement se = new SyntaxElement();

se.bitpattern = value;
se.length = n;
se.value1 = value;

writeUVLC2buffer(se, bitStream);

return se.length;
}

public int write_u_1(boolean value) {


SyntaxElement se = new SyntaxElement();

se.bitpattern = value ? 1 : 0;
se.length = 1;
se.value1 = value ? 1 : 0;

writeUVLC2buffer(se, bitStream);

return se.length;
}

// -------------------------------------------------------------------------
// EXP-GOLOMB
// -------------------------------------------------------------------------

public int write_ue_v(int value) {


SyntaxElement se = new SyntaxElement();

se.value1 = value;
se.value2 = 0;

ue_linfo(se);
symbol2uvlc(se);

writeUVLC2buffer(se, bitStream);

return se.length;
}

public int write_se_v(int value) {


SyntaxElement se = new SyntaxElement();

se.value1 = value;
se.value2 = 0;

se_linfo(se);
symbol2uvlc(se);

writeUVLC2buffer(se, bitStream);
90

return se.length;
}

/**
* Mapping for ue(v) syntax elements.<br>
* {@link SyntaxElement#length}: returns total mapped value length, including leading
* zeros<br>
* (i.e., the length of the string 0 0 .. 0 1 Xn .. X1 X0 )<br>
* {@link SyntaxElement#info}: returns mapped value Xn..X2 X1 X0
*
* @param se <br>
* <code>SyntaxElement.value1</code>: value to be mapped
*/
private void ue_linfo(SyntaxElement se) {
int nn = (se.value1 + 1) >> 1;
int i;
for (i = 0; i < 33 && nn != 0; i++) {
nn >>= 1;
}
se.length = (i << 1) + 1;
// read_bits( leadingZeroBits ) = codeNum - 2^leadingZeroBits (page 154)
se.info = se.value1 + 1 - (1 << i);
}

/**
* Mapping for se(v) syntax elements.<br>
* {@link SyntaxElement#length}: returns total mapped value length, including leading
* zeros<br>
* (i.e., the length of the string 0 0 .. 0 1 Xn .. X1 X0 )<br>
* {@link SyntaxElement#info}: returns mapped value Xn..X2 X1 X0
*
* @param se <br>
* {@link SyntaxElement#value1}: value to be mapped
*/
private void se_linfo(SyntaxElement se) {
int sign = (se.value1 <= 0) ? 1 : 0;
// n+1 is the number in the code table. Based on this we find length and
// info
int n = Math.abs(se.value1) << 1;
int nn = (n >> 1);
int i;
for (i = 0; i < 33 && nn != 0; i++) {
nn >>= 1;
}
se.length = (i << 1) + 1;
se.info = n - (1 << i) + sign;
}

/**
* Makes code word and passes it back through the attribute
* {@link SyntaxElement#bitpattern}, which receives a code word in the following
* format: <br>
* 0 0 0 ... 1 Xn ...X2 X1 X0.
*
* @param se <br>
* {@link SyntaxElement#info}: Xn..X2 X1 X0 <br>
* {@link SyntaxElement#length}: Total number of bits in the codeword
*/
private int symbol2uvlc(SyntaxElement se) {
int suffix_len = se.length >> 1;

assert (suffix_len < 32);


se.bitpattern = (1 << suffix_len) | (se.info & ((1 << suffix_len) - 1));

return 0;
}

// -------------------------------------------------------------------------
// CAVLC
// -------------------------------------------------------------------------

/**
* Makes code word and passes it back.
*
* @param se <br>
* {@link SyntaxElement#info}: Xn..X2 X1 X0 <br>
91

* {@link SyntaxElement#length}: Total number of bits in the codeword


*/
private int symbol2vlc(SyntaxElement se) {
int info_len = se.length;

// Convert info into a bit pattern integer


se.bitpattern = 0;

// VLC coding
while (--info_len >= 0) {
se.bitpattern <<= 1;
se.bitpattern |= (0x01 & (se.info >> info_len));
}
return 0;
}

// -------------------------------------------------------------------------
// PRE-DEFINED SYMBOLS WRITING FUNCTIONS
// -------------------------------------------------------------------------

public int writeMacroblockType(int value) {


return write_ue_v(value);
}

public int writeIntraChromaPredMode(int value) {


return write_ue_v(value);
}

public int writeMackoblockQpDelta(int value) {


return write_se_v(value);
}

public int writeResidualBlock(


int[] coeffLevel, int[] coeffRun, ResidualBlockType type,
ResidualBlockInfo residualBlock)
{
// Maximum number of coefficients of this block
int maxNumCoeff = getMaxNumCoeff(type);
int totalCoeff = 0; // Number of non-zero coefficients of this block
int trailingOnes = 0; // Number of +-1s
int totalZeros = 0; // Total number of zero coefficients
int vlcTableIdx = 0; // VLC Table Index
int lastCoeff = 0; // Last non-zero coefficient
int numBits = 0; // Amount of bits used to code this residual block

// Count the amount of non-zero coefficients and the trailing ones to build
// coeff_token.
for (int k = 0; k < maxNumCoeff; k++) {
int level = coeffLevel[k];
int run = coeffRun[k];

if (level != 0) {
totalZeros += run;

if (Math.abs(level) == 1) {
trailingOnes++;
trailingOnes = Math.min(trailingOnes, 3); // clip to 3
} else {
trailingOnes = 0;
}

totalCoeff++;
lastCoeff = k;
}
}

// Select coeff_token VLC Table.


vlcTableIdx = selectVlcTableIndex(type, residualBlock);

// Write coeff_token: the count of TrailingOnes and TotalCoeff.


numBits += writeCoeffToken(totalCoeff, trailingOnes, vlcTableIdx);

if (totalCoeff > 0) {
// Write trailing_ones_sign_flags: an integer with the signal of
// each trailing ones in reverse order, from highest frequency
// coefficient to DC coefficient.
92

int trailingOnesSignFlags = 0;
for (int k = lastCoeff; k > (lastCoeff - trailingOnes); k--) {
int level = coeffLevel[k];
trailingOnesSignFlags <<= 1;
if (level < 0) {
trailingOnesSignFlags |= 0x1;
}
}
if (trailingOnes > 0)
numBits += writeTrailingOnesSignFlag(trailingOnesSignFlags, trailingOnes);

// Write level_prefix and level_sufix: the remaining non-zero coefficients.


int suffixLength = ((totalCoeff > 10) && (trailingOnes < 3)) ? 1 : 0;
// Threshold to increment suffixLength: ( 3 << ( suffixLength – 1 ) )
int incVlc[] = { 0, 3, 6, 12, 24, 48, 32768 };
// TODO what is this level two or higher?
int levelTwoOrHigher = (totalCoeff > 3 && trailingOnes == 3) ? 0 : 1;

for (int k = (lastCoeff - trailingOnes); k >= 0; k--) {


int level = coeffLevel[k];
int levelTmp = level;

if (levelTwoOrHigher != 0) {
levelTwoOrHigher = 0;
if (levelTmp > 0)
levelTmp--;
else
levelTmp++;
}

numBits += writeLevel(levelTmp, suffixLength);

// update VLC table


if (Math.abs(level) > incVlc[suffixLength])
suffixLength++;

if ((k == (lastCoeff - trailingOnes)) && (Math.abs(level) > 3))


suffixLength = 2;

// Write total_zeros: the quantity of zeros that precede the highest


// frequency coefficient.
numBits += writeTotalZeros(totalZeros, totalCoeff, maxNumCoeff);

// Write run_before: the zeroLeft/runBefore pair for each


// coefficient, where zeroLeft is the total amount of zeros on the
// left of the coefficient and runBefore is the amount of zeros
// immediately on the left of the coefficient.
int zerosLeft = totalZeros;
int numCoeff = totalCoeff;
for (int k = lastCoeff; k >= 0; k--) {
int runBefore = coeffRun[k];

// For last coefficient, run is remaining total zeros.


// When zerosLeft is zero, remaining coefficients have 0 run.
if ((zerosLeft == 0) || (numCoeff <= 1))
break;

numBits += writeRunBefore(runBefore, zerosLeft);

zerosLeft -= runBefore;
numCoeff--;
}

return numBits;
}

private int getMaxNumCoeff(ResidualBlockType type) {


int maxNumCoeff = 0;

switch (type) {
case Intra16x16LumaDCLevel:
maxNumCoeff = 16; // 16 DCs from the sixteen 4x4 blocks
93

break;
case Intra16x16LumaACLevel:
maxNumCoeff = 15; // the first level/coefficient of each AC block is the DC
break;
case CbIntra8x8ChromaDCLevel:
case CrIntra8x8ChromaDCLevel:
maxNumCoeff = 4; // 4 DCs from the four 4x4 blocks
break;
case CbIntra8x8ChromaACLevel:
case CrIntra8x8ChromaACLevel:
maxNumCoeff = 15; // the first level of each AC block is the DC
break;
}

return maxNumCoeff;
}

private int selectVlcTableIndex(


ResidualBlockType type, ResidualBlockInfo residualBlock)
{
int nC = residualBlock.getMacroblockInfo().getPredictedNonZeroCoeff(type,
residualBlock);

int vlcTableIdx = 0;

if ((type == ResidualBlockType.CbIntra8x8ChromaDCLevel)
|| (type == ResidualBlockType.CrIntra8x8ChromaDCLevel))
{
// chroma DC (has its own VLC, Table 5) if nC == -1 for 4:2:0
vlcTableIdx = 4;
} else {
// selects VLC table based on nC
if (nC < 2) {
vlcTableIdx = 0;
} else if (nC < 4) {
vlcTableIdx = 1;
} else if (nC < 8) {
vlcTableIdx = 2;
} else {
vlcTableIdx = 3;
}
}

return vlcTableIdx;
}

private int writeCoeffToken(int totalCoeff, int trailingOnes, int vlcTableIdx) {


SyntaxElement se = new SyntaxElement();

if (vlcTableIdx == 3) { // Table 4: Fixed Length

se.length = 6; // 4 + 2 bit FLC

if (totalCoeff > 0) {
se.info = ((totalCoeff - 1) << 2) | trailingOnes;
} else {
se.info = 3;
}

} else { // Tables 1, 2, 3, 5 and 6: Variable Length.


// Table 1, 2 and 3: Luma
// Table 5: Chroma YUV 4:2:0
// Table 6: Chroma YUV 4:2:2
se.length = coeffTokenCodeLength[vlcTableIdx][trailingOnes][totalCoeff];
se.info = coeffTokenCodeValue[vlcTableIdx][trailingOnes][totalCoeff];
}

symbol2vlc(se);
writeUVLC2buffer(se, bitStream);

return se.length;
}

private int writeTrailingOnesSignFlag(int trailingOnesSignFlags, int trailingOnes) {


int numBits = 0;
94

if (trailingOnes >= 0) {
SyntaxElement se = new SyntaxElement();

se.length = trailingOnes;
se.info = trailingOnesSignFlags;

symbol2vlc(se);
writeUVLC2buffer(se, bitStream);

numBits = se.length;
}

return numBits;
}

private int writeLevel(int level, int levelSuffixSize) {


if (levelSuffixSize == 0)
return writeLevelVLC1(level);
else
return writeLevelVLCN(level, levelSuffixSize);
}

private int writeLevelVLC1(int level) {


SyntaxElement se = new SyntaxElement();
int levabs = Math.abs(level);
int sign = (level < 0 ? 1 : 0);

if (levabs < 8) {
se.length = levabs * 2 + sign - 1;
se.info = 1;

} else if (levabs < 16) {


// escape code1
se.length = 19;
se.info = (1 << 4) | ((levabs - 8) << 1) | sign;

} else {
int iLength = 28, numPrefix = 15;
int iCodeword, addbit;
int levabsm16 = levabs - 16;

// escape code2
if ((levabsm16) >= 2048) {
numPrefix++;
while ((levabsm16) >= (1 << (numPrefix - 3)) - 4096) {
numPrefix++;
}
}

addbit = numPrefix - 15;


iLength += (addbit << 1);
iCodeword = (1 << (12 + addbit)) | ((levabsm16) << 1) | sign;

// Assert to make sure that the code fits in the VLC


// TODO make sure that we are in High Profile to represent level_prefix > 15
if (numPrefix > 15) {
se.length = 0x0000FFFF; // This can be some other big number
se.info = iCodeword;
}

se.length = iLength;
se.info = iCodeword;
}

symbol2vlc(se);
writeUVLC2buffer(se, bitStream);

return se.length;
}

private int writeLevelVLCN(int level, int vlc) {


SyntaxElement se = new SyntaxElement();
int iCodeword;
int iLength;

int sign = (level < 0 ? 1 : 0);


95

int levabs = Math.abs(level) - 1;

int shift = vlc - 1;


int escape = (15 << shift);

if (levabs < escape) {


int sufmask = ~((0xffffffff) << shift);
int suffix = (levabs) & sufmask;
int numPrefix = (levabs) >> shift;

iLength = numPrefix + vlc + 1;


iCodeword = (1 << (shift + 1)) | (suffix << 1) | sign;
} else {
int addbit, offset;
int levabsesc = levabs - escape;
int numPrefix = 15;

iLength = 28;

if ((levabsesc) >= 2048) {


numPrefix++;
while ((levabsesc) >= (1 << (numPrefix - 3)) - 4096) {
numPrefix++;
}
}

addbit = numPrefix - 15;

iLength += (addbit << 1);


offset = (2048 << addbit) - 2048;

iCodeword = (1 << (12 + addbit)) | ((levabsesc - offset) << 1) | sign;

// Assert to make sure that the code fits in the VLC


// TODO make sure that we are in High Profile to represent level_prefix > 15
if (numPrefix > 15) {
se.length = 0x0000FFFF; // This can be some other big number
se.info = iCodeword;
}
}
se.length = iLength;
se.info = iCodeword;

symbol2vlc(se);
writeUVLC2buffer(se, bitStream);

return se.length;
}

private int writeTotalZeros(int totalZeros, int totalCoeff, int maxNumCoeff) {


int numBits = 0;

if (totalCoeff < maxNumCoeff) {


SyntaxElement se = new SyntaxElement();

int vlcTable = totalCoeff - 1;


if (maxNumCoeff == 4) {// YUV 4:2:0
se.length = totalZeros2x2CodeLength[vlcTable][totalZeros];
se.info = totalZeros2x2CodeValue[vlcTable][totalZeros];
} else {// Luma, YUV 4:4:4
se.length = totalZeros4x4CodeLength[vlcTable][totalZeros];
se.info = totalZeros4x4CodeValue[vlcTable][totalZeros];
}

symbol2vlc(se);
writeUVLC2buffer(se, bitStream);

numBits = se.length;
}

return numBits;
}

private int writeRunBefore(int runBefore, int zerosLeft) {


int numBits = 0;
96

if (zerosLeft > 0) {
SyntaxElement se = new SyntaxElement();
int vlcTable = Math.min(zerosLeft - 1, RUNBEFORE_NUM_M1);

se.length = runBeforeCodeLength[vlcTable][runBefore];
se.info = runBeforeCodeValue[vlcTable][runBefore];

symbol2vlc(se);
writeUVLC2buffer(se, bitStream);
}

return numBits;
}

// --------------------------------------------------------------------------
// HELPER
// --------------------------------------------------------------------------

/**
* Writes UVLC code to the appropriate buffer.
*
* @param se the syntax element to write in the stream.<br>
* {@link SyntaxElement#length}: number of bits to write.<br>
* {@link SyntaxElement#bitpattern}: the bits to write.
* @param currStream the stream in which the syntax element will be write.
*/
private void writeUVLC2buffer(SyntaxElement se, BitOutputStream currStream) {
currStream.write(se.bitpattern, se.length);
}

A9 Classes para controle dos macroblocos vizinhos

A9.1 Classe MacroblockAccess

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock;

import java.awt.Point;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;

/**
* Macroblock Neighbours Access.
* <p>
* This class provides facilities to access the macroblock neighbours.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public abstract class MacroblockAccess {

protected Macroblock macroblockX; // current macroblock


protected Macroblock macroblockA; // left
protected Macroblock macroblockB; // upper
protected Macroblock macroblockC; // upper-right
protected Macroblock macroblockD; // upper-left

protected int mbNr;


protected int frameWidth;
protected int picWidthInMbs;
protected int picSizeInMbs;

/**
* @param macroblockX the {@link Macroblock} owner of this {@link MacroblockAccess}.
*/
public MacroblockAccess(Macroblock macroblock) {
this.macroblockX = macroblock;

mbNr = macroblockX.getMbNr();
frameWidth = macroblockX.getSlice().getPicture().getWidth();
97

picWidthInMbs = frameWidth / Macroblock.MB_WIDTH;


picSizeInMbs = macroblockX.getSlice().getPicture().getPicSizeInMbs();
}

/**
* Checks the availability of neighboring macroblocks of the current macroblock for
* prediction and context determination.
*/
public abstract void checkAvailableNeighbours();

/**
* Gets the absolute frame pixel point from the macroblock relative point (xN, yN).
*
* @param xN input x position. The macroblock relative x coordinate within the interval
* [-1..15].
* @param yN input y position. The macroblock relative y coordinate within the interval
* [-1..15].
* @param maxW the block pixel width.
* @param maxH the block pixel height.
* @param p the {@link Point} where the absolute (x, y) coordinates will return.
* @return <code>true</code> if the {@link Point} was successfully filled with the
* absolute pixel coordinates, or <code>false</code> if there are no valid
* neighbours at the specified (xN, yN) coordinates.
*/
public abstract boolean getNeighbour(int xN, int yN, int maxW, int maxH, Point p);

/**
* Checks out if there are maxW pixels above this macroblock available for prediction.
*
* @param maxW the amount of pixels to check for availability.
* @return <code>true</code> if maxW pixels above this macroblock are available for
* prediction, or <code>false</code> otherwise.
*/
public boolean isUpAvailable(int maxW) {
Point p = new Point();
int maxH = 0;

return getNeighbour(0, -1, maxW, maxH, p);


}

/**
* Checks out if there are maxH pixels available for prediction on the left side of this
* macroblock.
*
* @param maxH the amount of pixels to check for availability
* @return <code>true</code> if maxW pixels on the left of this macroblock are available
* for prediction, or <code>false</code> otherwise.
*/
public boolean isLeftAvailable(int maxH) {
Point p = new Point();
int maxW = 0;

return getNeighbour(-1, 0, maxW, maxH, p);


}

/**
* Checks out if the pixel on the left upper corner of this macroblock available for
* prediction.
*
* @return <code>true</code> if the pixel on the left upper corner of this macroblock
are
* available for prediction, or <code>false</code> otherwise.
*/
public boolean isLeftUpAvailable() {
Point p = new Point();
int maxW = 0;
int maxH = 0;
return getNeighbour(-1, -1, maxW, maxH, p);
}

/**
* Gets the {@link Macroblock} with the given number.
*
* @param mbNr
* @return the {@link Macroblock} with the given number or <code>null</code> if its not
* available.
98

*/
public Macroblock getMacroblock(int mbNr) {
if ((mbNr < 0) || (mbNr > (picSizeInMbs - 1)))
return null;

return macroblockX.getSlice().getMacroblocks().get(mbNr);
}

/**
* Gets the column number where lies the given macroblock number.
*
* @param mbNr
* @return the column this macroblock number belongs.
*/
public int column(int mbNr) {
return (mbNr % picWidthInMbs);
}

/**
* Gets the line number where lies the given macroblock number.
*
* @param mbNr
* @return the line this macroblock number belongs.
*/
public int line(int mbNr) {
return (mbNr / picWidthInMbs);
}

/**
* @return the {@link Macroblock} on the left side of this one.
*/
public Macroblock getMacroblockA() {
return macroblockA;
}

/**
* @return the {@link Macroblock} above this one, the upper side one.
*/
public Macroblock getMacroblockB() {
return macroblockB;
}

/**
* @return the upper-right {@link Macroblock}.
*/
public Macroblock getMacroblockC() {
return macroblockC;
}

/**
* @return the upper-left {@link Macroblock}.
*/
public Macroblock getMacroblockD() {
return macroblockD;
}

A9.2 Classe MacroblockAccessNonMBAFF

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock;

import java.awt.Point;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;

/**
* Macroblock Neighbours Access for Non MBAFF coding.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class MacroblockAccessNonMBAFF extends MacroblockAccess {
99

/**
* @param macroblockX the {@link Macroblock} owner of this {@link
MacroblockAccessNonMBAFF}.
*/
public MacroblockAccessNonMBAFF(Macroblock macroblock) {
super(macroblock);
}

public void checkAvailableNeighbours() {


int mbNrA = mbNr - 1;
int mbNrB = mbNr - picWidthInMbs;
int mbNrC = mbNr - picWidthInMbs + 1;
int mbNrD = mbNr - picWidthInMbs - 1;

if (column(mbNr) != 0)
macroblockA = getMacroblock(mbNrA);
macroblockB = getMacroblock(mbNrB);
if (column(mbNr + 1) != 0)
macroblockC = getMacroblock(mbNrC);
if (column(mbNr) != 0)
macroblockD = getMacroblock(mbNrD);
}

public boolean getNeighbour(int xN, int yN, int maxW, int maxH, Point p) {
Macroblock neighbour = null;
boolean available = false;

if ((xN < 0) && (yN < 0)) {


neighbour = macroblockD;
} else if ((xN < 0) && ((yN >= 0) && (yN < maxH))) {
neighbour = macroblockA;
} else if (((xN >= 0) && (xN < maxW)) && (yN < 0)) {
neighbour = macroblockB;
} else if (((xN >= 0) && (xN < maxW)) && ((yN >= 0) && (yN < maxH))) {
neighbour = macroblockX;
} else if ((xN >= maxW) && (yN < 0)) {
neighbour = macroblockC;
} else {
neighbour = null;
}

if (neighbour != null) {
int xW = xN & (maxW - 1); // xW = (xN + maxW) % maxW
int yW = yN & (maxH - 1); // yW = (yN + maxH) % maxH
int pos_x = xW + column(neighbour.getMbNr()) * maxW;
int pos_y = yW + line(neighbour.getMbNr()) * maxH;
p.setLocation(pos_x, pos_y);

available = true;
}

return available;
}

A9.3 Classe MacroblockInfo

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.block.ResidualBlockInfo;
import br.ufsc.inf.guiga.media.codec.video.h264.vcl.datatype.ResidualBlockType;

/**
* Holds information about the residual blocks of a macroblock.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class MacroblockInfo {

private int mbNr;


private MacroblockAccess access;
100

private ResidualBlockInfo[][] lumaAcBlk;


private ResidualBlockInfo[][] cbChromaAcBlk;
private ResidualBlockInfo[][] crChromaAcBlk;

// Note: For 4:2:0, chroma statistics are unavailable

public MacroblockInfo(Macroblock macroblock) {


mbNr = macroblock.getMbNr();
access = macroblock.getMacroblockAccess();
lumaAcBlk = new ResidualBlockInfo[4][4];
cbChromaAcBlk = new ResidualBlockInfo[2][2];
crChromaAcBlk = new ResidualBlockInfo[2][2];
}

/**
* Get the {@link ResidualBlockInfo} object at a given (x,y) position.
*
* @param blkIdxX the x position.
* @param blkIdxY the y position.
* @param type the {@link ResidualBlockType}.
* @return the {@link ResidualBlockInfo} of type at position (x,y).
*/
public ResidualBlockInfo getBlockInfo(int blkIdxX, int blkIdxY, ResidualBlockType type)
{
ResidualBlockInfo blockInfo = null;

switch (type) {
case Intra16x16LumaDCLevel: // AC(0,0) and DC share the same prediction block
case Intra16x16LumaACLevel:
blockInfo = lumaAcBlk[blkIdxX][blkIdxY];
break;
case CbIntra8x8ChromaDCLevel: // AC(0,0) and DC share the same prediction block
case CbIntra8x8ChromaACLevel:
blockInfo = cbChromaAcBlk[blkIdxX][blkIdxY];
break;
case CrIntra8x8ChromaDCLevel: // AC(0,0) and DC share the same prediction block
case CrIntra8x8ChromaACLevel:
blockInfo = crChromaAcBlk[blkIdxX][blkIdxY];
break;
}

return blockInfo;
}

/**
* Set the {@link ResidualBlockInfo} object at a given (x,y) position.
*
* @param blkIdxX the x position.
* @param blkIdxY the y position.
* @param type the {@link ResidualBlockType}.
* @param blockInfo the {@link ResidualBlockInfo} of type at position (x,y).
*/
public void setBlockInfo(
int blkIdxX, int blkIdxY, ResidualBlockType type, ResidualBlockInfo blockInfo)
{
blockInfo.setMacroblockInfo(this);

switch (type) {
case Intra16x16LumaDCLevel: // AC(0,0) and DC share the same prediction block
case Intra16x16LumaACLevel:
blockInfo.setBlockIndexX(blkIdxX);
blockInfo.setBlockIndexY(blkIdxY);
lumaAcBlk[blkIdxX][blkIdxY] = blockInfo;
break;
case CbIntra8x8ChromaDCLevel: // AC(0,0) and DC share the same prediction block
case CbIntra8x8ChromaACLevel:
blockInfo.setBlockIndexX(blkIdxX);
blockInfo.setBlockIndexY(blkIdxY);
cbChromaAcBlk[blkIdxX][blkIdxY] = blockInfo;
break;
case CrIntra8x8ChromaDCLevel: // AC(0,0) and DC share the same prediction block
case CrIntra8x8ChromaACLevel:
blockInfo.setBlockIndexX(blkIdxX);
blockInfo.setBlockIndexY(blkIdxY);
crChromaAcBlk[blkIdxX][blkIdxY] = blockInfo;
break;
101

/**
* Get the Number of Nonzero Coefficients predicted from the neighboring 4x4 blocks.
*
* @param type the {@link ResidualBlockType}.
* @param residualBlock the {@link ResidualBlockInfo}.
* @return the number o nonzero coefficients predicted from the available neighbour
* blocks.
*/
public int getPredictedNonZeroCoeff(
ResidualBlockType type, ResidualBlockInfo residualBlock)
{
int blkIdxX = residualBlock.getBlockIndexX();
int blkIdxY = residualBlock.getBlockIndexY();
Macroblock mbA = null;
Macroblock mbB = null;
boolean isUpperBlockAvailable;
boolean isLeftBlockAvailable;
int nA, nB, nC = 0;

// Check the availability of the left block (block A)


if (blkIdxX == 0) {
if (access.column(mbNr) != 0) {
mbA = access.getMacroblockA();
isLeftBlockAvailable = true;
} else {
isLeftBlockAvailable = false;
}
} else {
mbA = access.getMacroblock(mbNr);
isLeftBlockAvailable = true;
}

// Check the availability of the upper block (block B)


if (blkIdxY == 0) {
if (access.line(mbNr) != 0) {
mbB = access.getMacroblockB();
isUpperBlockAvailable = true;
} else {
isUpperBlockAvailable = false;
}
} else {
mbB = access.getMacroblock(mbNr);
isUpperBlockAvailable = true;
}

// Check the position of blocks A and B


int blkIdxXA = getLeftNeighbourBlockIndex(blkIdxX, type);
int blkIdxYA = blkIdxY;
int blkIdxXB = blkIdxX;
int blkIdxYB = getLeftNeighbourBlockIndex(blkIdxY, type);

// Compute nC
if (!isLeftBlockAvailable && isUpperBlockAvailable) {
ResidualBlockInfo blockB = mbB.getMacroblockInfo().getBlockInfo(blkIdxXB,
blkIdxYB, type);
nB = blockB.getNonZeroCoeff();

// nC = nB, if only the upper block is available


nC = nB;

} else if (isLeftBlockAvailable && !isUpperBlockAvailable) {


ResidualBlockInfo blockA = mbA.getMacroblockInfo().getBlockInfo(blkIdxXA,
blkIdxYA, type);
nA = blockA.getNonZeroCoeff();

// nC = nA, if only the left block is available


nC = nA;

} else if (isLeftBlockAvailable && isUpperBlockAvailable) {


ResidualBlockInfo blockA = mbA.getMacroblockInfo().getBlockInfo(blkIdxXA,
blkIdxYA, type);
ResidualBlockInfo blockB = mbB.getMacroblockInfo().getBlockInfo(blkIdxXB,
102

blkIdxYB, type);
nA = blockA.getNonZeroCoeff();
nB = blockB.getNonZeroCoeff();

// nC = round((nA + nB)/2), when both blocks are available


nC = (nA + nB + 1) >> 1;

} else {
// nC = 0, when neither block is available
nC = 0;
}

return nC;
}

private int getLeftNeighbourBlockIndex(int blkIdx, ResidualBlockType type) {


int prevBlkIdx = 0;

switch (type) {
case CbIntra8x8ChromaDCLevel:
case CrIntra8x8ChromaDCLevel:
case CbIntra8x8ChromaACLevel:
case CrIntra8x8ChromaACLevel:
prevBlkIdx = (blkIdx != 0) ? blkIdx - 1 : 1;
break;
case Intra16x16LumaDCLevel:
case Intra16x16LumaACLevel:
prevBlkIdx = (blkIdx != 0) ? blkIdx - 1 : 3;
break;
}

return prevBlkIdx;
}
}

A9.4 Classe MacroblockPosition

package br.ufsc.inf.guiga.media.codec.video.h264.vcl.macroblock;

import br.ufsc.inf.guiga.media.codec.video.h264.vcl.Macroblock;

/**
* This class holds macroblock position.
*
* @author Guilherme Ferreira <guiga@inf.ufsc.br>
*/
public class MacroblockPosition {

public int mbX; // current MB horizontal


public int mbY; // current MB vertical
public int pixelX; // macroblock pixel horizontal position
public int pixelY; // macroblock pixel vertical position
public int pixelChromaX; // macroblock chroma pixel horizontal position
public int pixelChromaY; // macroblock chroma pixel vertical position

public MacroblockPosition(Macroblock macroblock) {


int mbNr = macroblock.getMbNr();
int frameWidth = macroblock.getSlice().getPicture().getWidth();
int picWidthInMbs = frameWidth / Macroblock.MB_WIDTH;

// calculate the macroblock (x,y) position within the frame buffer


this.mbX = mbNr % picWidthInMbs;
this.mbY = mbNr / picWidthInMbs;
this.pixelX = mbX * Macroblock.MB_WIDTH;
this.pixelY = mbY * Macroblock.MB_HEIGHT;
this.pixelChromaX = (mbX * Macroblock.MB_CHROMA_WIDTH);
this.pixelChromaY = (mbY * Macroblock.MB_CHROMA_HEIGHT);
}

}
103

Apêndice B: Diagramas UML

Neste apêndice são mostrados os diagramas das classes que compõem o codificador
proposto neste trabalho.

B1 Classes para escrita no arquivo H.264

A classe BasicMux, presente no diagrama abaixo, pertencem à JMF e foi estendida para
permitir a infra-estrutura de escrita em arquivos da JMF.

FIGURA APB.1 – DIAGRAMA DE CLASSE.

B2 Classes de controle de codificação

Tal como no parágrafo anterior, parte das classes presentes no diagrama abaixo pertencem à
JMF, as quais foram estendidas para criar a classe principal do codificador apresentado neste
trabalho.
104

FIGURA APB.2 – DIAGRAMA DE CLASSE.

B3 Classes de codificação

O diagrama abaixo demonstra a relação entre as interfaces e as classes que declaram e


implementam os modos de codificação presentes no codificador.
105

FIGURA APB.3 – DIAGRAMA DAS CLASSES DE MODO DE CODIFICAÇÃO.


106

Apêndice C: Equivalência entre os módulos

A tabela a seguir descreve os mais importantes módulos do código de referência e seus


equivalentes no código desenvolvido neste trabalho.

Módulo do código C Classe(s) equivalente(s) no código Java Descrição

block.c/.h Block.java

block.c/.h Intra16x16LumaAbstractPredictor.java O cálculo das predições Intra 16x16 e da


Intra16x16LumaDCPredictor.java transformada, presente no arquivo block.c, está
Intra16x16LumaHorizontalPredictor.java implementado nas classes Intra16x16LumaPredictor.
Intra16x16LumaPlanePredictor.java
Intra16x16LumaVerticalPredictor.java O arquivo block.c também calcula a transformada
Intra8x8ChromaAbstactPredictor.java para o componente chroma, o qual foi implementado
na classe Intra8x8ChromaAbstractPredictor.

lencod.c H264Encoder.java Ponto inicial da codificação, no código em C há um


VideoSequence.java laço que codifica cada quadro, já no código Java o
método process é chamado pela JMF para codificar
cada quadro.

mb_access.c/.h MacroblockAccess.java Funções para acesso aos macroblocos vizinhos.


MacroblockAccessNonMBAFF.java

mbuffer.c/.h Funções para gerenciamento de buffer de quadros


para referência.

mc_prediction.c/.h Intra8x8ChromaDCPredictor.java Contém o código que realiza a predição dos quatros


Intra8x8ChromaHorizontalPredictor.java modos Intra 8x8 Chroma.
Intra8x8ChromaPlanePredictor.java
Intra8x8ChromaVerticalPredictor.java

nalucommon.c NALU.java As funções de alocação de Unidades NAL.

q_matrix.c/.h Quantizer.java Funções para quantização de coeficientes.


q_offsets.c/.h IntegerQuantizer.java

transform.c/.h Transform.java Funções para operações de trasnformada em


IntegerTransform.java matrizes.

vlc.c/.h CAVLC.java Funções para codificação por entropia CAVLC e


Exp-Golomb.

TABELA APC.1 – MÓDULOS DO CÓDIGO DE REFERÊNCIA


107

Apêndice D: Ferramentas de suporte

Este apêndice contém uma breve descrição das ferramentas de suporte desenvolvidas
durante o projeto do codificador.

D1 YUVPlayer

A ferramente YUVPlayer foi implementada neste trabalho e permite a reprodução de arquivos


YUV com amostragem 4:2:0 utilizando a JMF. As figura ApD.1 e ApD.2 mostram um arquivo YUV
4:2:0 sendo reproduzido e sua estatística (taxa de bit, resolução, etc.), respectivamente.
As classes de conversão de espaços de cor e de demultiplexação de arquivos YUV
implementadas neste trabalho é quem permitem à JMF reproduzir esses tipos de arquivos.

FIGURA APD.1 – PLAYER REPRODUZINDO UM ARQUIVO YUV.

FIGURA APD.2 – ESTATÍSTICAS DO ARQUIVO SENDO REPRODUZIDO.


108

D2 AVCEncoder

AVCEncoder é a interface gráfica do codificador desenvolvido neste trabalho. Ela simplifica o


processo de codificação de um arquivo de vídeo YUV para um Byte Stream H.264/AVC.

FIGURA APD.3 – INTERFACE GRÁFICA DO CODIFICADOR DESENVOLVIDO NESTE TRABALHO.

Por meio da interface é possível configurar os parâmetros mais importantes da codificação.


Por exemplo, a resolução, o formato de amostragem e a taxa de quadros do vídeo. Todos esses
parâmetros devem ser configurados de acordo com a mídia que sera compactada. Na figura a seguir
são mostradas essas configurações:

FIGURA APD.4 – JANELA DE CONFIGURAÇÕES DO CODIFICADOR.


109

Anexo A: Parâmetros do padrão H.264

A1 Perfis do padrão H.264

High High 4:4:4


Baseline Extended Main High1 High 10
4:2:2 Predictive

Fatias I e P Sim Sim Sim Sim Sim Sim Sim

Fatias B Não Sim Sim Sim Sim Sim Sim

Fatias SI e SP Não Sim Não Não Não Não Não

Múltiplos Quadros de Referência Sim Sim Sim Sim Sim Sim Sim

Filtro Anti-Blocagem Sim Sim Sim Sim Sim Sim Sim

Codificação de entropia CAVLC Sim Sim Sim Sim Sim Sim Sim

Codificação de entropia CABAC Não Não Sim Sim Sim Sim Sim

Predição com peso Não Sim Sim Sim Sim Sim Sim

Seqüência Flexível de Macrobloco (FMO) Sim Sim Não Não Não Não Não

Seqüência Arbitrária de Fatia (ASO) Sim Sim Não Não Não Não Não

Fatias Redundantes (RS) Sim Sim Não Não Não Não Não

Particionamento de Dado (DP) Não Sim Não Não Não Não Não

Codificação Intrelaçada (PicAFF, MBAFF) Não Sim Sim Sim Sim Sim Sim

Formato de quadro 4:2:0 Sim Sim Sim Sim Sim Sim Sim

Formato de quadro 4:0:0 Não Não Não Sim Sim Sim Sim

Formato de quadro 4:2:2 Não Não Não Não Não Sim Sim

Formato de quadro 4:4:4 Não Não Não Não Não Não Sim

Amostragem de 8 Bit Sim Sim Sim Sim Sim Sim Sim

Amostragem de 9 e 10 Bit Não Não Não Não Sim Sim Sim

Amostragem de 11 a 14 Bit Não Não Não Não Não Não Sim

Adaptação de transformada 8x8 vs. 4x4 Não Não Não Sim Sim Sim Sim

Matrizes de Quantização Escalonáveis Não Não Não Sim Sim Sim Sim

Controle de QP Separado para Cb e Cr Não Não Não Sim Sim Sim Sim

Codificação Separada de Plano de Cor Não Não Não Não Não Não Sim

Codificação Preditiva sem Perda Não Não Não Não Não Não Sim

TABELA ANA.1 – PERFIS DO MPEG-4 PARTE 10 (H.264)

Durante o desenvolvimento deste trabalho, no ano de 2008, estava em processo de


aprovação um novo perfil chamado Constrained Baseline. A proposta desse perfil era promover a
compatibilidade entre os perfis Baseline e Main, de modo que dispositivos 3G pudessem codificar
seqüências de vídeo válidas para ambos perfis de decodificadores.

1
Perfis adicionados no Amendment 2, publicado em abril de 2007.
110

A2 Níveis do padrão H.264

Macroblocos Macroblocos Quadros por Quadros de


Nível Resolução Taxa de bit2
por Segundo1 por Quadro Segundo referência

1 1485 99 128 × 96 30,9 8 64 kbps


176 × 144 15 4

1b 1485 99 128 × 96 30,9 8 128 kbps


176 × 144 15 4

1.1 3000 396 176 × 144 30,3 9 192 kbps


320 × 240 10 3
352 × 288 7,5 3

1.2 6000 396 352 × 288 15 6 384 kbps

1.3 11880 396 352 × 288 30 6 768 kbps

2 11880 396 352 × 288 30 6 2 Mbps

2.1 19800 792 352 × 480 30 6 4 Mbps


352 × 576 25

2.2 20250 1620 720 × 480 15 5 4 Mbps


720 × 576 12.5

3 40500 1620 720 × 480 30 5 10 Mbps


720 × 576 25

3.1 108000 3600 1280 × 720 30 5 14 Mbps

3.2 216000 5120 1280 × 720 60 4 20 Mbps

4 245760 8192 1920 × 1080 30 4 20 Mbps


1280 × 720 60

4.1 245760 8192 1920 × 1080 30 4 50 Mbps


1280 × 720 60

4.2 491520 8192 1920 × 1080 60 4 50 Mbps

5 589824 22080 2048 × 1024 72 5 135 Mbps

5.1 983040 36864 2048 × 1024 120 5 240 Mbps


4096 × 2048 30

TABELA ANA.2 – NÍVEIS DO MPEG-4 PARTE 10 (H.264)

1
Determina o número máximo desse parâmetro.
2
Taxa de bit máxima para os perfis Baseline, Extended e Main.
111

Anexo B: Algoritmos de compressão

Este anexo fornece uma introdução às técnicas de compressão que são mais relevantes em
aplicações multimídia distribuídas. O objetivo não é fornecer uma visão exaustiva mas sim enfatizar
aspectos relacionados aos algoritmos de compressão mencionados no decorrer deste trabalho.

B1 Tipos de Compressão

As técnicas de compressão seguem duas estratégias:


• Compressão sem perdas: na compressão sem perdas, a informação original é recuperada
sem qualquer alteração após o processo de descompressão, isto é, o fluxo obtido após a
descompressão é exatamente idêntico àquele existente antes da descompressão. Estratégias
de compressão sem perdas são exigidas por certas aplicações multimídia onde a precisão da
informação é essencial, como em imagens médicas. A compressão sem perdas é também
conhecida como compressão reversível; e
• Compressão com perdas: na compressão com perdas ou compressão irreversível, a
informação obtida após a descompressão é diferente da original (antes da descompressão).
Esta é a estratégia utilizada pela maior parte dos algoritmos de compressão, tanto de áudio
quanto vídeo. Deve-se enfatizar, contudo, que muitas vezes as perdas ocorridas não são
percebidas pelo observador.

B2 Categorias de Compressão

As técnicas de compressão são classificadas em duas categorias principais: codificação1 por


entropia e codificação da fonte ("source encoding").

B2.1 Codificação de Entropia

A codificação de entropia refere-se às técnicas de compressão que não consideram a


natureza da informação a ser comprimida. Técnicas baseadas em entropia tratam todos os dados
como seqüências de bits, sem tentar otimizar a compressão através do conhecimento do tipo de

1
O termo "codificação" ("encoding") é o mais usual na terminologia de processamento de sinais
digitais. Contudo, o que os algoritmos realizam é, de fato, uma compressão e não simplesmente uma
codificação.
112

informação a ser comprimida, ou seja, essas técnicas ignoram a semântica da informação. Um


exemplo trivial de uma codificação por entropia é a substituição de uma série de 10 octetos
sucessivos de valor 0 por um caractere especial - o "flag" - seguido do número 10.
A codificação de entropia produz uma compressão sem perdas e é geralmente executada
através da supressão de sequencias repetitivas e da codificação estatística.

B2.1.1 Supressão de sequências repetitivas

A supressão de sequencia repetitivas é a mais simples e antiga técnica de compressão usada


em computação. Ela consiste na detecção de sequências de bits ou octetos (de fato, caracteres) e
sua substituição pelo número de ocorrências seguido do "flag". Dois octetos que são geralmente alvo
da substituição são aqueles representando os caracteres 0 (em dados numéricos) e branco (em
dados textuais).

B2.1.2 Codificação Estatística

A codificação estatística é uma técnica de codificação de entropia mais elaborada do que a


supressão de sequências repetitivas. Ela consiste na identificação dos padrões de bits ou “bytes”
mais frequentes em uma dada sequência e na sua substituição por menos bits. Os padrões menos
frequentes serão codificados com mais bits enquanto os mais frequentes serão codificados com
menos bits. Obviamente, há a necessidade de registro dos padrões (tanto inicial quanto a
correspondente codificação) em uma tabela que é usada na compressão e descompressão. Tal
tabela é referenciada como livro-código ("code-book'').
As duas principais formas de codificação estatística são a substituição de padrões e a
codificação de Huffman.
A substituição de padrões é usada para a codificação de informação textual. Padrões
frequentes de caracteres são substituídos por uma única palavra. Por exemplo, a palavra "multimídia"
poderia ser substituída neste texto por *M e a palavra "rede" por *R.
Na codificação de Huffman, para uma dada sequência de dados, são calculadas as
frequências de ocorrências de cada octeto. As ocorrências são armazenadas em uma tabela. A partir
dessa tabela, o algoritmo de Huffman determina o número mínimo de bits para representar cada
caractere e atribui um código que é armazenado no livro-código. Este método é usado tanto para
compressão de imagens estáticas quanto em movimento. Dependendo dos parâmetros da
implementação, um novo livro-código pode ser construído para todas imagens ou para um conjunto
de imagens. No caso de vídeo, o livro-código pode ser refeito para cada quadro ou para um conjunto
de quadros. Em todos os casos, o sistema final onde será feita a descompressão deve receber o
livro-código do sistema final onde foi feita a compressão.
113

B2.2 Codificação da Fonte

A codificação da fonte é uma técnica de compressão dependente do sinal original. Por


exemplo, um sinal de áudio tem certas características que podem ser exploradas na compressão: na
fala, a supressão do silêncio é um típico exemplo de uma transformação que é estritamente
dependente da semântica do sinal. De maneira similar, a pesquisa por blocos comuns entre quadros
sucessivos de um fluxo de vídeo é também uma operação baseada no conhecimento da natureza do
sinal.
A codificação da fonte pode produzir taxas de compressão bem mais altas do que a
compressão por entropia. Porém, essas taxas estão intimamente ligadas à semântica do dado,
sendo, assim, muito variáveis. Na realidade, a codificação por entropia e da fonte não são técnicas
mutuamente exclusivas: na compressão de som, imagem ou vídeo, as duas técnicas são combinadas
visando a obtenção da mais alta taxa de compressão possível.
A codificação da fonte pode produzir uma compressão com ou sem perdas, sendo
classificada em três tipos: codificação de transformada, codificação diferencial e quantização vetorial.

B2.2.1 Codificação de Transformada

Na codificação de transformada, o dado sofre uma transformação matemática de um domínio


espacial ou temporal para um domínio abstrato mais adequado à compressão. O processo é, na
maioria das vezes, reversível, isto é, aplicando a transformada inversa, o dado original é recuperado.
Um exemplo de transformada é a Transformada de Fourier, que permite transformar uma medida que
varia no tempo, f(t), em uma função g(λ). Essa nova função fornece a amplitude g - ou o coeficiente -
das frequências λ que compõem a função inicial. A função g(λ) é a distribuição espectral de f(t). Nas
representações espectrais de imagens, as frequências informam quão rapidamente as cores e a
luminância mudam.
A idéia que norteia o processo de codificação de transformada é que, após a transformação,
as partes mais significativas da informação - ou os coeficientes mais significativos (aqueles que
contêm mais "energia") - são facilmente identificáveis e, possivelmente, agrupados em pacotes. Isso
permite que os coeficientes mais significativos sejam codificados com maior precisão do que os
menos significativos (de fato, alguns coeficientes podem até ser descartados). O fato da técnica de
codificação de transformada considerar precisão e descartar coeficientes faz com que ela seja um
processo de compressão com perda.
Além da Transformada de Fourier, há também a Transformada de Hadamar, Transformada
de Haar e Transformada de Karhunen Loeve; a transformada matemática geralmente usada para
imagens é Transformada de Cosseno Discreta ("Discrete Cosine Transform'' - DCT).
114

B2.2.2 Codificação Diferencial

O princípio da codificação diferencial ou codificação preditiva é codificar apenas a diferença


entre o valor real de uma amostra e o próximo valor previsto. Essa diferença é chamada diferença de
predição ou termo de erro.
A codificação diferencial é particularmente adequada para sinais nos quais valores
sucessivos são significativamente diferentes de zero mas não diferem muito uns dos outros, como no
caso dos sinais de vídeo. Os três principais esquemas de codificação diferencial são: modulação de
código de pulso diferencial, modulação delta e modulação de código de pulso diferencial adaptável.
A modulação de código de pulso diferencial ("differential pulse code modulation'' - DPCM) é
um esquema onde o processo de predição (realizado através de uma função que calcula o próximo
valor) não varia no tempo. O caso mais simples consiste na transmissão no tempo tn da diferença
entre o valor da amostra em tn (o valor real) e o valor da amostra em tn+1 (o valor previsto).
A modulação delta é um caso particular da codificação DPCM, no qual a diferença entre o
valor previsto e o valor corrente é codificado com apenas um bit, indicando que o valor do sinal será
incrementado ou decrementado em um "quantum'' (uma constante pré-definida). A modulação delta é
adequada para codificar sinais cujos valores não mudam muito rapidamente para uma dada
frequência de amostras, isto é, é um esquema de codificação adequado para sinais de baixa
frequência.
A modulação de código de pulso diferencial adaptável ("adaptive differential pulse code
modulation'' - ADPCM) é uma versão mais sofisticada da codificação DPCM que, ao invés de usar
uma função de predição fixa, usa uma função variável para estimar características de curta duração
do sinal amostrado. Assim, uma extrapolação adaptável é aplicada. Como no DPCM, apenas o termo
do erro é transmitido.

B2.2.3 Quantização Vetorial

A quantização vetorial é um caso especial de substituição de padrão no qual o fluxo de dados


é dividido em blocos chamados vetores. No caso de uma imagem, por exemplo, um vetor é
geralmente um pequeno bloco, retangular ou quadrado, de pixels. O livro-código contém padrões de
vetores (pré-definidos ou dinamicamente montados). Para cada vetor de uma amostra, o livro-código
é consultado para verificar qual padrão (vetor do livro-código) que melhor combina com o vetor da
amostra. Para evitar distorções resultantes de uma diferença significativa entre o dado real e o
padrão, além da referência do padrão (o número de sua entrada na tabela), é transmitido também o
termo de erro.
115

B3 Compressão de Imagem

Existe um grande número de algoritmos de compressão para vídeo. Nesta seção serão vistos
apenas dois deles cujo funcionamento é similar a vários outros. São eles os algoritmos JPEG e
MPEG-1.

B3.1 O Padrão JPEG

O padrão JPEG é um padrão ISO originário do "Joint Photographic Expert Group" da ISO/IEC
JTC1/Subcomitê 2 [46]. Ele foi desenvolvido em colaboração com a ITU.
O JPEG é um padrão de compressão para imagens coloridas ou com níveis de cinza. Para a
compressão, ele usa uma combinação de DCT, quantização, supressão de sequências repetitivas e
codificação de Huffman, permitindo os seguintes modos de operação:

• codificação sequencial: é realizada uma única varredura na imagem, da esquerda para a


direita, do topo para a base. Esse modo de operação é com perdas;
• codificação progressiva: a codificação é feita através de múltiplas varreduras na imagem.
Esse modo de operação também é com perdas;
• codificação sem perdas: o processo de compressão é reversível; e
• codificação hierárquica: a codificação contempla vários níveis de resolução que podem ser
descomprimidos separadamente.

B3.1.1 Passos da Codificação Progressiva

Para ilustrar como o algoritmo de compressão JPEG usa as técnicas de codificação vistas
nas seções anteriores, serão vistos os passos usados por ele para a codificação progressiva e
mostrados na Figura AnB1.

FIGURA ANB.1 - PASSOS PARA COMPRESSÃO DE IMAGENS USANDO O ALGORITMO JPEG COM O MODO DE OPERAÇÃO
SEQUENCIAL.

O primeiro passo é a preparação dos blocos, na qual a imagem é dividida em blocos de 8×8
pixels. Seja, por exemplo, uma imagem de 640×480 pixels representada por três componentes: a
116

luminância Y e as diferenças de cores U e V. Se a relação entre esses componentes é 4:1:1, então o


componente Y consiste de uma matriz 640×480 e os outros dois consistem de matrizes 320×240. A
preparação dos blocos irá fornecer para o passo seguinte 4800 blocos para o componente Y, 1200
para U e 1200 para V.
O segundo passo consiste na transformação dos blocos usando DCT. A submissão dos
blocos à transformação ocorre componente por componente e, dentro de um componente, da
esquerda para a direita, do topo para a base, em um esquema chamado de ordenamento não-
entrelaçado. Os blocos são compostos de 64 valores que representam a amplitude do sinal
amostrado que é função de duas coordenadas espaciais, ou seja, a = f(x,y) onde x e y são as duas
dimensões. Após a transformação, obtém-se a função c= g(Fx,Fy) onde c é um coeficiente e Fx e Fy
são as frequências espaciais para cada direção. O resultado é outro bloco de 64 valores onde cada
valor representa um coeficiente DCT - isto é, uma determinada frequência - e não mais a amplitude
do sinal na posição amostrada (x,y). O coeficiente g(0,0), correspondente às frequências zero, é
chamado de coeficiente DC. Ele representa o valor médio das 64 amostras. Como em um bloco
representando uma porção da imagem os valores amostrados geralmente variam pouco de um ponto
para outro, os coeficientes de mais baixa frequência serão altos e os de média e alta frequência terão
valores baixos ou zero, podendo ser descartados. A energia do sinal é concentrada nas frequências
espaciais mais baixas. A Figura AnB.2 [31] é uma representação tridimensional da transformação
DCT.

FIGURA ANB.2 – REPRESENTAÇÃO TRIDIMENSIONAL DA TRANSFORMAÇÃO DCT: ANTES DA TRANSFORMAÇÃO (ESQUERDA);


DEPOIS DA TRANSFORMAÇÃO (DIREITA ).

Em uma imagem, os coeficientes de média e baixa frequência ocorrerão quando há uma


mudança brusca (em um desenho preto-e-branco, a mudança de uma zona totalmente branca para
um zona com uma linha preta representando parte da figura, por exemplo). Em fotografias - o tipo de
imagem-alvo do JPEG -, as transições entre as zonas da imagem são suaves.
117

O terceiro passo é a quantização. Nesse passo, são introduzidas perdas (até o passo da
transformação, o processo era totalmente reversível1). O passo da quantização consiste em
normalizar cada coeficiente DCT através de sua divisão por valores pré-definidos, armazenados em
uma tabela chamada tabela de quantização. Cada elemento da tabela pode ter um valor de 1 a 255.
A tabela determina quais coeficientes serão mantidos ou descartados e quais serão representados
com mais ou menos precisão (se todos os elementos da tabela têm valor 1, a quantização não terá
nenhum efeito). O incremento dos valores dos coeficientes aumenta a taxa de compressão e reduz a
fidelidade da imagem resultante. A sequência de coeficientes DC de cada bloco (g(0,0)) também é
codificada usando DPCM, o que significa calcular o termo de erro existente entre os coeficientes DC
de blocos adjacentes.
O último passo antes da transmissão ou armazenamento consiste na aplicação de algum
esquema de codificação de entropia. Nesse passo, o algoritmo JPEG aplica ou a codificação de
Huffman ou alguma técnica mais dinâmica. A ordem com que os coeficientes são pegos é em
ziguezague, visando maximizar a probabilidade de ocorrência de valores idênticos sucessivos.
O algoritmo JPEG foi concebido para compressão de imagens estáticas mas ele pode ser
usado também para compressão de vídeo, sendo referenciado como M-JPEG ("motion''-JPEG). Os
resultados são bons em termos da qualidade da imagem, mas a largura de banda requerida é alta
(entre 8 e 10 Mbps).

B3.2 Padrão MPEG

O padrão MPEG é, na verdade, uma família de padrões para gravação e transmissão de


informações de áudio e vídeo digitais. O primeiro da série foi o MPEG-1, publicado sob a referência
ISO 11172 [47]. O grupo ISO tem realizado as especificações do padrão MPEG em fases distintas,
onde cada fase tem como alvo uma aplicação específica. Para cada fase, foi dado um nome: MPEG1,
MPEG-2, MPEG-3 e MPEG-4. O padrão MPEG-1 tem como alvo aplicações que usam áudio/vídeo
armazenados em CD-ROM com resolução SIF ("Standard Interchange Fomat'' - resolução média),
exigindo uma largura de banda em torno de 1.2 MBps; o padrão MPEG-2 tem como alvo imagens
com qualidade de TV e múltiplos canais de áudio com qualidade de CD, exigindo uma largura de
banda de 4 a 6 MBps; o padrão MPEG-3 tinha como alvo imagens com qualidade HDTV, sendo
abandonado a partir do momento que o padrão MPEG-2 passou a englobar esse tipo de aplicação; o
padrão MPEG-4 foi concebido para videoconferência, utilizando pouca largura de banda.
Nesta seção, será analisado apenas o funcionamento do padrão MPEG-1, um padrão MPEG
otimizado para obter taxas de compressão de até 26:1. Como todos algoritmos do padrão MPEG, o
MPEG-1, além de usar a correlação espacial (como o JPEG), faz uso da correlação temporal entre os
quadros para fazer a compressão. Essa correlação é explorada através da divisão dos quadros em
três tipos, como será visto a seguir.

1
Na prática, é difícil encontrar codificadores que calculem DCT e IDCT (a transformação inversa) com
uma precisão que assegure que nenhuma diferença ocorrerá.
118

B3.2.1 Quadros de Referência e Intracodificados

A idéia que norteia a exploração da correlação temporal é que em uma seqüência de quadros
uma boa parte da informação é comum a eles, ou seja, os quadros possuem áreas semelhantes ou
mesmo iguais que podem ser codificadas apenas uma vez. Assim, determinados quadros
comprimidos armazenam apenas diferenças em relação a outros quadros. Um quadro que contém
informações necessárias para a reconstrução de um ou mais quadros é chamado quadro de
referência.
Sejam três quadros de um vídeo, como na Fig. AnB.4 (a). Como pode ser visto, os quadros
possuem áreas comuns, isto é, áreas de igual conteúdo (Fig. AnB.4 (b). Tais áreas, contudo, estão
situadas em diferentes posições nos três quadros. Essa diferença de posição é representada através
de um vetor chamado vetor de movimento (Fig. AnB.4 (c)) e os blocos nos quais esse vetor será
aplicado são chamados blocos combinantes ("matching blocks"). O tamanho desses blocos depende
dos componentes da imagem. No MPEG-1, uma imagem é formada por três componentes ou planos:
um plano para luminância e dois planos que representam a diferença de cor que são sub-amostrados.
Assim, um bloco combinante é, na prática, um quadrado de 16×16 pixels no plano da luminância e
quadrados de 8×8 pixels para cada um dos planos que representam a diferença de cor. A
combinação desses três quadrados é chamada de macrobloco1.

1
O termo "macrobloco" não deve ser confundido com os blocos de 8×8 pixels usados no JPEG (e
também no MPEG-1) para eliminar redundâncias espaciais via DCT.
119

FIGURA ANB.4 – EXPLORAÇÃO DA CORRELAÇÃO TEMPORAL USANDO O ALGORITMO MPEG-1.

Supondo que o quadro 3 tenha macroblocos em comum com o quadro 1 e supondo, ainda,
que o quadro 3 é construído a partir do quadro 1 (e somente dele). Neste caso, o quadro 3 é um
quadro predito ("predicted frame") ou quadro P. Ele é construído a partir do quadro de referência 1
que passa a ser um quadro intracodificado ("intracoded frame") ou quadro I. Supondo ainda que o
quadro 2 tem macroblocos em comum com o quadro 1 e o quadro 3 (Fig. AnB.4 d)). Assim,
conceitualmente, o quadro 2 pode ser reconstruído usando pedaços dos quadros 1 e 3, desde que o
quadro 3 esteja disponível quando o quadro 2 é codificado. Isso implica que os três quadros têm que
ser temporariamente armazenados.
O quadro 2 é chamado de quadro bidirecional ou quadro B, sendo construído a partir da
interpolação do intraquadro 1 e do quadro predito 3.
120

Muitas vezes dois macroblocos não combinam totalmente. Neste caso, existe uma diferença
representada aritmeticamente (o erro do termo). As áreas de um quadro P ou B para os quais não há
nenhum bloco combinante são codificadas como os macroblocos dos quadros I.
Existem algumas sequências-padrão para quadros I, P e B: IBBBPBBBI, IBBPBBPBBI e
IBBPBBPBBPBBI. Quanto mais quadros B tem a sequência, maior será taxa de compressão obtida,
porém, às custas de uma diminuição a correlação temporal entre eles e entre os quadros de
referência, prejudicando, assim, a qualidade da imagem. Além disso, os quadros I servem como
pontos de sincronização, sendo estimado que o atraso máximo entre as ocorrências de dois quadros
desse tipo não deve exceder 300 ou 400 milissegundos. Em aplicações de reprodução de vídeo onde
são oferecidas operações VCR, o intervalo de ocorrência entre quadros de referência (I ou P) não
deve exceder 150 milissegundos.

B3.2.2 Compressão de Quadros I

Os quadros do tipo I são comprimidos de maneira muito semelhante à compressão dos


quadros JPEG no modo sequencial. Cada plano de luminância e diferença de cor é dividido em
blocos de 8×8 pixels que são transformados em domínios de frequência usando DCT. O passo de
quantização é aplicado usando a tabela de quantização. Como resultado, certos coeficientes
geralmente serão descartados. As séries de coeficientes mais significativos de cada bloco
(coeficientes DC) são codificadas usando a técnica DPCM (apenas a diferença entre dois valores DC
é codificada). Os coeficientes de cada bloco são ordenados em ziguezague e um supressor de
sequências repetitivas é aplicado. Finalmente, é aplicada a codificação de Huffman.

B3.2.3 Compressão de Quadros P e B

Na compressão de quadros do tipo P e B, para cada macrobloco, é pesquisado no quadro de


referência o melhor macrobloco combinante. A diferença entre o macrobloco real e o melhor
macrobloco combinante é calculada na forma de um vetor de movimento. O termo de erro (que
também é um macrobloco) é transformado via DCT. Os passos seguintes são a quantização, o
ordenamento em ziguezague, a supressão de sequências repetitivas e a aplicação da codificação de
Huffman. Os coeficientes DC são codificados do mesmo modo que os demais, ao contrário do que
ocorre no algoritmo JPEG e nos quadros do tipo I. O vetor de movimento de cada bloco é codificado
usando a técnica DPCM já que os vetores de movimento adjacente não são significativamente
diferentes. A sequência resultante é submetida à codificação de Huffman.
121

Referências Bibliográficas

[1] RICHARDSON, Iain E. G. H.264 and MPEG-4 Video Compression: Video Coding for Next-
Generation Multimedia. West Sussex, Inglaterra: John Wiley & Sons, 2003.

[2] ITU-T & ISO/IEC JVT. H.264/AVC Reference Software Encoder version JM 14.0. Disponível em:
<http://iphome.hhi.de/suehring/tml/>. Acesso em: 3 Janeiro 2009.1

[3] INTERNATIONAL TELECOMMUNICATION UNION. Telecommunication Standardization Sector.


ITU-T Recommendation H.264: Advanced video coding for generic audiovisual services. Novembro
de 2007.2

[4] MANOEL, E. T. M. Codificação de Vídeo H.264: Estudo de codificação mista de macroblocos.


2007. 117 f.. Dissertação (Mestrado em Engenharia Elétrica) – Departamento de Engenharia Elétrica,
Universidade Federal de Santa Catarina, Florianópolis, 2007.

[5] TOURAPIS, Alexis M, et al. H.264/MPEG-4 AVC Reference Software Manual. Joint Video Team
(JVT) of ISO/IEC MPEG & ITU-T VCEG, Julho de 2007.

[6] WIEGAND, Thomas, et al. “Overview of the H.264/AVC Video Coding Standard”. IEEE
Transactions on circuits and systems for video technology, vol. 13, no. 7, p. 563-565, Julho de
2003.

[7] RICHARDSON, Iain E. G. Vcodex White Paper: An overview of H.264. Disponível em:
<http://www.vcodex.com>. Acesso em: 19 Dezembro 2007.

[8] Wikipedia. H.264/MPEG-4 AVC. Disponível em: <http://en.wikipedia.org>. Acesso em: 7 Abril
2009.

[9] ATI AVIVO. Introduction to H.264. Disponível em: <http://www.atiavivo.com>. Acesso em: 26
Agosto 2008.

[10] TELIA SONERA FINLAND. Medialab. MPEG-4 White paper. Disponível em


<http://www.medialab.fi>. Acesso em: 6 Setembro 2008.

[11] Bond. MPEG-4 AVC/H.264 Information. Mensagem disponível em: <http://forum.doom9.org>.


Acesso em: 8 Novembro 2008.

[12] Texas Instruments Developer Conference India, 8, 2005, Bangalore. Overview of the
H.264/AVC. Bangalore: 2005, 75 p.

[13] TERAOKA, Kleber. Implementação do codificador de vídeo H.264 com transformada FLICT.
2003. 75 f.. Dissertação (Mestrado em Engenharia Elétrica) – Faculdade de Engenharia Elétrica e
Computação, Universidade Estadual de Campinas, Campinas, 2003.

1
Inicialmente foi utilizada a versão 12.4 com acesso em 15 Outubro 2007.
2
Inicialmente foi usado o documento de Março de 2003.
122

[14] INTERNATIONAL TELECOMMUNICATION UNION. Telecommunication Standardization Sector.


ITU-T Recommendation H.263: Video coding for low bit rate communication. Janeiro de 2005.

[15] INTERNATIONAL TELECOMMUNICATION UNION. Telecommunication Standardization Sector.


ITU-T Recommendation H.262: Generic coding of moving pictures and associated audio information:
video. Julho de 1995.

[16] SUN MICROSYSTEMS, INC. Java Media Framework API Guide. Califórnia, EUA, 1999.

[17] SUN MICROSYSTEMS, INC. Java Media Framework 2.1.1e API README and BINARY CODE
LICENSE. Califórnia, EUA, 2003.

[18] SUN MICROSYSTEMS, INC. JMF 2.1.1 API Documentation. Disponível em:
<http://java.sun.com/javase/technologies/desktop/media/jmf/reference/api/>. Acesso em: 5 Agosto
2008.

[19] SUN MICROSYSTEMS, INC. JMF 2.1.1 - Supported Formats. Disponível em:
<http://java.sun.com/javase/technologies/desktop/media/jmf/2.1.1/formats.html>. Acesso em: 5 Agosto
2008.

[20] SUN MICROSYSTEMS, INC. JMF FAQs. Disponível em:


<http://java.sun.com/javase/technologies/desktop/media/jmf/reference/faqs/>. Acesso em: 17 Agosto
2008.

[21] SUN MICROSYSTEMS, INC. Java SE 6 API Documentation. Disponível em:


<http://java.sun.com/javase/6/docs/api/>. Acesso em: 1 Agosto 2008.

[22] SUN MICROSYSTEMS, INC. The Java Tutorials. Disponível em:


<http://java.sun.com/docs/books/tutorial>. Acesso em: 27 Dezembro 2008.

[23] DEITEL, H. M.; DEITEL, P. J. Java Como Programar. Tradução: Carlos Arthur Lang Lisbôa.
4.ed. Porto Alegre: Bookman, 2003.

[24] FREEMAN, Eric; FREEMAN, Elisabeth. Head First Design Patterns. Califórnia, EUA: O’Reilly,
2004.

[25] WILLRICH, Roberto. Compressão de dados multimídia. Florianópolis, 2008. Cap. 3, p. 31-51.

[26] JACK, Keith. Video Demystified: A handbook for the Digital Engineer. 4.ed. Oxford, Inglaterra:
Elsevier, 2005.

[27] JANUS, Scott. Video in the 21st Century. Oregon, EUA: Intel Press, 2002.

[28] WESTWATER, Raymond; FURHT, Borko. Real-Time Video Compression: techiniques and
algorithms. Massachusetts, EUA: Kluwer Academic Publishers, 1997.
123

[29] GAHANBARI, Mohammed. Standard Codecs: Image Compression to Advanced Video Coding.
Londres, Reino Unido: The Institution of Electrical Engineers, 2003.

[30] SALOMON, David. Data Compression: The Complete Reference. 3.ed. Nova Iorque, EUA:
Springer, 2004.

[31] KOLIVER, C. . Técnicas de Computação Inteligente para Obtenção de Qualidade de


Serviço. Exatec (Caxias do Sul), Caxias do Sul, v. 2, n. 1, p. 9-27, EDUCS: 2004.

[32] WINKLER, Stefan. Digital Video Quality: Vision Models and Metrics. West Sussex, Inglaterra:
John Wiley & Sons, 2005.

[33] MALVAR, H. S., et al. “Low-Complexity Transform and Quantization in H.264/AVC”. IEEE
Transactions on circuits and systems for video technology, vol. 13, no. 7, p. 598-603, Julho de
2003.

[34] WIEN, Mathias. “Variable Block-Size Transforms for H.264/AVC”. IEEE Transactions on circuits
and systems for video technology, vol. 13, no. 7, p. 604-613, Julho de 2003.

[35] BRITANAK, Vladimir. Discrete Cosine and Sine Transforms. In: RAO, K. R. e YIP, P. C. The
transform and data compression handbook. Nova Iorque, EUA: CRC Press, 2001. p. 138-212.

[36] CARVALHO, Carolina Medeiros. In: WORKSHOP DA DISCIPLINA DE FUNDAMENTOS DE


SISTEMAS MULTIMÍDIA, 2., 2006, Rio de Janeiro. Codificação por entropia no padrão MPEG-
4/AVC: CAVLC (Context-Based Adaptive Variable Length Coding) e CABAC (Context-Based Adaptive
Binary Arithmetic Coding). Rio de Janeiro: UFF, 2006.

[37] MARPE, Detlev; SCHWARZ, Heiko; WIEGAND, Tomas. “Context-Based Adaptive Binary
Arithmetic Coding in the H.264/AVC Video Compression Standard”. IEEE Transactions on circuits
and systems for video technology, vol. 13, no. 7, p. 620-636, Julho de 2003.

[38] WITTEN, Ian H.; RADFORD, M. N.; CLEARY, John G. “Arithmetic coding for data compression”.
Computing Practices, vol. 30, no. 6, p. 520-540, Junho de 1987.

[39] KATSAGGELOS, Aggelos G.; MELNIKOV, Gerry. Rate-Distortion Techniques in Image and Video
Coding. GUAN, Ling; KUNG, Sun-Yuan; LARSEN, Jan. Multimedia Image and Video Processing.
Nova Iorque, EUA: CRC Press, 2001. p. 336-364.

[40] WIEGAND, Thomas; SULLIVAN, Gary J. “Rate-Distortion Optimization for Video Compression”.
IEEE Signal Processing Magazine. p. 74-90, Novembro de 1998.

[41] GIROD, Bernd. Image and Video Compression: Rate-Distortion Theory.


124

[42] WIEGAND, Thomas, et al. “Rate-Constrained Coder Control and Comparison of Video Coding
Standards”. IEEE Transactions on circuits and systems for video technology, vol. 13, no. 7, p.
688-703, Julho de 2003.

[43] WENGER, S., et al. “RTP Payload Format for H.264 Video”, RFC 3984, Fevereiro de 2005.

[44] YU, Keman; LV, Jiangbo; LI, Jiang; LI, Shipeng. IEEE International Conference on Multimedia &
Expo. Pratical real-time video CODEC for mobile devices. Baltimore, Maryland, EUA: Julho de
2003, vol. 3, p. 509-512.

[45] ASSOCIAÇÃO BRASILEIRA DE NORMAS TÉCNICAS. NBR 15606-1: Televisão digital terrestre
– codificação de dados e especificações de transmissão para radiodifusão digital. Parte 1:
Codificação de dados. Rio de Janeiro: ABNT , 2008.

[46] WALLACE, G. K. The JPEG Still-Picture Compression Standard. Communications of the ACM,
34(4): 30-44. 1991.

[47] GALL, D. L. MPEG: A Video Compression Standard for Multimedia Applications. Communication
of the ACM, 34(4), 1991.

[48] FISCHER, Walter. Digital Video and Audio Broadcasting Technology: A pratical engineering
guide. 2. ed. Berlin, Alemanha: Springer-Verlag, 2008.

Вам также может понравиться