Guilherme Maciel Ferreira Implementação de um Codificador de Vídeo H.264 em Java Florianópolis 2008 ii Universidade Federal de Santa Catarina Bacharelado em Ciências da Computação Implementação de um Codificador de Vídeo H.264 em Java Monografia submetida à 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, Novembro de 2008 iii Implementação de um Codificador de Vídeo H.264 em Java Guilherme Maciel Ferreira Bacharelado em Ciências da Computação . . Prof. Roberto Willrich, Dr. Prof. Responsável . . Prof. Cristian Koliver, Dr. Co-orientador Banca examinadora: . . Prof. Roberto Willrich, Dr. . . Prof. Cristian Koliver, Dr. . . Prof. Jean-Marie Farines, Dr. 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 é o que nos leva a alcançar os objetivos.Agradecimentos iv 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 co-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 Brasil!Resumo Co-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 de curso consiste em rescrever em Java um codificador de vídeo H.264 originalmente escrito em linguagem C, além de um projeto orientado a objetos a partir de código estruturado.Abstract TODO v Lista de figuras FIGURA 2.1 – MODELO DE ENCADEAMENTO DOS PROCESSOS NO CODIFICADOR PADRÃO H.264 [3]. FIGURA 3.1 – ESTRUTURA DE UM PROCESSADOR COM OS ESTÁGIOS DE PROCESSAMENTO. FIGURA 4.1 – (A) ESTRUTURA E (B) EXEMPLO DE QUADRO YUV 4:2:0 PLANAR. FIGURA B.1 – PLAYER REPRODUZINDO UM ARQUIVO YUV. FIGURA B.2 – ESTATÍSTICAS DO ARQUIVO SENDO REPRODUZIDO. 9 12 13 30 30 vi Lista de tabelas TABELA 4.1 – RESUMO DAS ESPECIFICAÇÕES DO PROJETO PARA VÍDEO DE ENTRADA TABELA 4.2 – RESUMO DAS ESPECIFICAÇÕES DO PROJETO PARA VÍDEO DE SAÍDA TABELA A.2 – PERFIS DO MPEG-4 PARTE 10 (H.264) TABELA A.1 – NÍVEIS DO MPEG-4 PARTE 10 (H.264) 13 14 31 32 Sumário ACRÔNIMOS VIII GLOSSÁRIO VIII CAPÍTULO 1: INTRODUÇÃO 1 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.2 1 1 1 2 2 2 MOTIVAÇÕES POR QUE UM CODIFICADOR DE VÍDEO? POR QUE O PADRÃO H.264? POR QUE A LINGUAGEM JAVA? POR QUE A JAVA MEDIA FRAMEWORK? ORGANIZAÇÃO DO DOCUMENTO CAPÍTULO 2: CODIFICAÇÃO DE VÍDEO H.264 3 2.1 FUNDAMENTOS DA CODIFICAÇÃO DE VÍDEO 2.1.X CODIFICAÇÃO POR ENTROPIA 2.2 ASPECTOS TÉCNICOS DO PADRÃO H.264 2.2.2 MELHORIAS NA PREDIÇÃO 2.2.2.1 Compensação de Movimento com Blocos de Tamanho Variável (VBSMC) 2.2.2.2 Compensação de Movimento com amostras precisão de ¼ de pixel (Qpel) 2.2.2.3 Vetores de movimento além dos limites do quadro 2.2.2.4 Múltiplos Quadros de Referência 2.2.2.5 Filtro Anti-Blocagem 2.2.2.6 Predição com peso 2.2.3 MELHORIAS NA TRANSFORMADA E QUANTIZAÇÃO 2.2.3.1 Transformada com bloco de tamanho menor 2.2.3.2 Transformada de bloco hierárquico 2.2.3.3 Transformada com palavra de pequeno comprimento 2.2.3.4 Transformada inversa exata 2.2.4 MELHORIAS NA CODIFICAÇÃO POR ENTROPIA 2.2.4.1 Context Adaptive Variable Length Coding (CAVLC) 2.2.4.2 Context Adaptive Binary Arithmetic Coding (CABAC) 2.2.4.3 Exponecial Golomb Variable Length Coding (Exp-Golomb) 2.2.5 MELHORIAS NA ROBUSTEZ E TRANSPORTE 2.2.5.1 Fatias SI e SP 2.2.5.2 Seqüência Flexível de Macrobloco (FMO) 2.2.5.3 Seqüência Arbitrária de Fatia (ASO) 3 3 4 4 4 4 4 5 5 5 5 5 5 6 6 6 6 7 7 7 7 7 8 vii 2.2.5.4 Fatias Redundantes (RS) 2.2.5.5 Particionamento de Dado (DP) 2.3 O CODIFICADOR H.264 2.3.1 ESQUEMA 2.3.1.1 Caminho de codificação 2.3.1.2 Caminho de reconstrução 8 8 8 8 9 10 CAPÍTULO 3: CODIFICAÇÃO DE VÍDEO EM JAVA 10 3.1 3.2 3.2.1 3.2.2 10 11 11 11 PLATAFORMA JAVA JAVA MEDIA FRAMEWORK (JMF) HISTÓRICO COMPONENTES CAPÍTULO 4: ESPECIFICAÇÕES DO CODIFICADOR H.264 EM JAVA 12 4.1 4.1.1 4.1.2 4.2 4.2.1 4.2.2 4.2.3 12 13 14 14 14 14 15 VÍDEO DE ENTRADA FORMATO DO QUADRO RESOLUÇÃO, TAXA DE QUADROS E TAXA DE BITS. VÍDEO DE SAÍDA PERFIS ORIENTAÇÃO DA PALAVRA DE DADO (ENDIANESS) PLANO DE COR CAPÍTULO 5: DESENVOLVIMENTO DO CODIFICADOR H.264 EM JAVA 15 5.1 EQUIVALÊNCIA ENTRE MÓDULOS 5.1.1 LEITURA DOS QUADROS DO ARQUIVO YUV 5.1.1.1 Função encode_one_frame 5.1.1.2 Função ReadOneFrame 15 15 16 16 CAPÍTULO 6: TESTE DO CODIFICADOR H.264 EM JAVA 16 6.1 6.2 17 17 ANÁLISE DE DESEMPENHO DURANTE CODIFICAÇÃO ANÁLISE DO ARQUIVO CODIFICADO CAPÍTULO 7: CONCLUSÕES 17 7.1 17 CONSIDERAÇÕES PARA O FUTURO APÊNDICE A: CÓDIGO-FONTE 19 A1 A1.1 A2 A2.1 A2.2 A2.3 A3 A3.1 A3.2 19 19 20 20 23 24 25 25 28 CLASSES DE SUPORTE CLASSE REGISTRY CLASSES PARA LEITURA DO ARQUIVO DE VÍDEO YUV CLASSE YUVPARSER CLASSE YUVVIDEOTRACK CLASSE YUVFORMATHANDLER CLASSES PARA CONVERSÃO ENTRE ESPAÇOS DE COR CLASSE JAVAYUVTORGB CLASSE RGBCOLORCONVERTER APÊNDICE B: FERRAMENTAS DE SUPORTE 30 viii B1 B2 YUVPLAYER AVCCODER 30 30 ANEXO A: PARÂMETROS DO PADRÃO H.264 31 A1 A2 31 32 PERFIS DO PADRÃO H.264 NÍVEIS DO PADRÃO H.264 REFERÊNCIAS BIBLIOGRÁFICAS 32 Acrônimos AVC: Advanced Video CODEC. CABAC: Context-based Adaptive Binary Arithmetic Coding. CBR: Constant Bit Rate. CAVLC: Context-based Adaptive Variable Length Coding. CIF: Common Interchange Format. É uma resolução de vídeo medindo 352 por 288 pixels. DPB: Decoded Picture Buffer. FPS: Frames per Second. JMF: Java Media Framework. QCIF: Quarter Common Interchange Format. Um quarto de um CIF, mede 176 por 144 pixels. QP: Quantization Parameter. Ver Quantização. VBR: Variable Bit Rate. Glossário CODEC Um CODEC (Compression Decompression Algorithm) é um programa que codifica e decodifica dados digitais com intuito de comprimir esses dados, reduzindo a quantidade de espaço necessária para armazenar ou a largura de banda para transmiti-los. Crominância (Chroma) Corresponde à amostra dos dois sinais de cor (U e V). Geralmente possui uma freqüência de amostragem menor em relação às amostras de luma. Framework É um conjunto de classes que fornecem uma funcionalidade genérica comum a vários projetos de software. Diferente das bibliotecas, a Framework quem dita o fluxo de controle da aplicação, o que é chamado de Inversão de Controle. ix Luma Corresponde às amostras do sinal acromático (componente Y do espaço de cor YCbCr), ou brilho. Segundo [3], 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. 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) Um quadro I, ou Intra codificado, é um quadro em um vídeo que é comprimido sem fazer referência a nenhum quadro, anterior ou posterior a ele, na seqüência. Esse quadro é comprimido usando técnicas similares às utilizadas pelas técnicas de compressão de imagens estáticas, tal como as empregadas na compressão JPEG. Quadro Predito (P-Frame) Quadros P são preditos de um quadro de anterior de referência. Isso significa que para decodificar um quadro P em um dado instante de tempo, é necessário um quadro de referência anterior. Quadros P podem servir como quadro de referência para predizer outros quadros P ou B. Quantização É o ato de redução da informação, por meio do truncamento de números, para obter uma maior taxa de compressão. O parâmetro de quantização QP seleciona o nível de truncamento dos coeficientes. Quanto mais alto o QP, maior o truncamento e compressão dos dados, e consequentemente menor a qualidade do vídeo decodificado. 1 Capítulo 1: Introdução Proposto pelo Prof. Jean-Marie Farines do Departamento de Automação de Sistemas (DAS) da Universidade Federal de Santa Catarina (UFSC), este trabalho consiste no desenvolvimento de um codificador de vídeo baseado no padrão H.264/AVC utilizando a linguagem de programação Java. Esse padrão é fruto do esforço de diversos grupos de estudos e institutos de padronização, de modo que em cada um deles é conhecido por um nome distinto: - ISO/IEC 14496-10; - ITU-T H.264; - MPEG-4 Parte 10: Advanced Video Coding; Por esse motivo, nesse documento será adotado o nome H.264/AVC quando se referir ao padrão em questão. 1.1 Motivações Houveram diversas mudanças no projeto durante o seu desenvolvimento, que exigiram novas pesquisas, estudos e implementações. Considero quatro as principais motivações que levaram ao desenvolvimento do presente trabalho. Motivações as quais vou expor em ordem cronológica, por meio de respostas à perguntas que surgiram ao longo desses anos de desenvolvimento do projeto. São elas: 1.1.1 Por que um codificador de vídeo? Quando li na lista de e-mail da computação a proposta de um TCC sobre codificação de vídeo, logo fiquei interessado, era a chance de fazer algo diferente e desafiador. Prontamente entrei em contado com o Prof. Farines para obter mais detalhes e, após algumas conversas, acertamos os detalhes e iniciei minha pesquisa sobre codificação de vídeo, a qual se perpetua por aproximadamente dois anos. 1.1.2 Por que o padrão H.264? A proposta inicial do Prof. Jean-Marie era o desenvolvimento de um codificador MPEG-2 em Java, foi quando ele me apresentou ao meu atual co-orientador, a pessoa que de fato mais me ajudou no desenvolvimento desse trabalho, o Prof. Cristian Koliver. 2 O Prof. Cristian propôs algo ainda mais desafiador, o desenvolvimento de um codificador que, até o presente momento, não havia sido desenvolvido em Java, o codificador de vídeo H.264. Essa é a mais recente tecnologia de codificação de vídeo até a data de apresentação deste trabalho e possui diversas melhorias em relação aos seus antecessores. 1.1.3 Por que a linguagem Java? Muito mais que uma linguagem, Java é uma incrível tecnologia. Além de possuir uma gama enorme de recursos para facilitar a programação, ela funciona em diversos equipamentos, o que abre um grande leque de consumidores para os produtos desenvolvidos nessa plataforma. Outro fator que pesou para a escolha dessa linguagem foi o desempenho. A plataforma Java foi agregando diversas melhorias em relação a velocidade de execução de seus programas ao longo dos anos, desse modo, é um excelente exercício acadêmico comparar o desempenho entre programas desenvolvidos em C e Java. 1.1.4 Por que a Java Media Framework? A idéia inicial era portar todo o código de referência, escrito em C, para Java, tal como ele havia sido escrito. Entretanto, neste trabalho eu teria a oportunidade de colocar em prática diversos conceitos de engenharia de software, por essa razão, optei por criar um modelo orientado a objetos a partir do código de referência, que é estruturado. Após certo tempo, descobri que havia uma Framework desenvolvida em Java especialmente voltada ao tratamento de dados multimídia, principalmente áudio e vídeo. A JMF permitiu focar o trabalho no codificador, poupando esforço para desenvolver componentes de teste, tal como reprodutores de vídeo, leitores de arquivo, etc. 1.2 Organização do documento Este documento tem como objetivo descrever os diversos aspectos relacionados ao trabalho implementado. Para dar um embasamento teórico, necessário para entender o domínio do problema, o Capítulo 2 aborda os conceitos fundamentais da codificação de vídeo e em especial a codificação de vídeo utilizada pelo padrão H.264. 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. 3 Como o padrão H.264 é muito abrangente em relação às suas aplicações, no Capítulo 4 são tratadas as restrições impostas na implementação. Este capítulo é essencial para a compreensão do Capítulo 5. O Capítulo 5 é o ponto central do documento, o qual explica como foi implementado o trabalho, mencionando pontos chave do código-fonte do trabalho e do código-fonte de referência. O Apêndice A contém o código-fonte completo desenvolvido neste trabalho e é referenciado diversas vezes ao longo do Capítulo 5. Para verificar o desempenho do codificador implementado neste trabalho em relação ao código de referência, foram feitos alguns testes, cujos resultados e análises estão descritos no Capítulo 6. Por fim, o Capítulo 7 expõem as conclusões tiradas após o desenvolvimento do trabalho, além de mencionar possíveis melhorias no projeto.Capítulo 2: Codificação de Vídeo H.264 Em um primeiro momento, este capítulo aborda 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. A maioria dos documentos de referência utiliza o termo Figura (Picture) para designar tanto Frames (amostras progressivas) quanto Fields (amostras entrelaçadas), todavia, como o trabalho se restringe a vídeo progressivo, utilizo apenas o termo Quadro (Frame) para designar tanto Figura quanto Quadro. 2.1 Fundamentos da Codificação de vídeo Como funciona? Quais os principais aspectos? 2.1.X Codificação por Entropia A codificação por entropia converte uma série de símbolos, que representam os elementos da seqüê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, 4 quadros ou seqüência de quadros) e informações suplementares (informações não essenciais para correta decodificação da mídia). 2.2 Aspectos técnicos do 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 [5], 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. 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 sobre o código do codificador. 2.2.2 Melhorias na Predição 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. 5 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 somente a um quadro de referência, tal como no MPEG-2, mas permite que seja escolhido um dos quadros de referência em uma lista. 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. 2.2.2.6 Predição com peso 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. 2.2.3 Melhorias na Transformada e Quantização Além das melhorias propostas no método de predição, foram aprimorados os processos de transformada, quantização e codificação de entropia. 2.2.3.1 Transformada com bloco de tamanho menor Os padrões anteriores realizam a transformada em blocos de 8x8, no H.264/AVC baseia-se principalmente em transformadas em blocos de 4x4, principalmente para 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.2 Transformada de bloco hierárquico 6 Em casos especiais é possível aplicar uma transformada Hadamard 2×2, 2×4 ou 4×4 nos coeficientes DC (de mais baixa freqüê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.3 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.4 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 [5], tudo graças à transformada DCT ser realizada sobre números inteiros ao invés de números de ponto flutuante. Tal como é apresentado em [13], 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 CAVLC 1 e todos os outros elementos de sintaxe são codificados usando códigos de largura fixa ou Exp-Golomb de largura variável. 2.2.4.1 Context Adaptive Variable Length Coding (CAVLC) A Codificação de Largura Variável (VLC) mapea uma série de símbolos de entrada para uma série de códigos (os quais tem tamanho variável). Símbolos que ocorrem com maior freqüência são 1 CABAC seria utilizado no lugar de CAVLC. 7 mapeados para códigos com tamanho menor, e símbolos menos freqüentes são mapeados para códigos de tamanho maior. No H.264/AVC foi incluída uma forma aprimorada do VLC. 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. A Codificação Aritmética é superior à codificação de Huffman em diversos aspectos [16], 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) [15]. 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. 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. 2.2.5.2 Seqüência Flexível de Macrobloco (FMO) 8 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 independente 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, particularmente quando usadas em redes com entrega de pacotes fora de ordem, 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 um codificador reorganizar 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 O 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 vistos na figura 2.1, todavia, no H.264 esses elementos são um pouco diferente dos padrões anteriores, sendo que o acúmulo dessas pequenas melhorias proporciona uma grande taxa de compactação por parte desse padrão. 2.3.1 Esquema 9 Tal como apresentado na figura 2.1, o codificador inclui dois caminhos, um caminho de codificação (em azul) e outro de reconstrução (em vermelho). No esquema mostrado nessa figura, o caminho de codificação “flui” da esquerda para à direita e é chamado em inglês de forward path, ou caminho para à frente. Todavia, preferi 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.1 – MODELO DE ENCADEAMENTO DOS PROCESSOS NO CODIFICADOR PADRÃO H.264 [3]. 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. 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.1; 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.1, o quadro de referência é mostrado como um quadro já codificado F’ n-1. Mas a predição de referência de 10 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. 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 11 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.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.[9] 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 qual FILE, FTP e HTTP. Seria necessário implementar para suportar novos protocolos onde os dados podem trafegar. Como compacto arquivos locais, utilizo o DataSource para o protocolo FILE, que já está disponível na JMF; 12 - Demultiplexer: representa um demultiplexador, necessário para suportar novos tipos de arquivos. No meu caso, preciso implementar um para manusear os arquivos YUV puros. 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. As classes fornecidas pela JMF que implementam a interface DataSink atendem plenamente as necessidades do trabalho aqui apresentado; - 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. 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 13 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 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. FIGURA 4.1 – (A) ESTRUTURA E (B) EXEMPLO DE QUADRO YUV 4:2:0 PLANAR. 14 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 ?? quadros Formato de vídeo comprimido Fluxo de bytes bruto H.264 (ISO/IEC 14496-15) TABELA 4.2 – RESUMO DAS ESPECIFICAÇÕES DO PROJETO PARA VÍDEO DE SAÍDA 4.2.1 Perfis De acordo com [7], 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 a dispositivos de baixa capacidade de processamento e o público de aplicações via Internet. 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. Para testar em Celular verificar isso. 15 4.2.3 Plano de cor Não são separados, o que tira o macro IS_INDEPENDENT(input). Capítulo 5: Desenvolvimento do Codificador H.264 em Java Neste capítulo é explicado em detalhes o código-fonte do trabalho. Na primeira seção é abordada a forma pela qual cada parte do código de referência foi implementada no trabalho. 5.1 Equivalência entre módulos O comitê criador do padrão H.264 disponibilizou um código-fonte de referência 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 feito 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 toda uma arquitetura particular. 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. 5.1.1 Leitura dos quadros do arquivo YUV 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. 16 Após a leitura, o plano de luma é passado para um objeto da classe ByteBuffer, da mesma forma são passados cada um dos planos de cor, U e V. A classe ByteBuffer encapsula um vetor de bytes. 5.1.1.1 Função encode_one_frame No código do trabalho, o método process da classe H264Encoder eqüivale a 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. 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.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. Capítulo 6: Teste do Codificador H.264 em Java 17 Neste capítulo será realizada uma avaliação quantitativa do desempenho do codificador implementado, comparando-o a outros codificadores disponíveis. Os testes serão realizados utilizando parâmetros equivalentes. 6.1 Análise de desempenho durante codificação Comparativo entre codificadores. Parâmetros \ Máquina Codificador Java Codificador C de X264 referência Consumo Memória Clock CPU (MHz) Desempenho Quadros por segundo Taxa de bits Taxa de bits 6.2 Análise do arquivo codificado Comparação entre arquivo codificado entre o codificador Java e os outros, além de MPEG-2. Capítulo 7: Conclusões 7.1 Considerações para o futuro O codificador implementado manteve os algoritmos originais do código de referência do ITUT. Um trabalho futuro seria utilizar algoritmos mais eficientes para estimação de movimento (Motion Estimation), DCT e Quantização, para permitir uso com dispositivos móveis, os quais possuem menos poder de processamento. 18 Segundo artigo [10], 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. 19 Apêndice A: Código-fonte A1 Classes de suporte A1.1 Classe Registry package br.ufsc.inf.guiga.media; import java.io.IOException; import java.util.Vector; import import import import import javax.media.Codec; javax.media.Demultiplexer; javax.media.Multiplexer; javax.media.PackageManager; javax.media.PlugInManager; import import import import import br.ufsc.inf.guiga.media.codec.video.colorspace.JavaYUVToRGB; br.ufsc.inf.guiga.media.codec.video.h264.H264Encoder; br.ufsc.inf.guiga.media.multiplexer.video.H264Mux; br.ufsc.inf.guiga.media.multiplexer.video.MP4Mux; br.ufsc.inf.guiga.media.parser.video.YUVParser; import com.sun.media.MimeManager; /** * H.264 CODEC initialization routines. * * @author Guilherme Ferreira <[email protected]> */ public class Registry { @SuppressWarnings("unchecked") public static void registerYUVHandler() { // PackageManager maintains a registry of packages that contain JMF // classes, such as custom Players, Processors, DataSources and // DataSinks Vector<String> packagePrefix = PackageManager.getContentPrefixList(); packagePrefix.add("br.ufsc.inf.guiga"); PackageManager.setContentPrefixList(packagePrefix); PackageManager.commitContentPrefixList(); // 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); 20 // Register a plugin to convert encode H.264 Video plugin = (Codec) new H264Encoder(); name = plugin.getClass().getName(); PlugInManager.addPlugIn(name, plugin.getSupportedInputFormats(), plugin .getSupportedOutputFormats(null), PlugInManager.CODEC); // Register the 264 raw bytestream 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 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; import import import import import import import import import javax.media.BadHeaderException; javax.media.IncompatibleSourceException; javax.media.Time; javax.media.Track; javax.media.format.YUVFormat; javax.media.protocol.ContentDescriptor; javax.media.protocol.DataSource; javax.media.protocol.Positionable; javax.media.protocol.PullSourceStream; 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() { 21 super(); tracks = new Track[1]; supportedFormat = new ContentDescriptor[]{new ContentDescriptor( "video.yuv") }; stream = null; } /** * Gets the duration of this media stream when played at the default rate. * <br> * 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 DURATION_UNKNOWN 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. * @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. */ public void setSource(DataSource source) throws IOException, IncompatibleSourceException { super.setSource(source); stream = (PullSourceStream) streams[0]; // TODO how to automatic get/set YUV frame size, type, etc? providing // Controls? // track properties boolean enabled = true; float fps = 15; 22 YUVFormat yuvFormat = YUVFormatHandler.getQCIF(YUVFormat.YUV_420, fps); int frameSize = getFrameSize(yuvFormat); int numFrames = (int) (stream.getContentLength() / frameSize); Time duration = new Time((numFrames / fps)); Time startTime = new Time(0); int numBuffer = 1; // 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 * 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 Track interface. The Track * interface also provides methods for enabling or disabling a track. <br> * 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 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 { return tracks; } /** * 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 <code>YUVFormat</code> of this frame. * @return the amount of bytes required to store a YUV frame with such * format. */ protected int getFrameSize(YUVFormat videoFormat) { int symbolSizeInBytes = 1; // 8-bit for each Y, Cb and Cr pixel int int int int frameWidth = videoFormat.getSize().width; frameHeight = videoFormat.getSize().height; frameChromaWidth = videoFormat.getSize().width / 2; frameChromaHeight = videoFormat.getSize().height / 2; int int int int int imgSizeY = frameWidth * frameHeight; imgSizeUV = frameChromaWidth * frameChromaHeight; bytesY = imgSizeY * symbolSizeInBytes; bytesUV = imgSizeUV * symbolSizeInBytes; frameSizeInBytes = bytesY + 2 * bytesUV; return frameSizeInBytes; } 23 } A2.2 Classe YUVVideoTrack package br.ufsc.inf.guiga.media.parser.video; import import import import import import javax.media.Buffer; javax.media.Time; javax.media.Track; javax.media.format.VideoFormat; javax.media.format.YUVFormat; javax.media.protocol.PullSourceStream; import com.sun.media.parser.BasicTrack; /** * YUV Video Track. <br> * 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; /** * * @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; } /** * 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, TIME_UNKNOWN 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; 24 } /** * Converts the given media time to the corresponding frame number. <br> * 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, FRAME_UNKNOWN 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 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 * */ public int return } /** * @return * */ public int return } the total number of frames this video contais. This total number is calculated through total content size by the frame size. getNumberOfFrames() { numberOfFrames; the next frame to be read by this track. Starting from 0 to total number of frames. getCurrentFrame() { currentFrame; /** * Set the next frame to be read by this Track. * * @param currentFrame * the next frame to be read by <code>readFrame</code>. */ public void setCurrentFrame(int currentFrame) { this.currentFrame = currentFrame; } } A2.3 Classe YUVFormatHandler package br.ufsc.inf.guiga.media.parser.video; 25 import java.awt.Dimension; import javax.media.format.YUVFormat; /** * Contains standard YUV formats. * @author Guilherme */ public class YUVFormatHandler { public static YUVFormat getSQCIF(int yuvType, float frameRate) { return new YUVFormat(new Dimension(128, 96), 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0, 0, 0); } public static YUVFormat getQCIF(int yuvType, float frameRate) { return new YUVFormat(new Dimension(176, 144), 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0, 0, 0); } public static YUVFormat getCIF(int yuvType, float frameRate) { return new YUVFormat(new Dimension(352, 288), 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0, 0, 0); } public static YUVFormat get4CIF(int yuvType, float frameRate) { return new YUVFormat(new Dimension(704, 576), 0, YUVFormat.byteArray, frameRate, yuvType, 0, 0, 0, 0, 0); } } A3 Classes para conversão entre espaços de cor A3.1 Classe JavaYUVtoRGB package br.ufsc.inf.guiga.media.codec.video.colorspace; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import import import import import javax.media.Buffer; javax.media.Format; javax.media.format.RGBFormat; javax.media.format.VideoFormat; javax.media.format.YUVFormat; import br.ufsc.inf.guiga.media.parser.video.YUVFormatHandler; import br.ufsc.inf.guiga.media.util.RGBColorConverter; import com.sun.media.BasicCodec; /** * Converts YCbCr color space input buffer into RGB output buffer color space. * Supported YUV formats: <br> - YUV Planar 4:2:0 * * @author Guilherme Ferreira <[email protected]> * */ public class JavaYUVToRGB extends BasicCodec { private static final String PLUGIN_NAME = "YUV To RGB Converter"; protected ByteBuffer Y; protected ByteBuffer Cb; protected ByteBuffer Cr; protected int frameWidth; 26 protected int frameHeight; protected int[] dataOut; /** * Initializes the Y, Cb and Cr arrays. * * @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 frameWidth * frameHeight size, and Cb and Cr has * one fourth of Y area each. */ protected void setYUVData(byte[] data) { int crOffset = Y.capacity(); int cbOffset = crOffset + Cb.capacity(); System.arraycopy(data, 0, Y.array(), 0, Y.capacity()); System.arraycopy(data, crOffset, Cb.array(), 0, Cb.capacity()); System.arraycopy(data, cbOffset, Cr.array(), 0, Cr.capacity()); } protected int getY8bit(int pos) { return (int) (Y.get(pos) & 0xff); } protected int getCb8bit(int pos) { return (int) (Cb.get(pos) & 0xff); } protected int getCr8bit(int pos) { return (int) (Cr.get(pos) & 0xff); } /** * Convert the YCbCr pixels into RGB pixels and fill <code>data</code> * with the converted frame. * * @param data * a valid buffer with frameWidth * frameHeight size. */ protected void getRGBData(int[] data) { int x, y; // pixel positions int indexY, indexU, indexV; // YUV indexes int luma, chromaU, chromaV; int rgbPixel; // build image RGB pixels from YUV luma-chroma pixels samples for (y = 0; y < frameHeight; y++) { for (x = 0; x < frameWidth; x++) { // calculate array components indexes indexY = x + y * frameWidth; indexU = (y / 2) * (frameWidth / 2) + (x / 2); indexV = indexU; // get each YUV component from array luma = getY8bit(indexY); chromaU = getCb8bit(indexU); chromaV = getCr8bit(indexV); // convert YUV into RGB rgbPixel = RGBColorConverter.getRGB24bit(luma, chromaU, chromaV); data[indexY] = rgbPixel; } } } /** * Converts a YCbCr frame into RGB frame, suitable to the default JMF * players renderization. * * @param input * The <code>Buffer</code> that contains the frame data in * YCbCr color space. * @param output * The <code>Buffer</code> in which to store the converted RGB * frame data. * @return BUFFER_PROCESSED_OK if the converted is successful. Other 27 * possible return codes are defined in <code>PlugIn</code>. */ public int process(Buffer in, Buffer out) { byte[] dataIn = (byte[]) in.getData(); setYUVData(dataIn); getRGBData(dataOut); out.setFormat(outputFormat); out.setData(dataOut); out.setLength(dataOut.length); return BUFFER_PROCESSED_OK; } /** * Lists all of the input formats that this CODEC accepts. * * @return An array that contains the supported input Formats. */ public Format[] getSupportedInputFormats() { Format[] formats = {YUVFormatHandler.getQCIF(YUVFormat.YUV_420, Format.NOT_SPECIFIED) }; return formats; } /** * 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 Format. If input is null, this * method lists all of the output formats supported by this plug-in. * * @param input * The 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) { List<Format> formats = new ArrayList<Format>(); if ((input != null) && (input instanceof VideoFormat)) { VideoFormat vf = (VideoFormat) input; formats.add(new RGBFormat(vf.getSize(), Format.NOT_SPECIFIED, Format.intArray, vf.getFrameRate(), 32, 0x00FF0000, 0x0000FF00, 0x000000FF)); } return formats.toArray(new Format[1]); } /** * Sets the format of the data to be input to this CODEC. * * @param format * The Format to be set. * @return The Format that was set, which might be the supported Format that * most closely matches the one specified. Returns null if the * specified Format is not supported and no reasonable match could * be found. */ public Format setInputFormat(Format format) { if (format instanceof YUVFormat) { YUVFormat yuvFormat = (YUVFormat) format; inputFormat = yuvFormat; frameWidth = yuvFormat.getSize().width; frameHeight = yuvFormat.getSize().height; int frameUVWidth = 0; int frameUVHeight = 0; switch (yuvFormat.getYuvType()) { case YUVFormat.YUV_420: frameUVWidth = yuvFormat.getSize().width / 2; frameUVHeight = yuvFormat.getSize().height / 2; break; 28 } Y = ByteBuffer.allocate(frameWidth * frameHeight); Cb = ByteBuffer.allocate(frameUVWidth * frameUVHeight); Cr = ByteBuffer.allocate(frameUVWidth * frameUVHeight); dataOut = new int[frameWidth * frameHeight]; return format; } return null; } /** * Sets the format for the data this CODEC outputs. * * @param format * The Format to be set. * @return The Format that was set, which might be the Format that most * closely matched the one specified. Returns null if the specified * Format is not supported and no reasonable match could be found. */ public Format setOutputFormat(Format format) { outputFormat = null; if ((format != null) && (format instanceof RGBFormat)) { outputFormat = (RGBFormat) format; } return outputFormat; } /** * @return human readable plugin name */ public String getName() { return PLUGIN_NAME; } } A3.2 Classe RGBColorConverter package br.ufsc.inf.guiga.media.util; /** * Convert YCbCr color space into RGB color space. Using the following formula: * <br>'R = 1.164(Y - 16) + 1.596(Cr - 128) * <br>'G = 1.164(Y - 16) - 0.813(Cr - 128) - 0.392(Cb - 128) * <br>'B = 1.164(Y - 16) + 2.017(Cb - 128) * * @author Guilherme Ferreira <[email protected]> */ public class RGBColorConverter { public static int getRed(int y, int u, int v) { return clip(Math.abs(1.164 * (y - 16) + 1.596 * (v - 128))); } public static int getGreen(int y, int u, int v) { return clip(Math.abs(1.164 * (y - 16) - 0.813 * (v - 128) - 0.392 * (u - 128))); } public static int getBlue(int y, int u, int v) { return clip(Math.abs(1.164 * (y - 16) + 2.017 * (u - 128))); } public static int getRGB24bit(int y, int u, int v) { // get each RGB component from YUV components int r = RGBColorConverter.getRed(y, u, v); int g = RGBColorConverter.getGreen(y, u, v); int b = RGBColorConverter.getBlue(y, u, v); 29 // build the RGB pixel from the components return (r << 16 | g << 8 | b); } /** * Avoid value overflow * @param value * @return */ protected static int clip(double value) { return (value > 254) ? 254 : (int) value; } } 30 Apêndice B: Ferramentas de suporte B1 YUVPlayer Essa ferramente foi implementada neste trabalho e permite a reprodução de arquivos YUV 4:2:0 utilizando a JMF. As figura B.1 e B.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 B.1 – PLAYER REPRODUZINDO UM ARQUIVO YUV. FIGURA B.2 – ESTATÍSTICAS DO ARQUIVO SENDO REPRODUZIDO. B2 AVCCoder 31 Anexo A: Parâmetros do padrão H.264 A1 Perfis do padrão H.264 Baseline Extended Main High High 10 High 4:2:2 High 4:4:4 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 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 Interlaced Coding (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 8x8 vs. 4x4 Transform Adaptivity 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 Não Cr 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 A.2 – PERFIS DO MPEG-4 PARTE 10 (H.264) Predictive 32 A2 Níveis do padrão H.264 Quadros por Segundo Quadros de Taxa de bit2 referência 128 × 96 176 × 144 30,9 15 8 4 64 kbps 99 128 × 96 176 × 144 30,9 15 8 4 128 kbps 3000 396 176 × 144 320 × 240 352 × 288 30,3 10 7,5 9 3 3 192 kbps 1.2 6000 396 352 × 288 15 6 384 kbps 1.3 11880 396 352 × 288 30 6 768 kbps 11880 396 352 × 288 30 6 2 Mbps 2.1 19800 792 352 × 480 352 × 576 30 25 6 4 Mbps 2.2 20250 1620 720 × 480 720 × 576 15 12.5 5 4 Mbps 40500 1620 720 × 480 720 × 576 30 25 5 10 Mbps 3.1 108000 3600 1280 × 720 30 5 14 Mbps 3.2 216000 5120 1280 × 720 60 4 20 Mbps 245760 8192 1920 × 1080 30 1280 × 720 60 4 20 Mbps 4.1 245760 8192 1920 × 1080 30 1280 × 720 60 4 50 Mbps 4.2 491520 8192 1920 × 1080 60 4 50 Mbps 589824 22080 2048 × 1024 72 5 135 Mbps 983040 36864 2048 × 1024 120 4096 × 2048 30 5 240 Mbps Nível Macroblocos Macrobolos Resolução por Segundo1 por Quadro 1 1485 99 1b 1485 1.1 2 3 4 5 5.1 TABELA A.1 – NÍVEIS DO MPEG-4 PARTE 10 (H.264) Referências Bibliográficas [1] RICHARDSON, Iain E. G. H.264 and MPEG-4 Video Compression: Video Coding for NextGeneration Multimedia. West Sussex, Inglaterra: John Wiley & Sons, 2003. [2] INTERNATIONAL TELECOMMUNICATION UNION. Telecommunication Standardisation Sector. ITU-T Recommendation H.264: Advanced video coding for generic audiovisual services. ISO/IEC 14496-10. 2003. 1 2 Determina o número máximo desse parâmetro. Taxa de bit máxima para os perfis Baseline, Extended e Main. 33 [3] 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. [4] SUN MICROSYSTEMS, INC. Java Media Framework API Guide. Califórnia, EUA, 1999. [5] WIEGAND, T.; SULLIVAN, G. J.; BJØNTEGAARD G. e LUTHRA A. “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. [6] TOURAPIS, Alexis M.; LEONTARIS, Athanasios; SÜHRING, Karsten e SULLIVAN, Gary. H.264/MPEG-4 AVC Reference Software Manual. Joint Video Team (JVT) of ISO/IEC MPEG & ITUT VCEG, Julho de 2007. [7] JACK, Keith. Video Demystified: A handbook for the Digital Engineer. 4. ed. Oxford, Inglaterra: Elsevier, 2005. [8] JANUS, Scott. Video in the 21st Century. Oregon: Intel Press, 2002. [9] SUN MICROSYSTEMS, INC. Java Media Framework 2.1.1e API README and BINARY CODE LICENSE. Califórnia, EUA, 2003. [10] YU, Keman; LV, Jiangbo; LI, Jiang e 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. [11] RICHARDSON, Iain E. G. Vcodex White Paper: An overview of H.264. Disponível em: <http://www.vcodex.com>. Acesso em: 19 dezembro 2007. [12] Wikipedia. H.264/MPEG-4 AVC. Disponível em: <http://en.wikipedia.org>. Acesso em: 21 dezembro 2007. [13] MALVAR, H. S.; HALLAPURO, Antti; KARCZEWICZ, Marta e KEROFSKY, Louis. “LowComplexity 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. [14] 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. [15] MARPE, Detlev; SCHWARZ, Heiko e 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. [16] WITTEN, Ian H.; RADFORD, M. N. e CLEARY, John G. “Arithmetic coding for data compression”. Computing Practices, vol. 30, no. 6, p. 520-540, Junho de 1987.