Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com...

141
Guilherme Maciel Ferreira Implementação de um Codificador de Vídeo H.264/AVC em Java Florianópolis 2009

Transcript of Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com...

Page 1: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

Guilherme Maciel Ferreira

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

Florianópolis2009

Page 2: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

Page 3: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

Implementação de um Codificador de Vídeo H.264/AVC em JavaGuilherme 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

Page 4: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

i

Page 5: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

Page 6: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

Page 7: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

Page 8: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

v

Page 9: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

i

Page 10: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

Page 11: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

Sumário

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

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

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

1.1 MOTIVAÇÕES.........................................................................................................................................11.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.............................................................................32.1.1 PRÉ-PROCESSAMENTO.........................................................................................................................32.1.2 MODELO TEMPORAL.............................................................................................................................42.1.2.1 Predição Inter Quadro................................................................................................................42.1.3 MODELO ESPACIAL..............................................................................................................................42.1.3.1 Predição Intra Quadro................................................................................................................42.1.3.2 Transformada.............................................................................................................................42.1.3.3 Quantização................................................................................................................................62.1.4 CODIFICAÇÃO POR ENTROPIA.................................................................................................................62.2 MELHORIAS ADOTADAS NO PADRÃO H.264..................................................................................................62.2.1 MUDANÇAS ESSENCIAIS.........................................................................................................................62.2.1.1 Mudança na unidade espacial....................................................................................................72.2.2 MELHORIAS NO MODELO TEMPORAL........................................................................................................72.2.2.1 Compensação de Movimento com Blocos de Tamanho Variável (VBSMC)...............................72.2.2.2 Compensação de Movimento com amostras precisão de ¼ de pixel (Qpel)..............................72.2.2.3 Vetores de movimento além dos limites do quadro....................................................................72.2.2.4 Múltiplos Quadros de Referência................................................................................................82.2.2.5 Filtro Anti-Blocagem...................................................................................................................82.2.2.6 Predição com peso (Weighted Prediction)..................................................................................82.2.3 MELHORIAS NO MODELO ESPACIAL..........................................................................................................82.2.3.1 Predição espacial (predição intra quadro)..................................................................................82.2.3.2 Transformada com bloco de tamanho menor.............................................................................92.2.3.3 Transformada de bloco hierárquico............................................................................................92.2.3.4 Transformada com palavra de pequeno comprimento...............................................................92.2.3.5 Transformada inversa exata.......................................................................................................92.2.4 MELHORIAS NA CODIFICAÇÃO POR ENTROPIA.............................................................................................92.2.4.1 Context Adaptive Variable Length Coding (CAVLC).................................................................102.2.4.2 Context Adaptive Binary Arithmetic Coding (CABAC)..............................................................102.2.4.3 Exponecial Golomb Variable Length Coding (Exp-Golomb).....................................................102.2.5 MELHORIAS NA ROBUSTEZ E TRANSPORTE..............................................................................................102.2.5.1 Fatias SI e SP...........................................................................................................................112.2.5.2 Seqüência Flexível de Macrobloco (FMO)................................................................................112.2.5.3 Seqüência Arbitrária de Fatia (ASO)........................................................................................112.2.5.4 Fatias Redundantes (RS).........................................................................................................112.2.5.5 Particionamento de Dado (DP).................................................................................................112.3 FUNCIONAMENTO DO CODIFICADOR H.264.................................................................................................112.3.1 ESQUEMA........................................................................................................................................12

Page 12: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

2.3.1.1 Caminho de codificação...........................................................................................................122.3.1.2 Caminho de reconstrução.........................................................................................................13

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

3.1 PLATAFORMA JAVA..............................................................................................................................143.1.1 MANIPULAÇÃO DE DADOS.....................................................................................................................143.2 JAVA MEDIA FRAMEWORK (JMF)...........................................................................................................153.2.1 HISTÓRICO.......................................................................................................................................153.2.2 COMPONENTES.................................................................................................................................15

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

4.1 VÍDEO DE ENTRADA...............................................................................................................................184.1.1 FORMATO DO QUADRO........................................................................................................................184.1.2 RESOLUÇÃO, TAXA DE QUADROS E TAXA DE BITS.......................................................................................194.2 VÍDEO DE SAÍDA...................................................................................................................................194.2.1PERFIS............................................................................................................................................204.2.1.1 Baseline....................................................................................................................................204.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.................................................................................................215.1.1 FUNÇÃO ENCODE_ONE_FRAME.............................................................................................................215.1.2 FUNÇÃO READONEFRAME..................................................................................................................225.2 PREDIÇÃO INTRA QUADRO.....................................................................................................................225.2.1 I_PCM..........................................................................................................................................235.2.2 INTRA 16X16 LUMA..........................................................................................................................235.2.2 INTRA 8X8 CHROMA..........................................................................................................................255.3 PREDIÇÃO INTER QUADRO.....................................................................................................................255.4 TRANSFORMADA, QUANTIZAÇÃO E REORDENAÇÃO.......................................................................................265.4.1 TRANSFORMADA................................................................................................................................265.4.2 QUANTIZAÇÃO...................................................................................................................................265.4.3 REORDENAÇÃO.................................................................................................................................275.5 CODIFICAÇÃO POR ENTROPIA..................................................................................................................285.5.1 EXP-GOLOMB...................................................................................................................................295.5.1.1 Conceito....................................................................................................................................295.5.1.2 Aplicação..................................................................................................................................305.5.2 CAVLC.........................................................................................................................................305.5.2.1 Codificação do número de coeficientes diferentes de zero e 1s em carreira............................315.5.2.2 Codificação do sinal de cada 1 em carreira..............................................................................325.5.2.3 Codificação da magnitude dos coeficientes diferentes de zero remanescentes.......................325.5.2.4 Codificação do número de zeros antes do último coeficiente...................................................325.5.2.5 Codificação das seqüências de zeros antes de cada coeficiente diferente de zero.................335.6 GRAVAÇÃO DO ARQUIVO CODIFICADO........................................................................................................335.6.1 FORMATOS DE SAÍDA..........................................................................................................................335.6.1.1 Fluxo de bits puro.....................................................................................................................335.6.1.2 Container MP4..........................................................................................................................34

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

6.1 FATORES TÉCNICOS..............................................................................................................................356.1.1 DIFICULDADES DURANTE O DESENVOLVIMENTO...........................................................................................356.2 CONSIDERAÇÕES PARA O FUTURO.............................................................................................................356.2.1 DESEMPENHO DE ALGORITMOS..............................................................................................................35

Page 13: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

6.2.2 TRANSPORTE E ARMAZENAMENTO...........................................................................................................366.2.3 PERFIS...........................................................................................................................................366.2.4 DECODIFICADOR................................................................................................................................36

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

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

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

B1 CLASSES PARA ESCRITA NO ARQUIVO H.264............................................................................................103B2 CLASSES DE CONTROLE DE CODIFICAÇÃO................................................................................................103B3 CLASSES DE CODIFICAÇÃO....................................................................................................................104

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

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

Page 14: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

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

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

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

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

REFERÊNCIAS BIBLIOGRÁFICAS..................................................................................................121

Page 15: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

Page 16: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

Glossário

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

Artefato de blocagemTraduçã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.

CODECUm 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.

Page 17: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

LumaCorresponde à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çãoProcesso 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.

Page 18: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

1

Page 19: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

2

Page 20: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

3

Page 21: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

4

Page 22: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

5

Page 23: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

6

Page 24: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

7

Page 25: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

8

Page 26: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

9

Page 27: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

10

Page 28: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

11

Page 29: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

12

Page 30: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

13

Page 31: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

14

Page 32: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

15

Page 33: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

- 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

16

Page 34: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

17

Page 35: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

18

Page 36: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 comprimido

Fluxo de bytes H.264 (ISO/IEC 14496-15), Anexo B da Recomendação do ITU-T.

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

19

Page 37: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

20

Page 38: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

21

Page 39: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

22

Page 40: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

23

Page 41: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

24

Page 42: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

25

Page 43: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

26

Page 44: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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).

27

Page 45: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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].

28

Page 46: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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).

29

Page 47: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 e de Figura (Sequence and Picture parameter Sets)

São os cabeçalhos que contém informações sobre todo o vídeo ou um quadro.

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

Blocos Codificados (Coded Block Pattern)

Indica quais blocos de um macrobloco contém coeficientes codificados.

Parâmetro do Quantizador (Quantiser Parameter, QP)

Transmitido como um valor delta a partir do 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).

30

Page 48: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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).

31

Page 49: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

32

Page 50: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

33

Page 51: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

5.6.1.2 Container MP4

O documento ISO 14496-14 especifica um container, que dentre outras coisas pode conter

vídeo H.264.

34

Page 52: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

35

Page 53: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

36

Page 54: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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) {

37

Page 55: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

38

Page 56: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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.

39

Page 57: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* @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

40

Page 58: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* 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 <[email protected]> */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; }

/**

41

Page 59: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* 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>.

42

Page 60: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

*/ 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;

43

Page 61: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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; }

44

Page 62: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

/** * 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.

45

Page 63: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* @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; }

}

46

Page 64: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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. *

47

Page 65: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* @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);

48

Page 66: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

// 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 <[email protected]> */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);

49

Page 67: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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.

50

Page 68: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* @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.

51

Page 69: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

*/ 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 <[email protected]> */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();

52

Page 70: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

}

/** * 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; }

53

Page 71: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

}

/** * 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);

54

Page 72: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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() {

55

Page 73: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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;

56

Page 74: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

57

Page 75: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

}

// 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];

58

Page 76: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

59

Page 77: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 } } };

60

Page 78: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

61

Page 79: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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; } } }

62

Page 80: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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); }

}

63

Page 81: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

}

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 <[email protected]> */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.

64

Page 82: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

*/ 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 <[email protected]> */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) {

65

Page 83: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.Intra16x16LumaHorizontalPredictor;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.Intra8x8ChromaHorizontalPredictor;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;

66

Page 84: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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); }

67

Page 85: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

} // 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; }

68

Page 86: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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)

69

Page 87: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

{ 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;

70

Page 88: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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();

71

Page 89: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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) {

72

Page 90: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

// 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. */

73

Page 91: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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);

74

Page 92: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

} }

}

}

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 <[email protected]> */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;

75

Page 93: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

// 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;

76

Page 94: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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);

77

Page 95: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

// 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);

78

Page 96: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

} } }

/** * 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);

79

Page 97: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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); } }

}

}

80

Page 98: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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++) {

81

Page 99: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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);

82

Page 100: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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; }

83

Page 101: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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 <[email protected]> */public class SATD implements DistortionMetric { private Transform transform; public SATD(Transform transform) { this.transform = transform; }

84

Page 102: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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];

85

Page 103: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

86

Page 104: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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},

87

Page 105: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

{ 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.

88

Page 106: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* * @author Guilherme Ferreira <[email protected]> */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);

89

Page 107: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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>

90

Page 108: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

* {@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.

91

Page 109: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

92

Page 110: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

93

Page 111: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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);

94

Page 112: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

95

Page 113: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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();

96

Page 114: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

97

Page 115: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

*/ 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 <[email protected]> */public class MacroblockAccessNonMBAFF extends MacroblockAccess {

98

Page 116: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

/** * @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 <[email protected]> */public class MacroblockInfo { private int mbNr; private MacroblockAccess access;

99

Page 117: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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;

100

Page 118: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

}

}

/** * 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,

101

Page 119: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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 <[email protected]> */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); }

}

102

Page 120: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

103

Page 121: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

104

Page 122: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

105

Page 123: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.javaIntra16x16LumaDCPredictor.javaIntra16x16LumaHorizontalPredictor.javaIntra16x16LumaPlanePredictor.javaIntra16x16LumaVerticalPredictor.javaIntra8x8ChromaAbstactPredictor.java

O cálculo das predições Intra 16x16 e da transformada, presente no arquivo block.c, está implementado nas classes Intra16x16LumaPredictor.

O arquivo block.c também calcula a transformada para o componente chroma, o qual foi implementado na classe Intra8x8ChromaAbstractPredictor.

lencod.c H264Encoder.javaVideoSequence.java

Ponto inicial da codificação, no código em C há um 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.javaMacroblockAccessNonMBAFF.java

Funções para acesso aos macroblocos vizinhos.

mbuffer.c/.h Funções para gerenciamento de buffer de quadros para referência.

mc_prediction.c/.h Intra8x8ChromaDCPredictor.javaIntra8x8ChromaHorizontalPredictor.javaIntra8x8ChromaPlanePredictor.javaIntra8x8ChromaVerticalPredictor.java

Contém o código que realiza a predição dos quatros modos Intra 8x8 Chroma.

nalucommon.c NALU.java As funções de alocação de Unidades NAL.

q_matrix.c/.h q_offsets.c/.h

Quantizer.javaIntegerQuantizer.java

Funções para quantização de coeficientes.

transform.c/.h Transform.javaIntegerTransform.java

Funções para operações de trasnformada em 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

106

Page 124: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

107

Page 125: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

108

Page 126: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

Anexo A: Parâmetros do padrão H.264

A1 Perfis do padrão H.264

Baseline Extended Main High1 High 10 High 4:2:2

High 4:4:4

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.

109

Page 127: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

A2 Níveis do padrão H.264

Nível Macroblocos por Segundo1

Macroblocos por Quadro Resolução Quadros por

SegundoQuadros de referência Taxa de bit2

1 1485 99 128 × 96176 × 144

30,915

84

64 kbps

1b 1485 99 128 × 96176 × 144

30,915

84

128 kbps

1.1 3000 396 176 × 144320 × 240352 × 288

30,3107,5

933

192 kbps

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 × 480352 × 576

3025

6 4 Mbps

2.2 20250 1620 720 × 480720 × 576

1512.5

5 4 Mbps

3 40500 1620 720 × 480720 × 576

3025

5 10 Mbps

3.1 108000 3600 1280 × 720 30 5 14 Mbps

3.2 216000 5120 1280 × 720 60 4 20 Mbps

4 245760 8192 1920 × 10801280 × 720

3060

4 20 Mbps

4.1 245760 8192 1920 × 10801280 × 720

3060

4 50 Mbps

4.2 491520 8192 1920 × 1080 60 4 50 Mbps

5 589824 22080 2048 × 1024 72 5 135 Mbps

5.1 983040 36864 2048 × 10244096 × 2048

12030

5 240 Mbps

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.

110

Page 128: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

111

Page 129: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

112

Page 130: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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).

113

Page 131: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

114

Page 132: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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

115

Page 133: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

116

Page 134: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

1Na 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á.

117

Page 135: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

118

Page 136: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

119

Page 137: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

120

Page 138: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

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.

121

Page 139: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

[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.

122

Page 140: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

[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.

123

Page 141: Implementação de um Codificador de Vídeo H.264/AVC em Java · codificador em linguagem C com esta implementação em linguagem Java, em termos de parâmetros como velocidade e

[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.

124