projet logiciel en c - ensimag

68
Ensimag — Juin 2010 Projet Logiciel en C Sujet 2 : Décodeur Vorbis Auteurs : Nicolas Fournel, Damien Hedde et Pierre-Henri Horrein

Upload: others

Post on 23-Oct-2021

13 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Projet Logiciel en C - Ensimag

Ensimag — Juin 2010—

Projet Logiciel en C—

Sujet 2 : Décodeur Vorbis

Auteurs : Nicolas Fournel, Damien Hedde et Pierre-Henri Horrein

Page 2: Projet Logiciel en C - Ensimag

2

Page 3: Projet Logiciel en C - Ensimag

Table des matières

1 Préambule 51.1 À propos des formats audio - Vorbis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.2 Objectif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.3 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2 Le format Vorbis 72.1 Présentation générale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.2 Extensibilité du Vorbis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.3 Représentation du spectre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2.3.1 Floors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3.2 Residues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3.3 Couplage des canaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.4 Changements de domaine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.5 Codebook : codage entropique et quantification . . . . . . . . . . . . . . . . . . . . . . . 13

3 Le conteneur Ogg 153.1 Flux logique et paquets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.2 Entrelacement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.3 Segments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.4 Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

3.4.1 En-tête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.4.2 Calcul du CRC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

4 Lecture d’un flux Vorbis 194.1 Préambule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194.2 Décodage des en-têtes Vorbis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

4.2.1 Header 1 : Identification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204.2.2 Header 2 : Commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204.2.3 Header 3 : Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4.3 Décodage des paquets audio Vorbis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274.3.1 Décodage du flux : Récupération des informations . . . . . . . . . . . . . . . . . 274.3.2 inverse coupling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294.3.3 dot product . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.3.4 iMDCT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.3.5 overlap add . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.3.6 Production des échantillons PCM . . . . . . . . . . . . . . . . . . . . . . . . . . 30

4.4 Floors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.4.1 Décodage de l’en-tête . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.4.2 Décodage du paquet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.4.3 Synthèse de la courbe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324.4.4 Fonctions pratiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4.5 Residues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.5.1 Partie commune . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.5.2 Type 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374.5.3 Type1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

3

Page 4: Projet Logiciel en C - Ensimag

4 TABLE DES MATIÈRES

4.5.4 Type 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

5 l’API fournie 395.1 Informations Globales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395.2 Module Time domain transform (time_domain) . . . . . . . . . . . . . . . . . . . . . . 40

5.2.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405.3 Module Mode (mode) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

5.3.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405.3.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

5.4 Module Mapping (mapping) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415.4.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415.4.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

5.5 Module Floor (floor) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445.5.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445.5.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

5.6 Module Floor type 1 (floor1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455.6.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455.6.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46

5.7 Module Floor type 0 (floor0) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475.7.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475.7.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

5.8 Module : Dot Product (dot_product) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475.8.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

5.9 Module Residues (residue) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485.9.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485.9.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

5.10 Module Vorbis I/O (vorbis_io) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495.10.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495.10.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

5.11 Module Ogg Core (ogg_core) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505.11.1 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505.11.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

5.12 Module Ogg Packet (ogg_packet) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525.12.1 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535.12.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

5.13 Module iMDCT (imdct) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545.13.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545.13.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

5.14 Module : Common Header (common_header) . . . . . . . . . . . . . . . . . . . . . . . . 555.14.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

5.15 Module Header 1 (header_1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555.15.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

5.16 Module Header 2 (header_2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555.16.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

5.17 Module Header 3 (header_3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555.17.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

5.18 Module Vorbis main (vorbis_main) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565.18.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

5.19 Module Helpers (helpers) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565.19.1 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

5.20 Module : Codebook (codebook) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565.20.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 565.20.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

5.21 Module de gestion des échantillons audios (pcm_handler) . . . . . . . . . . . . . . . . . 585.21.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585.21.2 Fonctions du module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

5.22 Module Envelope (envelope) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

Page 5: Projet Logiciel en C - Ensimag

TABLE DES MATIÈRES 5

5.22.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 595.22.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

5.23 Module Vorbis Packet (packet) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605.23.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605.23.2 Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

5.24 Programme principal (main) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

6 Travail attendu 63

A Description du format WAV 65A.1 Structure principale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65A.2 Chunk décrivant les caractéristiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65A.3 Chunk donnant les échantillons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

Page 6: Projet Logiciel en C - Ensimag

6 TABLE DES MATIÈRES

Page 7: Projet Logiciel en C - Ensimag

Chapitre 1

Préambule

1.1 À propos des formats audio - VorbisIl existe une multitude de formats audio, certains étant plus utilisés que d’autres. Certains des formats

ne sont utilisés qu’associés à la vidéo (AC3, DTS, AAC,. . . ), d’autres sont extrêmement répandus pour lestockage de la musique au format numérique. Parmi ces formats, on distingue principalement deux types deformats : les formats sans pertes, comme le WAV, ou le FLAC, et les formats avec pertes, comme le MP3,ou le Vorbis1.

Le Vorbis est un format audio libre, ouvert, et sans brevet. Il fournit de meilleures performances entermes de rapport compression/qualité audio que le mp3. Cependant, il reste moins utilisé que son concur-rent qui lui n’est pas un format libre. Il fait partie de la suite de codec libre développée par Xiph.org.Une dernière caractéristique intéressante est son extensibilité : tout est prévu dans le Vorbis pour permettrel’insertion de nouvelles méthodes sans remaniement profond de la norme, ce qui en fait un format trèsintéressant pour les groupement de recherche. Cette caractéristique évite également un gros effort de re-développement en cas de nouvelle méthode : si l’implémentation du codec est bien pensée, l’ajout d’unenouvelle fonctionnalité n’implique pas une réécriture complet, mais simplement l’implémentation de cettenouvelle fonctionnalité.

Le Vorbis n’est qu’un format d’encodage, qui requiert un format d’encapsulation, de la même manièrequ’en vidéo, l’AVI ou les formats MATROSKA (mkv) encapsulent des vidéos compressées avec certainsformats tels que le DivX ou le H.264 (ou le Theora, codec vidéo libre, ouvert, et sans brevet, appartenantaussi à la suite Xiph.org). Le format d’encapsulation généralement utilisé avec le Vorbis est l’Ogg, d’oùl’extension généralement trouvée pour les fichiers utilisant Vorbis (.ogg). Le Vorbis s’encapsule égalementtrès bien dans le conteneur RTC, prévu pour le streaming audio.

1.2 ObjectifL’objectif du projet est de développer intégralement un décodeur Vorbis conforme à la norme actuelle

du Vorbis. Votre décodeur prendra en entrée un fichier Ogg qui contient un flux Vorbis, et produira en sortieles échantillons représentant le son.

Pour des raisons pratiques, l’encodeur traduira le son décompressé en fichiers WAV (qui représentent leson brut), la cacophonie résultant de l’écoute des sons dans les salles TX se révélerait trop éprouvante pourvous comme pour nous.

Cependant, nous vous fournissons également de quoi écouter directement le son produit sur vos ma-chines persos, et les fichiers WAV sont lisibles par n’importe quel lecteur audio. Nous vous fournissonségalement diverses méthodes pour admirer le résultat de vos décodeurs.

1.3 PrincipeLe principe du projet est simple. Nous avons réalisé pour vous l’architecture du décodeur, et donc

découpé le projet en modules de taille raisonnable. Ces modules vous sont fournis sous la forme d’objets,

1on ne parle évidemment ici que des formats numériques

7

Page 8: Projet Logiciel en C - Ensimag

et de fichiers d’en-tête C. Vous pouvez donc les utilisez, mais ne disposez pas du code source. Le but du jeuest de reprogrammer tous les modules, et de remplacer ainsi nos modules par vos modules, jusqu’à obtenirun décodeur que vous aurez intégralement développé.

Ce document est normalement suffisant pour réaliser le projet entièrement. Nous vous avons retranscriset traduit la norme Vorbis, et détaillé les différents modules. Les personnes intéressées par un complémentd’informations peuvent consulter la bibliographie.

Bon courage à vous et bienvenue dans le monde du Vorbis !

Bibliographie

[1] Jack E. Bresenham, "Algorithm for computer control of a digital plotter", IBM Systems Journal, Vol.4, No.1, January 1965, pp. 25 30

[2] http://www.xiph.org/vorbis/

[3] Xiph.org Foundation, "Vorbis I specification", February 2010

[4] Ross N. Williams, "A painless guide to CRC error detection algorithms", http://www.ross.net/crc/crcpaper.html

8

Page 9: Projet Logiciel en C - Ensimag

Chapitre 2

Le format Vorbis

Ce chapitre a pour objectif de présenter les grands principes du format Vorbis, ainsi que l’aspect hautniveau de la chaîne de codage et décodage. L’aspect pratique du décodage, la séquence d’opérations, etle format final du fichier sont présentés dans la partie suivante. On appelle abusivement fichier Vorbis leflux Vorbis dans ce chapitre. En effet, le Vorbis est encapsulé dans un conteneur (le Ogg, généralement), lefichier n’est donc pas un fichier Vorbis mais un fichier Ogg.

2.1 Présentation généraleLe Vorbis est un format d’encodage audio libre. Il définit un ensemble d’opérations servant à représenter

un signal audio de manière compressée. C’est un format "avec pertes", ce qui signifie qu’une perte parrapport au signal original est acceptable (mais la perte doit être contrôlée).

D’un point de vue général, la compression audio prend en entrée des échantillons, aussi appelés PCM(Pulse Coded Modulation), qui sont le résultat de la discrétisation du signal sonore (qui est lui continu).Pour faire simple, les échantillons représentent l’amplitude du signal sonore à intervalle de temps régulier.Cet intervalle permet de définir la fréquence d’échantillonage, qui est classiquement 44, 1 kHz, soit 44100échantillons par seconde. L’opération de décompression permet de restituer ces échantillons.

Comme la plupart des applications de traitement du signal, la compression audio fait appel à la repré-sentation fréquentielle du signal. Cette représentation permet de considérer le signal comme une sommede fonctions sinusoïdales. Le signal peut donc être représenté sous forme fréquentielle (on parlera alors dereprésentation fréquentielle, ou de spectre), ou sous forme temporelle (on parle alors de signal, même sicette notation est abusive, puisqu’il ne s’agit en fait que d’une représentation).

Le spectre et la représentation temporelle sont deux représentations d’un même signal, on passe del’une à l’autre en utilisant une fonction de transformée (la plus classique étant la transformée de Fourier,dans notre cas, c’est une transformée en cosinus discrète, qui est une application de la transformée deFourier dans le cas réel discret).

La décompression du Vorbis se découpe en deux étapes principales :– une partie d’initialisation (ou configuration), qui récupère dans le fichier l’ensemble des données

nécessaires à la décompression, et qui se base sur les en-têtes du flux Vorbis– une partie de décodage, qui applique les opérations de décompression aux données pour produire les

PCMs, et qui se base sur le corps du flux Vorbis.La partie d’initialisation est détaillée dans le chapitre 4.

Le Vorbis est un format hautement configurable. La séquence d’opérations générique à appliquer pourle décodage est fixée, mais chaque opération peut utiliser différents algorithmes. Les opérations génériquessont présentées dans la figure 2.1. Les flèches en trait plein représentent les données, alors que les flèchesen pointillés représentent le paramètrage des blocs. La suite de ce chapitre s’attache à vous présenter lesdifférentes étapes de décompression. Le Vorbis est découpé en paquets, le décodage se fait donc paquet parpaquet.

Sur la figure, on peut observer l’ordonnancement des opérations à effectuer. Elles se classifient en deuxtypes, certaines permettant de récupérer des données dans le paquet (mode, codebook, floor), alors qued’autres ne font que réaliser des calculs, sans lire le flux. Avant la transformée, les données sont une repré-sentation fréquentielle du signal, après la transformée, le signal est sous forme temporelle. Les opérations

9

Page 10: Projet Logiciel en C - Ensimag

10 CHAPITRE 2. LE FORMAT VORBIS

F. 2.1 – Opérations de décompression d’un paquet Vorbis

sont présentées plus en détail dans les parties suivantes.

2.2 Extensibilité du Vorbis

Comme précisé précédemment, le Vorbis est un format hautement configurable, ce qui permet d’étendretrès facilement le format normalisé. En effet, même si la séquence d’opérations à appliquer est définie, plu-sieurs algorithmes peuvent être définis pour une même opération. Ces différents algorithmes peuvent êtreutilisés à l’intérieur d’un même flux sur des paquets différents. Certains paramètres sont aussi fixés par pa-quet, telle que la taille du paquet courant par exemple. Ceci implique donc un mécanisme de configuration,fourni dans le Vorbis par le mécanisme de mode et de mapping.

Lors de l’initialisation du décodage (lecture des en-têtes), on définit plusieurs configurations possibles.On appelle configuration ici un jeu de valeur pour l’ensemble des paramètres. Ainsi, si nos paramètres sontla taille du paquet et la représentation des floors, on peut avoir une configuration où la taille vaut 1024 et lefloor est de type 0, et une autre configuration où la taille vaut 512 et le floor est de type 1.

Dans le cadre du Vorbis, le mécanisme de configuration est fourni par les modes. Une configurationde mode contient la taille et le type de la fenêtre, la transformée utilisée, et le mapping. Ce dernier donneles informations sur la représentation des données, à savoir les floors, les résidus et les canaux. Ceci estreprésenté sur le schéma 2.1.

Notez que le Vorbis offre la possibilité de spécifier le type de transformée utilisé, mais cette possibilitén’est jamais exploitée, puisque la norme ne spécifie qu’un type de transformée. Il en est de même pour letype de la fenêtre.

Durant le décodage, la première information récupérée dans un paquet Vorbis est donc le mode utilisépour ce paquet, ce qui permet de déduire les paramètres et algorithmes utilisés.

2.3 Représentation du spectre

Le spectre d’un signal est sa représentation fréquentielle.La représentation de l’onde sonore dans le format Vorbis se base sur une double représentation : une

représentation à gros grain, appelée floor, qui définit l’aspect général de la courbe, et une représentationprécise à partir de la représentation gros grain, appelée residue, qui définit les détails de la courbe. Pourobtenir une valeur du spectre, il faut multiplier la valeur correspondante du floor par celle du residue.L’oreille humaine est moins sensible aux détails, l’utilisation d’une telle séparation autorise plus de pertessur les détails, et donc une meilleure compression.

Page 11: Projet Logiciel en C - Ensimag

2.3. REPRÉSENTATION DU SPECTRE 11

2.3.1 FloorsConcernant les floors, deux types de représentation sont proposées. Les deux types sont interchan-

geables, c’est-à-dire qu’on peut passer de la représentation de type 0 à la représentation de type 1 avec unefonction mathématique connue. Dans les deux cas, la récupération de l’aspect général du spectre se fait àpartir d’une méthode dite Linear Predictive Coding (LPC). On ne donne ici qu’un aperçu de cette méthode.Le LPC s’appuie sur un modèle de génération du son à base d’une source et d’un filtre. Les effets du filtresont appelés les résidus. Lorsqu’on supprime les effets du filtre sur la source, on obtient la forme généramedu spectre, qui correspond aux floors du Vorbis. Les floors ne peuvent pas être compressés avec pertes,puisqu’ils représentent l’aspect global du signal.

Les représentations type 0 et type 1 sont en fait une représentation différente des coefficients issus duLPC. Le type 0 utilise une représentation LSP (Line Spectral Pair). Cette représentation n’est plus utilisée(mais doit tout de même être supportée dans un décodeur de référence). Elle se base sur des transforméesen Z, et est particulièrement complexe à décoder. Nous ne vous demandons pas d’implémenter ce type, saufsi cela vous intéresse, ou si vous avancez particulièrement vite dans le développement du décodeur. Ce typede floor n’est pas décrit dans ce document.

Le type 1 utilise une interpolation linéaire par segment pour représenter la courbe. Cette représentationest beaucoup moins lourde pour le décodeur, et est actuellement la plus utilisée. Concrètement, la représen-tation se base sur une reconstruction étape par étape de la courbe. On commence par générer une courbed’aspect grossier (en fait, une ligne) entre les bornes de la courbe, puis on précise certains points de lacourbe un par un. Plus précisément, voici les étapes de l’induction qui permet de regénérer la courbe :

1. Initialiser l’itération en générant une ligne droite entre le premier et le dernier point

2. Sélectionner une abscisse (la sélection est faite à partir du flux, et non pas arbitrairement), calculer lanouvelle ordonnée à partir de l’ordonnée actuelle et d’une différence (récupérée dans le flux)

3. à partir de l’abscisse et de la nouvelle ordonnée, on peut reconstruire deux nouveaux segments dedroite

4. Recommencer à l’étape 2 tant qu’il reste des points d’interpolation

L’exemple suivant est extrait de la norme Vorbis. La courbe des floors est représentée parune liste d’abscisses : 0, 128, 64, 32, 96, 16, 48, 80, 112, et la liste d’ordonnées correspondantes :110, 20,−5,−45, 0,−25,−10, 30,−10. On applique l’algorithme en initiant l’induction, on trace donc lapremière ligne à partir des points (0, 110) et (128, 20). On prend ensuite le point suivant, 64. L’ordonnée estcodée comme une différence par rapport à la courbe résultant de l’itération précédente. L’ordonnée corres-pondant à x = 64 était 65, elle vaut maintenant 60, et on a donc deux lignes. On continue pour l’itérationsuivante avec le point (32, 85) qui devient (32, 40), et ainsi de suite jusqu’à terminer la liste d’abscisses. Onobtient alors la courbe finale. Les étapes sont présentées dans la figure 2.2. L’algorithme correspondant estprésenté dans le chapitre 4.

2.3.2 ResiduesLes residues sont, dans le codage LPC, les résultats du filtre appliqué à la source. Ils représentent en

fait les variations minimes du signal. Le Vorbis utilise également une opération de couplage des canaux (cf.2.3.3) qui peut modifier les residues. Dans le décodeur, le décodage des residues a lieu avant l’opération dedécouplage.

L’encodage des residues est fait à l’aide des codebooks (dictionnaires) (cf. 2.5). Plus précisément,chaque vecteur de residues est divisé en partitions, qui sont ensuite classifiées. A chaque classificationest associée une quantification et un arbre de codage entropique. La partition est donc encodée à partir deces paramètres. On notera que la classification elle-même est encodée à l’aide d’un codage entropique. Pourêtre plus précis, ce procédé est répété 8 fois avec accumulation (le décodage se fait en 8 passes). Ceci estmieux précisé dans le chaptire 4.

Il y a 3 possibilités pour représenter les residues, qui s’appellent types 0, 1 et 2 (le concepteur duVorbis n’est pas franchement original). Les types 0 et 1 fonctionnent en fait de la même façon, à part pourl’entrelacement des valeurs dans une partition. Le type 2 fonctionne comme le type 1, mais travaille sur lescanaux entrelacés. En clair, les types 0 et 1 travaillent canal par canal, alors que le type 2 fonctionne surun canal virtuel qui contient l’ensemble des canaux entrelacés. Le fonctionnement exact est décrit dans lechapitre 4, lors des informations spécifiques à la lecture du flux Vorbis.

Page 12: Projet Logiciel en C - Ensimag

12 CHAPITRE 2. LE FORMAT VORBIS

(a) Étape 1 : première ligne et positionnement du premierpoint

(b) Étape 2 : Positionnement des deux points suivants

(c) Étape 3 : étape intermédiaire (d) Étape 4 : courbe finale

F. 2.2 – Étapes de la création de la courbe des floors

2.3.3 Couplage des canaux

Dans un environnement multicanal, chaque canal produit un son différent, qui dépend de l’emplacement.Cependant, même si le son est différent, le son d’origine est le même, et il existe donc une corrélation entreles différents canaux. Encoder tous les canaux indépendemment génère donc de la redondance.

Le couplage (coupling en anglais) est une opération qui permet de ne pas inclure cette redondance dansle flux encodé. Le format Vorbis permet de fonctionner avec ou sans couplage canal.

Dans le cas où on utilise du couplage, deux mécanismes sont mis à disposition par le format Vorbis : lechannel interleaving (entrelacement de canal) et le square polar mapping (qui se traduirait pas "associationcartésien/polaire). On peut utiliser l’une ou l’autre des deux méthodes, ou les deux de manières conjointes.Ces deux mécanismes permettent d’obtenir divers modèles de couplage, du modèle sans pertes avec uneéquivalence parfaite entre la version couplée et la version non couplée, au modèle avec pertes, qui vise àréduire encore le débit binaire, en supprimant des éléments hors du domaine de l’audible, ou qui n’influeque légèrement sur le rendu du son.

On ne présente que sommairement la théorie associée aux deux méthodes. Elle est basée sur une re-présentation de l’espace audio à n dimensions, avec n le nombre de canaux. Le signal sonore à l’instantt est le point de coordonnées le vecteur d’échantillons fréquentiels : s( f ) = (sc1( f ), sc2( f ), . . . , scn( f )).Le square polar mapping permet de passer de la représentation cartésienne à une représentation polaire,dans laquelle l’information est représentée de manière asymètrique (le module contient beaucoup plusd’informations que l’argument). L’entrelacement de canal permet de jouer sur les méthodes de quantifi-cation à base de codebooks, présentées en 2.5, en entrelaçant les canaux dans le flux final. L’utilisationconjointe de ces différents mécanismes permet de dégager différents modèles. Pour les personnes intéres-sées, toutes les informations utiles sont sur le site de référence du Vorbis, plus précisément à l’adressehttp://www.xiph.org/vorbis/doc/stereo.html .

L’opération de découplage est donc un mélange entre du désentralacement (remise en forme des canauxà partir du flux entrelacé), et des rotations pour repasser le signal sonore en coordonnées cartésiennes. Elles’applique sur les residues uniquement.

Page 13: Projet Logiciel en C - Ensimag

2.4. CHANGEMENTS DE DOMAINE 13

2.4 Changements de domaineLa méthode de changement de domaine utilisée dans le Vorbis est une transformée en cosinus discrète

(puisqu’on travaille dans le cas discret), qui est en fait une version discrète et réelle de la transformée deFourier (qui est continue et complexe). La variante de la DCT (Discrete Cosine Transform) utilisée est laMDCT (Modified DCT). La MDCT a été introduite en 19871, et est définie par la fonction suivante :

Xk =4N

N−1∑n=0

xn cos[π

2N

(2n + 1 +

N2

)(2k + 1)

]avec :• x le signal sous forme temporelle de taille N, après fenêtrage• X le signal sous forme fréquentielle, de taille N

2Elle convertit donc N échantillons temporels (dans le cas du Vorbis, donc, des PCMs) en N

2 échantillonsreprésentant le signal dans le domaine fréquentiel.

L’inverse de la MDCT est définie par la fonction suivante (pour le Vorbis) :

xn =

N2 −1∑k=0

Xk cos[π

2N

(2n + 1 +

N2

)(2k + 1)

]L’iMDCT permet donc de repasser des échantillons fréquentiels aux PCMs. Cependant, le signal récu-

péré après l’inversion de la MDCT n’est pas le même que le signal d’origine. En effet, les plus perspicacesauront noté que le passage dans le domaine fréquentiel avec la MDCT génère une perte d’information (onobtient N

2 échantillons à partir de N échantillons). Pour pallier ce problème, on envoie les informations plu-sieurs fois, en créant des fenêtres qui se recouvrent. Une fenêtre est une portion du signal temporel. Vousremarquerez une similitude entre la notion de fenêtres et la notion de paquets Vorbis. La fenêtre est la por-tion de signal sous forme temporelle, alors que le paquet est la portion de flux Vorbis servant à représenterla fenêtre.

Le recouvrement permet d’envoyer l’information temporelle deux fois. Cependant, si on envoie le signaltel quel deux fois, l’information en sortie du décodeur ne sera plus exacte. On définit donc une fonction defenêtrage pour pallier ce problème.

Le fenêtrage du signal est présenté dans la figure 2.3. Dans le premier graphique, on peut voir le fenê-trage avec N = 1024. On applique une fenêtre (la fonction est donnée plus loin), c’est à dire qu’on multipliecoefficient à coefficient la fenêtre et la représentation temporelle du signal, et c’est le résultat de ce fenê-trage qui est transformée par la DCT. On remarque le recouvrement introduit par le fenêtrage (la fenêtre enpointillés et la fenêtre en trait plein se recoupent). Le deuxième graphique présente le cas où les fenêtres nesont pas de même taille (N1 = 1024,N2 = 128). La figure montre bien le recouvrement entre les fenêtres.

La fonction de fenêtrage du Vorbis est définie mathématiquement comme suit, avec N la taille de lafenêtre dans le domaine temporel :

∀n ∈ [0; N − 1],wn = sinπ2 × sin2

n + 12

N× π

La fonction de fenêtrage doit vérifier la relation : w2

n + w2n+ N

2= 1. En effet, la fenêtre est appliquée

deux fois aux échantillons, une fois lors de l’encodage, et une fois lors du décodage, et les échantillonssont additionnés pour le recouvrement. On va donc, pour encoder puis décoder un échantillon, appliquer lafenêtre 4 fois à un seul échantillon :

– une fois à l’encodage du premier bloc : sn × wn+ N2

– une fois à l’encodage du second bloc : sn × wn

– une fois au décodage du premier bloc : sn × w2n+ N

2

– une fois au décodage du second bloc : sn × w2n

Lors de la correction du recouvrement, on obtient alors le signal reçu

rn = sn × w2n+ N

2+ sn × w2

n = sn × (w2n + w2

n+ N2) = sn

1le but recherché de la MDCT est de s’affranchir de problèmes fréquents dans le traitement audio tels que le crênelage (aliasing),que l’on ne traitera pas ici

Page 14: Projet Logiciel en C - Ensimag

14 CHAPITRE 2. LE FORMAT VORBIS

0 512 1024/00 512 1024

512

00 12864

768736 800

F. 2.3 – Exemple de fenêtres

En pratique, le Vorbis autorise deux tailles de fenêtre différentes par flux (la grande et la petite fenêtre).Comme l’intersection de ces deux fenêtres doit être adaptée, on calcule la fenêtre avec l’algorithme suivant.

On note Ni la taille de la fenêtre i. Le mécanisme d’adaptation fait correspondre le point d’abscisse3Ni−1

4 avec le point d’abscisse Ni4 , et le point d’abscisse 3Ni

4 avec le point d’abscisse Ni+14 . Plus concrètement,

l’algorithme calcule les indices de début et de fin de pente autour de ces points de correspondance, génèrant

Page 15: Projet Logiciel en C - Ensimag

2.5. CODEBOOK : CODAGE ENTROPIQUE ET QUANTIFICATION 15

ainsi 5 parties disctinctes. On génère ensuite l’enveloppe w comme suit :

wi =

0 si 0 ≤ i < i0sin

(π2 × sin2

(i−i0+ 1

2N × π

2

))avec N = min(Ni−1,Ni) si i0 ≤ i < i1

1 si i1 ≤ i < i2sin

(π2 × sin2

(i−i2+ 1

2N × π

2 + π2

))avec N = min(Ni+1,Ni) si i2 ≤ i < i3

0 si i3 ≤ i < Ni

– i0 et i1 délimitent la pente de gauche– i2 et i3 délimitent la pente de droitei0, i1, i2, et i3 sont récupérées à partir du flux lors du décodage, l’algorithme correspondant à ce calcul

est présenté dans le chapitre 4. Cependant, la méthode de calcul est triviale (cf. figure).Finalement, l’opération inverse de la MDCT se décompose comme suit :– application de l’i-MDCT pour récupérer les signaux temporels– application de la fonction de fenêtrage– addition des signaux se recouvrant, qui forment alors le signal sous forme temporelle finalOn notera bien que pour une taille de bloc N, les données fréquentielles contiendront N

2 coefficients,alors que les données temporelles en contiendront N.

2.5 Codebook : codage entropique et quantificationLes codebooks du format Vorbis servent à deux opérations qu’on retrouve dans tous les formats de

compression : le codage entropique et la quantification.Le codage entropique est une opération qui consiste à coder un dictionnaire de mots de taille fixe à

l’aide de codes de taille variable, en privilégiant les codes de petite taille pour les mots fréquents. Le codageutilisé dans le Vorbis est le codage de Huffman, qui est un codage préfixe.

Le principe du codage entropique est le suivant : quand on travaille avec un alphabet de taille finie, ilest possible d’étudier la fréquence d’apparition de certains symboles. Par exemple, avec l’alphabet latin, le’e’ est la lettre la plus utilsée dans la langue française, alors que le ’w’ est la lettre la moins utilisée (si on neconsidère pas les accents). On associe alors aux symboles les plus fréquents un code de taille courte, et auxmots les moins fréquents les codes de grande taille. Le code de Huffman est préfixe car par construction,aucun code n’est le préfixe d’un autre code, ce qui évite d’avoir besoin de séparateurs. On travaille ici surdes codes binaires.

Les codes de Huffman sont généralement représentés comme des arbres binaires, avec chaque feuille del’arbre qui représente un symbole, et chaque noeud un bit du mot de code (noeud gauche : ’0’, noeud droit :’1’). Prenons l’exemple suivant :

Symbole Codea 110b 11110c 1110d 0e 10

Le décodage du bitstream 0101110110010 produit la suite desymboles decade. Il aurait fallut 3 bits par symbole pour distin-guer 5 symboles pour un code de taille fixe (tous les codes sontalors équivalents), et donc la suite de 6 symboles aurait requis18 bits, alors que 13 seulement sont nécessaires ici.

1

0

c

01

10

d

a

0

e

root

1

0

b

La quantification est une autre technique de compression, qui consiste à réduire la précision sur lescoefficients pour réduire leur taille. Par exemple, des coefficients codés sur 16 bits peuvent être quantifiésen les codant sur 15 bits. Dans le cas du Vorbis, on utilise pour quantifier des vecteurs de quantifications.Ils sont définis dans l’en-tête du fichier et sont utilisés pour coder les residues. Ils sont associés aux arbresde Huffman : à chaque feuille de l’arbre est associée un vecteur de quantification. Les codebooks pourrontdonc être utilisés, à chaque fois, de deux manières : soit en scalaire, auquel cas on retourne l’entier associé,soit en "VQ", auquel cas on s’intéressera au vecteur de quantification.

Page 16: Projet Logiciel en C - Ensimag

16 CHAPITRE 2. LE FORMAT VORBIS

La représentation des dictionnaires dans le Vorbis implique donc une représentation de l’arbre de Huff-man et une représentation des vecteurs de quantification (VQ). L’algorithme représentant les VQ est fournidans le chapitre 4.

Les codes de Huffman sont des codes préfixés. Une fois qu’un mot de code a été utilisé, il ne peut plusservir comme début à un autre mot. Si on fixe comme règle que les codes sont assignés avec d’abord le 0(arbre gauche) puis le 1 (arbre droit), et que l’on donne les symboles et leur longueur dans l’ordre où ilssont rangés dans l’arbre on peut reconstruire l’arbre assez facilement. Prenons l’exemple suivant, tiré de lanorme Vorbis.

Symbole longueur0 21 42 43 44 45 26 37 3

Pour construire les mots de code associés, on procède symbole par symbole. On commence donc parle symbole 0, qui a un code de longueur 2. Comme on assigne toujours le bit 0 d’abord, le mot de codeassocié à 0 est 00. L’entrée 1 a un mot de code de longueur 4. On ne peut plus commencer par 00, le motcommence donc par 01, puis on assigne les 0 d’abord. On obtient donc 0100. En continuant on obtient :

Symbole longueur Code0 2 001 4 01002 4 01013 4 01104 4 01115 2 106 3 1107 3 111

0

1 2 3 4

5

6 7

0 1

0 1

0 1

0 1 0 1

0 1

0 1

Cela revient donc à créer les feuilles d’un arbre binaire. On cherche toujours à créer la feuille leplus à gauche possible, à une profondeur égale à la longueur du mot de code. Les symboles Vorbis sonttoujours dans l’ordre, la seule information à récupérer dans le fichier est donc les longueurs associées auxsymboles. Les algorithmes pour les récupérer sont donnés dans le chapitre 4.

Page 17: Projet Logiciel en C - Ensimag

Chapitre 3

Le conteneur Ogg

Le format Ogg est un conteneur multimédia (de même que les formats Avi ou Mkv). Il sert à stocker ouà transmettre ensemble différents médias (par exemple de la vidéo, de l’audio, des images ou du texte). Oggest un format qui permet d’encapsuler et d’entrelacer différents flux/fichiers médias appelés flux logiques.

Un flux/fichier Ogg est constitué de un ou plusieurs flux physiques qui sont mis bout à bout. Ces fluxphysiques sont complètement indépendants. Dans la suite nous ne traiterons donc que d’un seul flux phy-sique.

3.1 Flux logique et paquetsPour qu’un flux logique puisse être encapsulé dans un flux Ogg il faut qu’il soit organisé en paquets.

Ce découpage n’est pas fait par Ogg, il est dépendant du format du flux logique. Lors de la lecture d’unflux logique, les paquets sont restitués l’un après l’autre au décodeur approrié. Le format vidéo Theora, leformat audio Vorbis, le format d’image Png sont des exemples de flux logiques supportés par le Ogg.

Un flux logique doit commencer par un ou plusieurs paquets d’en-tête qui permettent d’initialiser ledécodeur approprié. Ceux-ci ne doivent pas contenir de données. Le premier paquet doit de plus permettrede trouver facilement de quel format est le flux logique. La manière la plus classique pour cela est que lepremier paquet commence par une séquence particulière spécifique au format.

3.2 EntrelacementUn flux physique est lui même constitué de plusieurs flux logiques entrelacés. Au sein d’un flux phy-

sique, chaque flux logique est identifié par un numéro différent. Dans Ogg, l’unité d’entrelacement est lapage. Une page appartient à un unique flux logique et en contient une portion. Au sein d’un flux logique,les pages sont ordonnées et numérotées par ordre croissant.

La première page d’un flux logique est appelée page BOS (Begin Of Stream) et ne doit contenir que lepremier paquet du flux. La dernière est appelée page EOS (End Of Stream). Les pages dans un flux physiquedoivent respecter l’ordre suivant :

– ensemble des pages BOS de tous les flux logiques,– pages éventuelles contenant les paquets d’en-tête supplémentaires1,– les pages contenant les paquets de données2.

F. 3.1 – Entrelacement des pages dans un flux physique

Il n’y a pas de contraintes sur les pages EOS. Elles n’ont pas à être forcément à la fin du flux physique.Une page BOS peut même être aussi une page EOS si le flux logique ne contient qu’une page.

1Bien que Ogg impose que les paquets d’en-tête soient mis avant tout paquet de données, il n’y a aucun mécanisme pour différencierces deux catégories de paquets dans Ogg.

2Aucun paquet de données ne doit apparaître dans les pages contenant des paquets d’en-tête

17

Page 18: Projet Logiciel en C - Ensimag

18 CHAPITRE 3. LE CONTENEUR OGG

3.3 SegmentsLes paquets ne sont pas directement encapsulés dans les pages d’un flux logique. Chaque paquet est

d’abord divisé en un ou plusieurs morceaux appelés segments comme illustré sur la F. 3.2.

F. 3.2 – Subdivision des paquets d’un flux logique en segments

Un segment fait au maximum 255 octets, ainsi sa taille peut se coder sur un octet. Le premier segmentd’un paquet contient ainsi les 255 premiers octets, le deuxième les 255 suivants, etc. Cela jusqu’à atteindrela fin du paquet. Le dernier segment d’un paquet contient obligatoirement moins de 255 octets : si un paquetcontient un multiple de 255 octets, alors son dernier segment contiendra 0 octets. Cela permet de repérer lesfin de paquets : dès qu’un segment a une taille différente de 255, cela signifie que c’est le dernier du paqueten cours. Les paquets de taille nulle ne sont pas interdits, ils ne seront formés que d’un segment de taillenulle.

Ces segments sont ensuite encapsulés dans les pages du flux logique correspondant dans l’ordre du flux :le premier segment du premier paquet, suivi du deuxième segment du premier paquet, etc.

3.4 PagesDans un flux physique, les pages se succèdent les unes aux autres. Chacune contient jusqu’à 255 seg-

ments3. Une page est formée d’un en-tête, qui indique en particulier le nombre et la taille des segmentsqu’elle contient, suivie des segments mis bout à bout (voir F. 3.3. Il n’y a aucune séparation entre lesdifférents segments puisque la taille de chacun d’entre eux est indiquée dans l’en-tête de la page.

F. 3.3 – Exemple de page

3.4.1 En-têteL’en-tête de la page contient 8 champs de taille fixe et un champ de taille variable. Les champs utilisent

la convention petit-boutiste (little-endian) (octet de poids faible d’abord) pour coder les champs de plusieursoctets. Le tableau 3.1 récapitule les différents champs et leur taille.

– capture pattern (4 octets)Indique le début d’une page. Ce champ doit contenir les 4 caractères ASCII "OggS".

– stream structure version (1 octet)Indique la version du format du fichier Ogg, la version décrite ici est la 0.

– header type flag (1 octet)Renseigne les attributs de la page. Seuls les 3 premiers bits sont utilisés, les 5 autres doivent être misà 0.– bit 0 : Quand il est à 1, indique que la page continue le paquet non terminé dans la page précédente4.– bit 1 : Indique que la page est la première du flux logique (page BOS)– bit 2 : Indique que la page est la dernière du flux logique (page EOS)

3Il n’est pas interdit qu’une page ne contienne aucun segment4Ce bit est obligatoire, mais redondant puisque la taille du dernier segment de la page précédente nous indique si le paquet est

terminé

Page 19: Projet Logiciel en C - Ensimag

3.4. PAGES 19

Nom Taille (en octets)Capture pattern 4Stream structure version 1Header type flag 1Granule position 8Bitstream serial number 4Page sequence number 4CRC checksum 4Page segments 1Segment table variable

T. 3.1 – En-tête d’une page

– granule position (8 octets)Informe sur la position absolue dans le flux logique une fois que le dernier paquet complet de la pageest décodé (le dernier paquet de la page, s’il n’est pas terminé, n’est pas comptabilisé). Sa valeurdépend du format du flux logique. Néanmoins la valeur −1 (en complément à 2)5 indique qu’aucunpaquet ne se termine sur la page (elle ne contient qu’une portion d’un paquet). Pour le format Vorbis,il indique le nombre d’échantillons décodés.

– bitstream serial number (4 octets)Numéro identifiant le flux logique. Il doit être unique dans chaque flux physique, mais il est complè-tement aléatoire.

– page sequence number (4 octets)Numéro identifiant la page dans le flux logique. Les pages doivent être numérotées en ordre croissant.Ainsi si un numéro est sauté, cela indique que le flux est corrompu.

– CRC checksum (4 octets)Code de contrôle permettant de vérifier que la page n’est pas corrompue. La méthode pour le calculerest exposée dans la partie 3.4.2.

– page segments (1 octet)Indique le nombre de segments que contient la page (de 0 à 255).

– segment tableListe d’octets indiquant la taille de chacun des segments (de 0 à 255). La taille de ce champ est doncvariable et est égale à la valeur du champ précédent.

3.4.2 Calcul du CRCLe Contrôle de Redondance Cyclique (Cyclic Redundancy Check, CRC) est une méthode utilisée pour

détecter les erreurs dans un message. Ils permettent de calculer un résumé du message (qu’on appellehash, ou abusivement et plus couramment checksum ou tout simplement CRC). Concrètement, il sert àvérifier l’intégrité des données. Des explications sur la théorie peuvent être trouvés dans l’article de RossWilliams [4], ou dans les livres sur la théorie de l’information pour les plus perfectionnistes.

Le calcul des CRC est basé sur la division polynomiale. Dans le cas du Ogg, on utilise un résumé sur 32bits, avec comme polynôme générateur6 x32 +x26 +x23 +x22 +x16 +x12 +x11 +x10 +x8 +x7 +x5 +x4 +x2 +x+0.On note ce polynôme 0x04c11db7 : les bits à 1 correspondent aux coefficients présents, le coefficient le plushaut n’est pas représenté.

Nous vous proposons l’algorithme suivant pour le calcul du CRC de l’Ogg, qui est une méthode simplemais peu performante.

polynome := 0x04c11db7 (32 bits)crc := 0x00000000 (32 bits)boucle pour tous les bits ’bit’ du messagesi (bit xor bit31(crc) = 1)crc := (crc << 1) xor polynome

5Toutes les machines sauf les plus exotiques travaillent en complément à 2 pour les nombres signés, vous n’avez donc pas à vousen préoccuper dans le cadre du projet

6le polynôme est en fait celui de la norme CRC-32-IEEE-802.3

Page 20: Projet Logiciel en C - Ensimag

20 CHAPITRE 3. LE CONTENEUR OGG

sinoncrc := crc << 1

fin sifin bouclereturn crc

Dans notre cas le message est une page complète dont le champ CRC de l’en-tête aura été mis à 0 (onne l’a pas encore calculé). Les octets sont traités dans l’ordre (en-tête, puis les segments) ; chaque octet esttraité du bit de poids fort au bit de poids faible.

Page 21: Projet Logiciel en C - Ensimag

Chapitre 4

Lecture d’un flux Vorbis

4.1 Préambule

L’objectif de cette section est de vous présenter l’emplacement des différentes informations requisespour décoder un fichier Vorbis. Le Vorbis est un fichier binaire, sans alignement. Ceci pose certains pro-blèmes lorsqu’on programme. En effet, les processeurs classiques sont prévus pour travailler sur des octets,ou plus généralement sur des ensembles d’octets (des mots de 32 bits ou 64 bits). Les langages de program-mation ne permettent donc pas, ou peu, de travailler sur des éléments binaires qui ne rentrent pas dans lestailles habituelles. De même, pour la lecture du fichier, on ne peut pas utiliser les librairies habituelles, quilisent les fichiers octets par octets (comme les fichiers de texte). Toute lecture du fichier se fera donc à traversle module d’entrée-sortie spécifiquement développé pour le projet. Le Vorbis est un format little-endian.

Pensez aussi à utiliser les opérateurs binaires du C et les masques :– & pour le AND– | pour le OR– ~ pour le NOT– ^ pour le XOR

De même, pensez à annuler les bits non utilisés, lorsque vous travaillez en binaire. Par exemple, si vousreprésentez le bit ’1’ sur un octet, les 7 bits de poids forts ne sont pas utilisés. Si vous effectuez des opéra-tions, il peut arriver qu’ils soient quand même modifiés. Si vous testez ensuite la valeur, pour savoir si ellevaut 1 ou 0, elle ne vaudra ni l’un, ni l’autre ! Pour rappel, un AND avec le bit ’0’ fait toujours ’0’, un ORavec le bit ’1’ fait toujours ’1’.

Les machines sur lesquelles vous développez étant pour la plupart également en little-endian (sauf sicertains développent sur des machines Sun c©, par exemple, ou PowerPC c© pour certains Apple c©), il n’yaura pas de problème d’endianness à gérer. Par contre, les outils utilisés pour afficher les fichiers binairesfont automatiquement la conversion en big endian, pour plus d’aisance de lecture. En effet, tout le mondeapprend à compter en big endian, et par habitude, nous sommes nous-même big-endian (on écrit 10 pour dix,et non pas 01). Pensez donc bien, lorsque vous lisez deux bits successivement dans un octet, que les valeursque vous voyez à l’écran sont à inverser. Par exemple, si vous voyez l’octet 0x80, et que vous voulez lire 1bit, le bit en question sera un ’0’ (le plus à droite sur le visualiseur, mais le plus à gauche dans la machine),et non pas un ’1’. Enfin, une dernière recommendation à propos des manipulations de types en C : d’unemanière générale, les cast vers les types signés étendent le signe : si vous passez un unsigned char ensigned short, et que ce caractère était plus grand que 127, le short résultant sera négatif. Ceci s’appliqueaux shifts binaires aussi. D’une manière générale, n’utilisez les valeurs signées qu’avec précaution, et quandc’est nécessaire.

Dans la suite du document, les différentes sections du flux Vorbis vous sont présentées sous la formede pseudo-code. Pensez bien à lire tous les bits précisés, même si l’information n’est pas utilisée. Si dansl’algorithme, il est précisé que le flux est non décodable, vous devez arrêter le décodage. Pour cela, unesimple commande exit sera suffisante, nous ne vous demandons pas de contrôle d’erreurs avancé.

Le flux Vorbis est divisé en deux parties :– une en-tête, ou header, qui contient toutes les informations utiles au décodage, comme les codebooks,

les informations sur les méthodes de représentation, ou autres. Toutes les informations nécessaires audécodage sont contenues dans le fichier. Il y a en fait 3 en-têtes dans le Vorbis.

21

Page 22: Projet Logiciel en C - Ensimag

22 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

– le corps du fichier, qui contient les ondes sonores compressées.

4.2 Décodage des en-têtes VorbisLes 3 headers du Vorbis commencent tous par 7 octets ayant la même signification :

[packet_type] = lire 1 bit, doit valoir 1 pour les headers[header_type] = lire 7 bits[identification] = lire 6 fois 8 bits, doit valoir :

0x76,0x6f,0x72,0x62,0x69,0x73 ("vorbis")

4.2.1 Header 1 : IdentificationLe premier header du Vorbis identifie le flux comme étant un flux Vorbis. Il est identifié par la valeur

header_type 0. On y trouve toutes les informations globales sur le flux en cours : nombre de canaux, débit,taux d’échantillonage, grande taille et petite taille des fenêtres, etc . . .

[vorbis_version] = lire 32 bits comme un entier non-signé[audio_channels] = lire 8 bits comme un entier non-signé[audio_sample_rate] = lire 32 bits comme un entier non-signé[bitrate_maximum] = lire 32 bits comme un entier signé[bitrate_nominal] = lire 32 bits comme un entier signé[bitrate_minimum] = lire 32 bits comme un entier signé[blocksize_0] = 2 puissance (lire 4 bits comme un entier non-signé)[blocksize_1] = 2 puissance (lire 4 bits comme un entier non-signé)[framing_flag] = lire 1 bit, doit valoir 1 sinon erreur

[vorbis_version] doit être égal à 0, sinon le flux n’est pas décodable. [audio_channels] et [audio_sample_rate] doivent être supérieur à 0. [blocksize_i] représentent les tailles de fenêtres, etdoivent vérifier 64 ≤[blocksize_0]≤[blocksize_1] ≤ 8192.

Les valeurs de bitrate ne sont que des indications, et ne sont pas utilisées dans notre décodeur.Enfin le fanion framing_flag ne sert qu’à verifier le bon déroulement de l’opération de lecture de l’en-

tête. Il suffit de vérifier sa valeur (1), et il peut être jeté. Dans tous les cas, si vous avez un framing_flag àlire, il ne sert qu’à vérifier le positionnement dans le fichier, et n’est pas utilisé lors du décodage.

4.2.2 Header 2 : CommentairesLe deuxième header a comme [header_type] 1. Il sert à encoder les méta-informations sur le flux

Vorbis en cours, comme par exemple le titre de la chanson, la date, la durée ou l’auteur. N’importe quelinformation peut être incluse, mais certaines informations sont normalisées, pour faciliter le décodage.

Le format de stockage est le suivant :

[vendor_length] = lire 32 bits comme un entier non-signé[vendor_string] = lire [vendor_length] caractères[user_comment_list_length] = lire 32 bits comme un entier non-signépour i de 0 à [user_comment_list_length - 1] {

[length_i] = lire 32 bits comme un entier non-signé[user_comment_i] = lire [length_i] caractères

}[framing_bit] = lire 1 bit, doit valoir 1 sinon erreur

Chacun des commentaires [user_comment_i] est de la forme "FIELD=value". Pour rappel, ’=’ en AS-CII se code 0x3D. La première moitié, le champ (FIELD) est insensible à la casse, et indique quelle seral’information qui suit. Ce champ peut valoir n’importe quoi, mais certaines valeurs sont réservées :

TITLE Titre de la piste

Page 23: Projet Logiciel en C - Ensimag

4.2. DÉCODAGE DES EN-TÊTES VORBIS 23

VERSION Info sur la version de la piste (remix ou autre)

ALBUM Album dont est extraite la piste

TRACKNUMBER Le numéro de la piste dans l’album

ARTIST L’artiste qui interprète la piste

COPYRIGHT Propriétaire des droits

LICENSE Information légale

ORGANIZATION Organisation qui produit la piste, ie. le label

DESCRIPTION Description de la piste

GENRE Genre musical

DATE Date d’enregistrement

LOCATION Lieu d’enregistrement

CONTACT Personne à contacter en cas de besoin

ISRC International Standard Recording Code, information de référencement

Ici encore le framing_flag doit être jeté après usage.

4.2.3 Header 3 : ConfigurationLe troisième et dernier header contient toutes les informations requises pour décoder le flux Vorbis. Il

est constitué de 6 sous-parties, une pour chacun des éléments du Vorbis(mode, mapping, codebook, resi-due, floors, et transformée). Chacune des sous-parties contient d’abord le nombre de configurations, puisl’ensemble des configurations disponibles. Les configurations sont stockées dans l’ordre de leur identifiant.

4.2.3.a Codebook

Le décodage des codebooks dans le flux Vorbis est complexe de par le nombre de différents cas pos-sibles.

On commence par récupérer le nombre de codebooks à décoder :

[vorbis_codebook_count] = lire 8 bits comme un entier non-signé[vorbis_codebook_count] = [vorbis_codebook_count] + 1pour [codebook_id] dans 0 .. [vorbis_codebook_count - 1]

décoder le codebook [codebook_id]

Le décodage du codebook se découpe en deux parties : la récupération de l’arbre de Huffman et saconstruction, puis la génération des vecteurs de quantification associés.

Tous les codebooks commencent par un schéma de synchronisation, 0x564342. Avant de commencer àdécoder, il faut vérifier la validité de ce champ :

[vorbis_sync_pattern] = lire 24 bits comme un entier non signési [vorbis_sync_pattern] != 0x564342 alors flux non décodable

On récupère ensuite les deux paramètres communs à tous les modes de représentation des codebooks :[codebook_entries] et [codebook_dimensions], qui représentent respectivement le nombre d’élémentsdans le codebook, et la taille du vecteur de quantification associé.

[codebook_dimensions] = lire 16 bits comme un entier non-signé[codebook_entries] = lire 24 bits comme un entier non-signé

Comme décrit en 2.5, un arbre de Huffman est complètement représenté si on donne l’association va-leur/longueur de l’ensemble de l’alphabet concerné. On distingue trois variantes de cette représentationdans le Vorbis :

– le mode ordonné, dans lequel les entrées sont stockées dans l’ordre des longueurs associées

Page 24: Projet Logiciel en C - Ensimag

24 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

– le mode non-ordonné plein, dans lequel toutes les entrées sont représentées, et toutes les longueurssont données dans l’ordre des entrées

– le mode non-ordonné creux, dans lequel certaines entrées peuvent ne pas être représentées dansl’arbre

Dans le flux, on procède alors comme suit, avec [codeword_lengths] un vecteur de taillecodebook_entries :

[ordered] = lire 1 bitsi [ordered] == 0 alors

[sparse] = lire 1 bitpour [entry] dans 0 .. [codebook_entries - 1] faire

si [sparse] == 1 alors[used] = lire 1 bitsi [used] == 1 alors

[length] = lire 5 bits comme un entier non signé[codeword_lengths[entry]] = [length + 1]

sinon[codeword_lengths[entry]] = <unused>

fin sisinon // sparse == 0

[length] = lire 5 bits comme un entier non signé[codeword_lengths[entry]] = [length + 1]

fin sifin pour

sinon // ordered == 1[entry] = 0[length] = lire 5 bits comme un entier non signétant que [entry] < [codebook_entries] faire

[length] = [length + 1][num_bits] = ilog([codebook_entries] - [entry])[entry_number] = lire [num_bits] bits comme un entier non signépour [i] dans [entry] .. [entry + entry_number - 1] faire

[codeword_lengths[entry]] = [length]fin pour[entry] = [entry] + [entry_number][length] = [length] + 1

fin tant quesi [entry] > [codebook_entries] alors flux non décodable

fin si

Une fois qu’on a récupéré [codeword_lengths], on peut donc construire l’arbre de Huffman. Nous vouslaissons libre de choisir l’algorithme à utiliser pour construire l’arbre.

La représentation des vecteurs de quantification se fait en deux étapes. D’abord on récupère les pa-ramètres nécessaires au décodage du vecteur, puis on décode le vecteur. Concernant la représentation, ondistingue trois types :

– le type 0, dans lequel il n’y a pas de vecteur– le type 1 construit sa table à partir des informations contenues dans le fichier– le type 2 lit sa table complètement dans le fluxLa récupération des paramètres nécessaires au décodage du vecteur se fait selon l’algorithme suivant.

Les fonctions float32_unpack et lookup1_values sont définies après.

[codebook_lookup_type] = lire 4 bits comme un entier non signési [codebook_lookup_type] > 0 alors

[codebook_minimum_value] = float32_unpack(lire 32 bits comme un entier nonsigné)

[codebook_delta_value] = float32_unpack(lire 32 bits comme un entier nonsigné)

[codebook_value_bits] = lire 4 bits comme un entier non-signé et ajouter 1[codebook_sequence_p] = lire 1 bit

Page 25: Projet Logiciel en C - Ensimag

4.2. DÉCODAGE DES EN-TÊTES VORBIS 25

si [codebook_lookup_type] == 1 alors[codebook_lookup_values] = lookup1_values([codebook_entries],[

codebook_dimensions] )sinon

[codebook_lookup_values] = [codebook_entries] * [codebook_dimensions]fin sipour [value] dans 0 .. [codebook_lookup_values - 1] faire[codebook_multiplicands[value]] = lire [codebook_value_bits] bits comme

un entier non signéfin pour

fin si

La fonction float32_unpack permet de récupérer un nombre flottant à partir de sa représentation entièredans le flux Vorbis. La représentation utilisée se base sur une représentation mantisse/exposant : m×2e, avecm la mantisse et e l’exposant. Ceci est fait avec l’algorithme suivant, avec x l’entier à transformer.

[mantissa] = [x] AND 0x001fffff (non signé)[sign] = [x] and 0x80000000 (non signé)[exponent] = [x] AND 0x7fe00000 >> 21 (non signé)si [sign] != 0 alors

[mantissa] = -[mantissa]fin sireturn [mantissa] * (2 ^ ([exponent] - 788))

La fonction lookup1_values(a,b) renvoie la valeur définie comme étant : "Le plus grand entier r telque rb ≤ a".

Une fois que l’on a extrait tous les paramètres nécessaires, on peut construire le vecteur de quantificationassocié à chaque entrée de l’arbre de Huffman. Ce vecteur [vq_vector] est de taille [codebook_dimensions].

Dans le cas d’un type 1, on a l’algorithme suivant, qui s’applique pour chaque entrée [entry] :

[last] = 0[index_divisor] = 1pour [i] dans 0 .. [codebook_dimensions - 1] faire

[index] = ([entry] / [index_divisor]) modulo [codebook_lookup_values][vq_vector[i]] = [codebook_multiplicands[index]] * [codebook_delta_value] +

[codebook_minimum_value] + [last]si [codebook_sequence_p] == 1 alors

[last] = [vq_vector[i]]fin si[index_divisor] = [index_divisor] * [codebook_lookup_values]

fin pour

Dans le cas d’un type 2, l’algorithme est légérement différent :

[last] = 0[index] = [entry] * [codebook_dimensions]pour [i] dans 0 .. [codebook_dimensions - 1] faire

[vq_vector[i]] = [codebook_multiplicands[index]] * [codebook_delta_value] +[codebook_minimum_value] + [last]

si [codebook_sequence_p] == 1 alors[last] = [vq_vector[i]]

fin si[index] = [index + 1]

fin pour

Page 26: Projet Logiciel en C - Ensimag

26 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

4.2.3.b Time Domain Transforms

Cette partie de l’en-tête 3 n’est pas utilisée dans la version actuelle de Vorbis, mais elle doit être luepour conserver la synchronisation dans le flux et pouvoir décoder la suite de l’en-tête.

[time_count] = lire 6 bits comme un entier non signé et ajouter 1pour [i] dans 0 .. [time_count - 1] faire

[val] = lire 16 bit;si [val] != 0 alorsflux non decodable

fin sifin pour

4.2.3.c floor

Pour la lecture des configurations de floor, on procède de la manière suivante. On récupère le nombrede configurations à lire, puis pour chacune des configurations, on identifie son type et on décode le typecorrespondant (Section 4.4).

[floor_count] = lire 6 bits comme un entier non signé et ajouter 1pour [i] dans 0 .. [floor_count - 1] faire

[floor_type] = lire 16 bits comme un entier non signési [floor_type] == 0 alors[floor_configurations[i]] = décoder un floor de type 0

fin sisi [floor_type] == 1 alors[floor_configurations[i]] = décoder un floor de type 1

fin sisi [floor_type] > 1 alorsflux non decodable

fin sifin pour

4.2.3.d residue

Pour la lecture des configurations de residue, on procède de la manière suivante. On récupère le nombrede configurations à lire, puis pour chacune des configurations, on identifie son type et on décode le typecorrespondant.

[residue_count] = lire 6 bits comme un entier non signé et ajouter 1pour [i] dans 0 .. [residue_count - 1] faire[residue_type] = lire 16 bits comme un entier non signési [residue_type] == 0 alors[residue_configurations[i]] = décoder un résidue de type 0

fin sisi [residue_type] == 1 alors[residue_configurations[i]] = décoder un résidue de type 1

fin sisi [residue_type] == 2 alors[residue_configurations[i]] = décoder un résidue de type 2

fin sisi [residue_type] > 2 alorsflux non décodable

fin sifin pour

Page 27: Projet Logiciel en C - Ensimag

4.2. DÉCODAGE DES EN-TÊTES VORBIS 27

4.2.3.e mapping

Pour la lecture des configurations de mapping, on procède de la manière suivante. On récupère le nombrede configurations à lire, puis pour chacune des configurations, on identifie son type et on décode le typecorrespondant.

Il n’existe qu’un type de mapping dans la norme actuelle, mais la norme est extensible.

[mapping_count] = lire 16 bits comme un entier non signé et ajouter unpour [i] dans 0 .. [mapping_count - 1] faire

[mapping_type] = lire 16 bits comme une entier non signési [mapping_type] == 0 alorsdécoder un mapping de type 0

sinonflux non décodable

fin sifin pour

Le mapping de type 0 : Le décodage spécifique du type 0 de mapping se présente comme suit.

[flag] = lire 1 bit comme un booleensi [flag] == 1 alors[mapping_submaps] = lire 4 bits comme un entier non signé et ajouter un

sinon[mapping_submaps] = 1

fin si[flag_square_polar] = lire 1 bit comme un booleensi [flag_square_polar] == 1 alors[mapping_coupling_steps] = lire 8 bits comme un entier non signé et ajouter

unpour [j] dans 0 .. [mapping_coupling_steps - 1] faire

[mapping_magnitude[j]] = lire ilog([audio_channels - 1]) bits comme unentier non signé

[mapping_angle[j]] = lire ilog([audio_channels - 1]) bits comme unentier non signé

si ([mapping_magnitude[j]] > [audio_channels - 1]) OU([mapping_angle[j]] > [audio_channels - 1]) alorsflux non décodable

fin sifin pour

sinon[mapping_coupling_steps] = 0

fin si

[reserved] = lire 2 bitssi [reserved] != 0 alorsflux non décodable

fin si

si [mapping_submaps] > 1pour [j] dans 0 .. [audio_channels - 1] faire

[mapping_mux[j]] = lire 4 bits comme un entier non signési [mapping_mux[j]] > [mapping_submaps - 1] alorsflux non décodable

fin sifin pour

sinonpour [j] dans 0 .. [audio_channels - 1] faire

[mapping_mux[j]] = 0fin pour

fin si

Page 28: Projet Logiciel en C - Ensimag

28 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

pour [j] dans 0 .. [mapping_submaps - 1] faire[discard] = lire 8 bits (non utilisés)[mapping_submap_floor[j]] = lire 8 bits comme un entier non signési [mapping_submap_floor[j]] > [floor_count - 1] alorsflux non décodable

fin si[mapping_submap_residue[j]] = lire 8 bits comme un entier non signési [mapping_submap_residue[j]] > [residue_count - 1] alorsflux non décodable

fin sifin pour

On remarque donc que dans le décodage du type 0 de mapping, on décode dans l’ordre les informationscontenues dans le mapping (cf. 2.2). On commence par décoder les éventuelles informations relatives aucouplage canal, uniquement le square_polar. Le couplage canal se réalisant en étape, pour chaque étape,on récupère l’indice du canal contenant le module (magnitude) et l’argument (angle).

Dans un second temps on récupère les informations relative au submap à utiliser pour chacun des canauxdans le mapping courant. Les submaps représentent une association floor/residue.

Enfin, on récupère les descriptions des différentes submap, à savoir, le floor et le residue à utiliser pourchaque submap.

4.2.3.f mode

Pour la lecture des configurations de mode, on procède de la manière suivante. On récupère le nombrede configurations à lire, puis pour chacune des configurations, on la décode.

Il n’existe pas d’extension possible sur ce point dans la norme Vorbis, on lit donc systématiquement lemême type d’entrée pour les configurations de mode.

[mode_count] = lire 6 bits comme un entier non signé et ajouter unpour [i] dans 0 .. [mode_count] faire[mode_configurations[i]] = décoder un mode

fin pour

Le décodage d’un mode se présente comme suit.

[mode_blockflag] = lire 1 bit[mode_windowtype] = lire 16 bits comme un entier non signé[mode_transformtype] = lire 16 bits comme un entier non signé[mode_mapping] = lire 8 bits comme un entier non signési ([mode_windowtype] != 0) OU([mode_transformtype] != 0) OU([mode_mapping] > [mapping_count]) alorsflux non décodable

fin si

Il existe tout de même des extensions possibles dans cette partie de la norme, mais sous la forme dechamps non utilisés, c’est le cas du [mode_windowtype] et du [mode_transformtype], qui doivent être àzéro puisque aujourd’hui la norme n’utilise qu’un type de fenêtre et un seul type de transformée (la MDCT).Ces champs sont présents uniquement pour que les flux actuels soient compatibles avec les versions futuresde la norme. Nous ne prendrons donc pas en compte ces champs dans ce projet (mais ils doivent être lusdans l’en-tête).

4.2.3.g Framing flag

La lecture de l’en-tête 3 se finit par la lecture d’un bit dit de framing qui permet de vérifier que l’on a bienconservé la synchronisation du flux pendant l’ensemble de la lecture de l’en-tête. Cette lecture s’effectue dela manière suivante. Il peut être jeter une fois l’opération terminée.

Page 29: Projet Logiciel en C - Ensimag

4.3. DÉCODAGE DES PAQUETS AUDIO VORBIS 29

[framing_flag] = lire 1 bitsi [framing_flag] != 1 alorsflux non décodable

fin si

La lecture de l’en-tête de configuration est maintenant finie.

4.3 Décodage des paquets audio VorbisCette section vous présente les étapes de lecture du flux permettant de récupérer les données nécessaires

au décodage.

4.3.1 Décodage du flux : Récupération des informationsLe décodage d’un paquet audio commence par la lecture du flux Vorbis.

4.3.1.a Packet type

La première lecture dans le flux vorbis est le type de paquet. On procède comme suit.

[packet_type] = lire 1 bitsi [packet_type] != 0 alorspaquet incorrect

fin si

4.3.1.b mode

L’étape suivante de la lecture du paquet est la récupération du mode à utiliser pour le paquet courant,ainsi que les informations complémentaires sur la fenêtre s’il y a lieu.

[mode_number] = lire ilog([mode_count - 1]) bitssi [mode_blockflag] == 0 alors[n] = [blocksize_0]

sinon[n] = [blocksize_1]

fin si

si [mode_blockflag] == 1 alors[previous_window_flag] = lire 1 bit[next_window_flag] = lire 1 bit

si [previous_window_flag] == 0 alorsfenetre hybride à gauche

fin sisi [next_window_flag] == 0 alorsfenetre hybride à droite

fin sifin si

À l’issue de cette étape on peut préparer le filtre qui sera utilisé dans l’iMDCT. La préparation del’enveloppe (fenêtre) se fait à partir de l’algorithme présenté en Section 2.4. Les indices de début et de finde pente sont calculés comme suit, avec [n] qui représente la taille de la fenêtre courante (blocksize_0 oublocksize_1).

si fenêtre hybride à gauche alors[debut_pente_gauche] = [n] / 4 - [blocksize_0] / 4

Page 30: Projet Logiciel en C - Ensimag

30 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

[fin_pente_gauche] = [n] / 4 + [blocksize_0] / 4sinon

[debut_pente_gauche] = 0[fin_pente_gauche] = [n] / 2

fin sisi fenêtre hybride à droite alors

[debut_pente_droite] = [n] * 3/4 - [blocksize_0] / 4[fin_pente_droite] = [n] * 3/4 + [blocksize_0] / 4

sinon[debut_pente_droite] = [n] / 2[fin_pente_droite] = [n]

fin si

4.3.1.c mapping

Les prochaines étapes du décodages sont faites sous le contrôle du mapping courant, car seul lui connaitles informations qui permettront de réaliser les lecture dans le flux (les floors à utiliser, les residues, etc).Son travail se résume en les tâches suivantes.

i) Floor Les floors sont codés dans l’ordre dans le flux. On va donc les lire un par un.

pour [i] dans 0 .. [audio_channels - 1] faire[submap_number] = [mapping_mux[i]][floor_number] = [submap_floor[submap_number]][floor_type] = [floor_types[floor_number]]

[ret] = décoder le floor pour le canal [i] en fonction de son type.

si [ret] == <unused> alors[no_residue[i]] = true

fin sifin pour

En cas de fin de paquet durant le décodage des floors, tous les canaux sont nuls, et on passe directementà l’étape de recouvrement.

ii) Non-zero propagate De manière générale, lorsque le floor d’un canal est non utilisé, le flux necontient pas de residue codé pour ce canal. Cependant pour des soucis de couplage, on doit maintenir desvecteurs pour l’ensemble des couples. En effet, le couplage peut utiliser de l’entrelacement de canal. Dansce cas, on pourrait entrelacer un canal nul avec un canal non-nul, ce qui donnerait deux canaux non nuls.Cependant, les canaux marqués comme non-utilisés ne sont pas décodés, on aurait alors une partie desresidues manquante au moment du désentrelacement.

pour [i] dans 0 .. [mapping_coupling_steps - 1] faire[magnitude_channel] = [mapping_magnitude[i]][angle_channel] = [mapping_angle[i]]si ([no_residue[magnitude_channel]] == false) OU([no_residue[angle_channel]] == false) alors[no_residue[magnitude_channel]] = false[no_residue[angle_channel]] = false

fin sifin pour

iii) Residues Contrairement aux floors, les residues ne sont pas codés dans l’ordre des canaux dans leflux, mais dans l’ordre des submap.

Page 31: Projet Logiciel en C - Ensimag

4.3. DÉCODAGE DES PAQUETS AUDIO VORBIS 31

pour [i] dans 0 .. [mapping_submaps - 1] faire[ch] = 0pour [j] dans 0 .. [audio_channels - 1] fairesi [mapping_mux[j]] == [i] alorssi [no_residue[j]] == true[do_not_decode_flag[ch]] = true

sinon[do_not_decode_flag[ch]] = false

fin siincrémenter [ch]

fin sifin pour

[residue_number] = [mapping_submap_residue[i]][residue_type] = [residue_types[residue_number]]

décoder [ch] vecteurs en utilisant [residue_type] et [do_not_decode_flag]dans [decoded_residues]

[ch] = 0pour [j] dans 0 .. [audio_channels - 1] fairesi [mapping_mux[j]] == [i] alors[residues[j]] = [decoded_residues[ch]]incrémenter [ch]

fin sifin pour

fin pour

4.3.2 inverse couplingUne fois que toutes les informations ont été extraites du flux Vorbis, il faut appliquer les opérations

permettant de regénérer les PCM. La première opération sert à inverser le couplage des canaux, pour regé-nérer les residues propres à chaque canaux plutôt que les residues couplés. On utilise pour cela l’algorithmesuivant (qui représente une rotation dans le référentiel utilisé).

pour [i] dans [vorbis_mapping_coupling_steps - 1] .. 0[magnitude_vector] = [vorbis_mapping_magnitude][i][magnitude_angle] = [vorbis_mapping_angle][i]pour [j] dans 0 .. taille([magnitude_vector]) - 1 faire

[M] = [magnitude_vector[j]];[A] = [angle_vector[j]];si [M] > 0 alors

si [A] > 0 alors[angle_vector[j]] = [M] - [A]

sinon[angle_vector[j]] = [M][magnitude_vector[j]] = [M] + [A]

fin sisinon

si [A] > 0 alors[angle_vector[j]] = [M] + [A]

sinon[angle_vector[j]] = [M][magnitude_vector[j]] = [M] - [A]

fin sifin si

fin pourfin pour

Page 32: Projet Logiciel en C - Ensimag

32 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

4.3.3 dot productAprès l’inverse coupling, chaque canal dispose de ses informations de floor et de residues. L’opération

suivante consiste en une reconstruction du signal fréquentiel à partir des ces deux éléments. L’algorithmeutilisé est un produit scalaire (dot product en anglais), qui consiste en une multiplication coefficient à coef-ficient des floors avec les residues.

4.3.4 iMDCTUne fois qu’on a récupéré le signal fréquentiel en entier, il faut le convertir en temporel. Ceci se fait en

utilisant l’iMDCT, qui a été présenté au chapitre précédent. Le N de l’iMDCT représente bien ici la taillede la fenêtre. N’oubliez pas de multiplier par la fonction de fenêtrage !

4.3.5 overlap addLa dernière opération pour obtenir le signal temporel final, tel qu’il sera écouté ou presque, est le re-

couvrement. Cette opération consiste en une addition des données issues de la fenêtre précédente avec lesdonnées issues de la fenêtre courante. Une fois que les indices qui se recouvrent sont calculés (cf. Section2.4), il s’agit simplement d’une addition de vecteurs.

Attention cependant, les seules données valides après cette opération sont les données qui se recou-vraient. Les données non encore additionnées sont à conserver et à garder pour la fenêtre suivante. Lapremière fenêtre ne sert qu’à initialiser, rien n’est transmis après son décodage.

4.3.6 Production des échantillons PCMLes PCMs sont disponibles après l’opération de recouvrement. La dernière étape à prendre en compte

concernent les canaux, et l’ordre défini dans la norme :

1 canal : son monophonique

2 canaux : son stéréophonique, gauche puis droite

3 canaux : son "1d-surround", gauche, centre, droite

4 canaux : son quadriphonique, avant gauche, avant droite, arrière gauche, arrière droite

5 canaux : son surround, avant gauche, centre, avant droite, arrière gauche, arrière droite

6 canaux : son 5.1, même ordre que 5 canaux, le 6ème représente le LFE (Low Frequency Effect, la basse)pour le caisson de basse

7 canaux : son 6.1, avant gauche, centre, avant droite, côté gauche (dans le 6.1, on a 3 enceintes devant, 2directement sur le coté, et une derrière), côté droit, arrière centre, LFE

8 canaux : son 7.1, même ordre que le 6.1 sauf que l’arrière est remplacé par un arrière gauche et unarrière droite (dans l’ordre).

4.4 FloorsNous ne présentons ici que le floor de type 1 car les floors de type 0 ne font pas partie du sujet, ils ne

sont pas à réaliser.

4.4.1 Décodage de l’en-têteLes floors de type 1 sont rangés dans le paquet par partition, et chaque partition est d’une classe donnée.

La première information présente dans le descripteur dans l’en-tête pour un floor de type 1 est la liste desclasses pour chacune des partitions.

De plus, chaque classe représente un certain nombre de points significatifs pour la construction de lacourbe gros grain. Ce nombre de points et les sous classes qui permettront de les lire dans les paquets audiosont ensuite lus dans l’en-tête. On notera la présence des [masterbooks] et [subclass_books] qui sont desindices de codebook.

Pour finir, dans les paquets audio seules les ordonnées de la courbe sont présentes. La dernière partie dudescripteur contient les abscisses correspondantes.

Page 33: Projet Logiciel en C - Ensimag

4.4. FLOORS 33

[floor1_partitions] = lire 5 bits comme un entier non signé[maximum_class] = -1pour [i] dans 0 .. [floor1_partitions - 1] faire[floor1_partition_class_list[i]] = lire 4 bits comme un entier non signé

fin pour

[maximum_class] = valeur maximum du vecteur [floor1_partition_class_list]

pour [i] dans 0 .. [maximum_class] faire[floor1_class_dimensions[i]] = lire 3 bits comme un entier non signé et

ajouter un[floor1_class_subclasses[i]] = lire 2 bits comme un entier non signési ([floor1_class_subclasses[i]] != 0) alors[floor1_class_masterbooks[i]] = lire 8 bits comme un entier non signé

fin sipour [j] dans 0 .. (2 puissance [floor1_class_subclasses[i]]) - 1 faire[floor1_subclass_books[i][j]] = lire 8 bits comme un entier non signé et

soustraire unfin pour

fin pour

[floor1_multiplier] = lire 2 bits comme un entier non signé et ajouter un[rangebits] = lire 4 bits comme un entier non signé[floor1_X_list[0]] = 0[floor1_X_list[1]] = 2 puissance [rangebits];[floor1_values] = 2

pour [i] dans 0 .. [floor1_partitions - 1] faire[current_class_number] = [floor1_partition_class_list[i]]pour [j] dans 0 .. [floor1_class_dimensions[current_class_number] - 1] faire[floor1_X_list[floor1_values]] = lire [rangebits] bits comme un entier

non signéincrémenter [floor1_values] de un

fin pourfin pour

4.4.2 Décodage du paquet

Le décodage d’un floor de type 1 dans un paquet audio commence par la lecture d’un bit qui permet desavoir si le floor courant est utilisé pour ce canal.

[nonzero] = lire 1 bit comme un booleen

Si le bit [nonzero] est égal à zéro, il n’y a aucune donnée à décoder et le canal en court de décodagen’a pas de floor. La fonction de décodage doit donc retourner <unused> (cf 4.3.1.c). Sinon le décodage desdonnées se déroule de la manière suivante :

[range] = élement [floor1_multiplier - 1] du vecteur { 256, 128, 86, 64 }[floor1_Y[0]] = lire ilog([range - 1]) bits comme un entier non signé[floor1_Y[1]] = lire ilog([range - 1]) bits comme un entier non signé[offset] = 2;

pour [i] dans 0 .. [floor1_partitions - 1] faire[class] = [floor1_partition_class[i]][cdim] = [floor1_class_dimensions[class]][cbits] = [floor1_class_subclasses[class]][csub] = (2 puissance [cbits]) - 1[cval] = 0

Page 34: Projet Logiciel en C - Ensimag

34 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

si ([cbits > 0) alors[cval] = lire dans le paquet en utilisant le codebook scalaire numéro

([floor1_class_masterbooks[class]])fin sipour [j] dans 0 .. [cdim - 1] faire[book] = [floor1_subclass_books[class][cval AND csub]][cval] = [cval] décalé à droite de [cbits] bitssi ([book] >= 0) alors[floor1_Y[[j]+[offset]] = lire dans le paquet en utilisant le codebook

scalaire [book]sinon[floor1_Y[[j]+[offset]] = 0

fin sifin pour[offset] = [offset] + [cdim]

fin pour

Les données rangées dans le paquet audio pour un floor de type 1 correspondent aux ordonnées del’ensemble des points décrits dans la configuration de floor courante. On lit tout d’abord les deux premièresvaleurs de manière systématique, pour rappel elles correspondent à la première et à la dernière valeur de lacourbe. On lit ensuite, pour chaque partition, une valeur qui nous permettra de connaître les sous classesà utiliser en utilisant le codebook masterbooks[class] en mode scalaire. Les ordonnées sont ensuite luesgrâce au codebook des sous classes ainsi décrites en mode scalaire toujours.

4.4.3 Synthèse de la courbe

La première phase de la synthèse de la courbe est le calcul en amplitude des ordonnées. En effet, seulesla première et la dernière valeur de la courbe sont placées tel quel dans le paquet audio. Seules des valeursdifférentielles sont ensuite placées pour les autres points de la courbe. Cette étape consiste donc à rétablirles valeurs en lieu et place des valeurs différentielles.

[range] = vector { 256, 128, 86, 64 } element [floor1_multiplier - 1][floor1_step2_flag[0]] = set[floor1_step2_flag[1]] = set[floor1_final_Y[0]] = [floor1_Y[0]][floor1_final_Y[1]] = [floor1_Y[1]]

pour [i] dans 2 .. [floor1_values - 1] faire[low_neighbor_offset] = low_neighbor([floor1_X_list], [i])[high_neighbor_offset] = high_neighbor([floor1_X_list], [i])

[predicted] = render_point( [floor1_X_list[low_neighbor_offset]],[floor1_final_Y[low_neighbor_offset]],[floor1_X_list[high_neighbor_offset]],[floor1_final_Y[high_neighbor_offset]],[floor1_X_list[i]] )

[val] = [floor1_Y[i]][highroom] = [range] - [predicted][lowroom] = [predicted]si ( [highroom] < [lowroom] ) alors[room] = [highroom] * 2

sinon[room] = [lowroom] * 2

fin si

si ([val] != 0) alors[floor1_step2_flag[low_neighbor_offset]] = set[floor1_step2_flag[high_neighbor_offset]] = set[floor1_step2_flag[i]] = set

Page 35: Projet Logiciel en C - Ensimag

4.4. FLOORS 35

si ([val] >= [room]) alorssi ( [highroom] > [lowroom] ) alors[floor1_final_Y[i]] = [val] - [lowroom] + [predicted]

sinon[floor1_final_Y[i]] = [predicted] - [val] + [highroom] - 1

fin sisinonsi ([val] est impair) alors[floor1_final_Y[i]] = [predicted] - (([val] + 1) / 2)

sinon[floor1_final_Y[i]] = [predicted] + ([val] / 2)

fin sifin si

sinon[floor1_step2_flag[i]] = unset[floor1_final_Y[i]] = [predicted]

fin sifin pour

Pour calculer la valeur en amplitude de chacun des points de la courbe, cette fonction doit interpolerentre ses deux voisins les plus proches (un de chaque coté) dont on connait déjà la valeur (dans l’ordre danslequel sont rangées les valeurs). On fait appel pour cela à deux fonctions low_neighbor et high_neighbor.L’interpolation est réalisée par la fonction render_point. Muni de la valeur prédite et de la valeur lue onpeut donc déduire la valeur réelle pour le point en question. On notera que le codage des valeurs est un peudifférent lorsqu’on approche des valeurs extrêmes. On notera aussi que la lecture d’une valeur nulle dans lepaquet abouti à un cas où on utilise directement la valeur prédite conformément à la description du format.

La seconde phase de la synthèse de la courbe gros grain est l’interpolation linéaire entre chacun descouples de points décrits par la configuration de floor courante. Vous aurez remarqué jusqu’alors que lespoints de la courbes ne sont pas ordonnés correctement pour cette étape. La première chose à faire estdonc d’ordonner les vecteurs [floor1_final_Y], [floor1_step2_flags] et [floor1_X_list] dans l’ordredes valeurs de [floor1_X_list] croissantes. Les vecteurs ordonnés sont notés [floor1_final_Y_o], [floor1_step2_flags_o] et [floor1_X_list_o]. À partir de ces trois vecteurs ordonnés, on peut faire appelà la fonction render_line pour chacun des couples de deux points afin de peupler le vecteur [floor]. Cevecteur est de taille n2 (ou faut-il lire N

2 ). Au besoin, on étend la valeur jusqu’à la fin du vecteur. Dans le cascontraire, si le floor courant est plus long que la fenêtre en cours, il faudra songer à ne pas déborder du vec-teur, on tronque le floor. L’étape finale de cette fonction est la production des valeurs finales, puisque l’en-semble des valeurs produites jusqu’alors sont des indices dans la table [floor1_inverse_dB_static_table] (cette table vous est donnée dans le fichier d’exemple floor_type1_exemple.c).

[hx] = 0[lx] = 0[ly] = [floor1_final_Y_o[0]] * [floor1_multiplier]

pour [i] dans 1 .. [floor1_values - 1] faire

si ([floor1_step2_flag_o[i]] is set ) alors[hy] = [floor1_final_Y_o[i]] * [floor1_multiplier][hx] = [floor1_X_list_o[i]]render_line( [lx], [ly], [hx], [hy], [floor] )[lx] = [hx][ly] = [hy]

fin sifin pour

si ( [hx] < [n2] ) alorsrender_line( [hx], [hy], [n], [hy], [floor] )

fin si

si ( [hx] > [n2] ) alorstronquer [floor] à [n] élements

Page 36: Projet Logiciel en C - Ensimag

36 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

fin si

pour [i] dans 0 .. [n2 - 1] faire[floor[i]] = [floor1_inverse_dB_static_table[floor[i]]]

fin pour

4.4.4 Fonctions pratiques

i) low_neighbor La fonction low_neighbor(v, x) renvoie la position n tel que v[n] soit le plus grandélément inférieur à v[x] et que n soit inférieur à x.

ii) high_neighbor La fonction high_neighbor(v, x) renvoie la position n tel que v[n] soit le plus petitélément supérieur à v[x] et que n soit inférieur à x.

iii) render_point Cette fonction permet de calculer l’interpolation de la valeur en un point précis.

[dy] = [y1] - [y0][adx] = [x1] - [x0][ady] = absolute value of [dy][err] = [ady] * ([X] - [x0])[off] = [err] / [adx] using integer divisionsi ([dy] < 0) alors[Y] = [y0] - [off]

sinon[Y] = [y0] + [off]

fin si

iv) render_line Cette fonction est une extension de l’algorithme de Bresenham[1] pour le tracé deligne.

[dy] = [y1] - [y0][adx] = [x1] - [x0][ady] = valeur_absolue([dy])[base] = [dy] / [adx] (division entière)[x] = [x0][y] = [y0][err] = 0si ([dy] < 0) alors[sy] = [base] - 1

sinon[sy] = [base] + 1

fin si[ady] = [ady] - valeur_absolue([base]) * [adx][v[x]] = [y]pour [x] dans [x0 + 1] .. [x1 - 1] faire[err] = [err] + [ady];si ( [err] >= [adx] ) alors[err] = [err] - [adx][y] = [y] + [sy]

sinon[y] = [y] + [base]

fin si[v[x]] = [y]

fin pour

Page 37: Projet Logiciel en C - Ensimag

4.5. RESIDUES 37

4.5 ResiduesLes trois types (0, 1 et 2) de residue sont relativement similaires. Une bonne partie de leur traitement est

donc commun. La partie suivante décrit les parties communes du traitement de ces trois types de residue.Les trois parties qui la suivent évoquent les particularités de chacun.

4.5.1 Partie communeÉtant donné que la grosse différence entre les trois types de residue actuels de la norme Vorbis réside

essentiellement dans l’organisation des données, l’en-tête est commune aux trois types. D’un autre coté,même si l’organisation des données dans le paquet audio est un peu différente (type 0 ou 1) ou encore queles canaux sont tous codés ensemble (type 2), une partie non négligeable du décodage du paquet est elleaussi commune.

4.5.1.a Décodage de l’en-tête

Le descripteur de residue (type 0, 1 ou 2) présent dans l’en-tête 3 se présente de la manière suivante.Ce descripteur commence par des informations globales, à savoir, le début et la fin de la portion de vecteurcodé par ce descripteur de residue. Cette portion se subdivise en partitions dont la taille est donnée parle troisème champ. Ces partitions ont une classification (le nombre maximum est donné par le quatrièmechamp). On trouve enfin la référence au codebook qui permettra de connaître les classifications réellementutilisées par lecture en mode scalaire dans le paquet audio.

La lecture du fichier, et la production des valeurs de residue se fait en plusieurs passes (8). On lit doncensuite pour chaque classification un mot de 8 bits qui représente par chaque bit si pour cette passe laclassification code des valeurs.

La dernière étape est enfin de récupérer pour chaque combinaison utilisée (comme précédemment ex-pliqué précédement) le codebook qui permettra de lire les valeurs en mode VQ.

[residue_begin] = lire 24 bits comme un entier non signé[residue_end] = lire 24 bits comme un entier non signé[residue_partition_size] = lire 24 bits comme un entier non signé et ajouter 1[residue_classifications] = lire 6 bits comme un entier non signé et ajouter 1[residue_classbook] = lire 8 bits comme un entier non signé

pour (i] dans 0 .. [residue_classifications - 1] faire[high_bits] = 0[low_bits] = lire 3 bits comme un entier non signé[bitflag] = lire 1 bit comme un booleensi ( [bitflag] est vrai ) alors[high_bits] = lire 5 bits comme un entier non signé

fin si[residue_cascade[i]] = [high_bits] * 8 + [low_bits]

fin pour

pour[i] dans 0 .. [residue_classifications - 1] fairepour [j] dans 0 .. 7 fairesi ( [residue_cascade[i]] bit [j] == 1 ) alors[residue_books[i][j]] = lire 8 bits comme un entier non signé

sinon[residue_books[i][j]] = <unused>

fin sifin pour

fin pour

4.5.1.b Décodage du paquet audio

La partie commune aux trois types de residue pour le décodage des paquet audio correspond au squelettede récupération des partitions une par une.

Page 38: Projet Logiciel en C - Ensimag

38 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

En premier lieu, un certain nombre de variables peuvent être utiles pour réaliser ce décodage. comme lataille du vecteur de sortie ou encore la taille réellement codée par ce residue (en effectuant une intersectionentre la taille totale du vecteur et la taille totale du residue codé).

On récupère la taille des codebook, on calcule aussi le nombre de valeurs à lire et enfin le nombre departitions à lire.

La lecture se déroule donc en 8 passes. La première passe possède un traitement supplémentaire quiconsiste à décoder les classifications à utiliser pour lire les valeurs à suivre. Ce décodage s’effectue pardivision euclidienne par [residue_classifications] où le reste est l’indice de la classification de la ieme

partition.On décode enfin les partitions une par une en utilisant les informations fraichement produites qui nous

permettent de récupérer le codebook de référence. On notera que cette lecture de partition est spécifique autype de residue, elle sera donc expliquée dans les partie suivantes.

[actual_size] = current blocksize/2;si ([residue_type] == 2) alors[actual_size] = [actual_size] * [ch]

fin si[limit_residue_begin] = minimum ([residue_begin], [actual_size]);[limit_residue_end] = minimum ([residue_end], [actual_size]);

[classwords_per_codeword] = [codebook_dimensions] du codebook [residue_classbook]

[n_to_read] = [limit_residue_end] - [limit_residue_begin][partitions_to_read] = [n_to_read] / [residue_partition_size]

allouer et mettre à zéro les vecteurs de sortie

si ([n_to_read] == 0) alorsStop. Pas de résidue à lire.

fin si

pour [pass] dans 0 .. 7 faire[partition_count] = 0tant que ([partition_count] < [partitions_to_read]) fairesi ( [pass] == 0 ) alorspour [j] dans 0 .. [ch - 1] fairesi (non [do_not_decode[j]]) alors[temp] = lire dans le paquet en utilisant le codebook [

residue_classbook]en mode scalaire

pour [i] dans [classwords_per_codeword - 1] ... 0 faire[classifications[j][i + [partition_count]] =

[temp] modulo [residue_classifications](modulo entier)

[temp] = [temp] / [residue_classifications](division entière)

fin pourfin si

fin pourfin sipour [i] dans 0 .. [classwords_per_codeword - 1] ettant que [partition_count] < [partitions_to_read] fairepour [j] dans 0 .. [ch - 1] fairesi (non [do_not_decode[j]]) alors[vqclass] = [classifications[j][partition_count]][vqbook] = [residue_books[vqclass][pass]]si ([vqbook] non <unused >) alors

Page 39: Projet Logiciel en C - Ensimag

4.5. RESIDUES 39

décoder la partition dans le vecteur numéro [j] en commençantà la case [limit_residue_begin]+[partition_count]*[

residue_partition_size]en utilisant le codebook [vqbook] en mode VQ

fin sifin si

fin pourincrémenter [partition_count] de un

fin pourfin pour

fin pour

4.5.2 Type 0La seule spécificité du residue de type 0 réside dans la lecture des partitions. Dans ce residue, les valeurs

d’un même canal sont entrelacées dans les vecteurs de VQ comme suit.

vecteur de residue original: [0 1 2 3 4 5 6 7 ]codebook dimensions = 8 encodé comme: [0 1 2 3 4 5 6 7 ]codebook dimensions = 4 encodé comme: [0 2 4 6], [1 3 5 7]codebook dimensions = 2 encodé comme: [0 4], [1 5], [2 6], [3 7]codebook dimensions = 1 encodé comme: [0], [1], [2], [3], [4], [5], [6], [7]

Le décodage d’une partition se présente donc comme suit. On produit donc [n] valeurs dans le vecteur[v] en commençant à la case [offset].

[n] = [residue_partition_size][v] = vecteur de sortie[offset] décalage dans le vecteur [v]

[step] = [n] / [codebook_dimensions]pour [i] dans 0 .. [steps - 1][entry_temp] = lire le vecteur à partir du paquet en utilisant le codebook

courant en mode VQpour [j] dans 0 .. [codebook_dimensions - 1] faire[v[offset+i+j*step]] = [v[offset+i+j*step] + [entry_temp[j]]

fin pourfin pour

4.5.3 Type1La seule spécificité du residue de type 1 est là aussi la lecture des partitions. Dans ce residue, les valeurs

d’un même canal sont codées dans l’ordre dans les vecteurs de VQ comme suit.

vecteur de residue original: [0 1 2 3 4 5 6 7]codebook dimensions = 8 encodé comme: [0 1 2 3 4 5 6 7]codebook dimensions = 4 encodé comme: [0 1 2 3], [4 5 6 7]codebook dimensions = 2 encodé comme: [0 1], [2 3], [4 5], [6 7]codebook dimensions = 1 encodé comme: [0], [1], [2], [3], [4], [5], [6], [7]

Là aussi le décodage produit [n] valeurs dans le vecteur [v] en commençant à la case [offset]. Cesvaleurs sont lues de la manière suivante.

[n] = [residue_partition_size][v] = vecteur de sortie[offset] décalage dans le vecteur [v]

Page 40: Projet Logiciel en C - Ensimag

40 CHAPITRE 4. LECTURE D’UN FLUX VORBIS

[i] = 0tant que ([i] < [n]) faire[entry_temp] = lire le vecteur à partir du paquet en utilisant le codebook

courant en mode VQpour [j] dans 0 .. [codebook_dimensions - 1] faire[v[offset+i]] = [v[offset+i]] + [entry_temp[j]]incrémenter [i] de 1

fin pourfin tant que

4.5.4 Type 2La dernier type de residue est legèrement différent des deux autres en ce sens que la différence ne réside

pas uniquement dans la manière de lire les partitions.En effet, ce type de residue code l’ensemble des canaux en un seul canal entrelacé. On peut donc voir

ce type de residue comme un residue de type 1 avec un vecteur de taille ch*n2Une autre spécificité de ce residue vient de sa manière de gérer les marquages ’do_not_decode’. Il n’y

a aucun décodage à faire si l’ensemble des canaux est marqué.On finit par désentrelacer les valeurs.

1. si (do_not_decode[0 .. [ch-1]] == vrai) alorsproduire un vecteur de taille n2*ch de zéro et passer à 3

fin si

2. Décoder un residue de type 1 pour un canal de taille n2*ch

3. pour [i] dans 0 .. [n - 1] fairepour [j] dans 0 .. [ch - 1] fairevecteur de sortie [j] élément [i] = [v[i*ch + j]]

fin pourfin pour

Page 41: Projet Logiciel en C - Ensimag

Chapitre 5

l’API fournie

Ce chapitre présente une description module par module des structures de données et des fonctions qu’ilvous faudra implanter dans le projet.

5.1 Informations GlobalesLe type sample_t est utilisé pour représenter les PCM. Dans la version qui vous est proposée, les

échantillons sont des nombres flottants, c’est à dire des double. Cette section vous présente les différentesstructures qui sont utilisées dans tout le décodeur, car elles contiennent l’état du décodeur.• vorbis_stream_t :

typedef struct vorbis_stream vorbis_stream_t;struct vorbis_stream {

vorbis_codec_t *codec;

pcm_handler_t *pcm_hdler;vorbis_io_t *io_desc;

};

La première structure utilisée transversalement dans le décodeur Vorbis, vous permettra d’avoir accésaux trois informations essentielles, à savoir :

– la configuration du décodeur via codec, remplie lors de la lecture des différentes en-têtes.– le module qui réalise les opérations de lecture dans le flux grâce à io_desc– la production des échantillons PCM, en utilisant l’API proposée au travers de pcm_handler.Cette structure n’est en aucun cas à étendre, elle garantit (avec la suivante) la bonne compatibilité des

modules fournis avec ceux que vous developperez.• vorbis_codec_t :

typedef struct vorbis_codec vorbis_codec_t;struct vorbis_codec {

/** Identification header content*/uint32_t vorbis_version;uint8_t audio_channels;uint32_t audio_sample_rate;uint32_t bitrate_maximum;uint32_t bitrate_nominal;uint32_t bitrate_minimum;uint16_t blocksize[2];

41

Page 42: Projet Logiciel en C - Ensimag

42 CHAPITRE 5. L’API FOURNIE

/** Comment header infos*//* Skipped */

/** Setup header content*/codebook_setup_t *codebooks_desc;floors_setup_t *floors_desc;residues_setup_t *residues_desc;mappings_setup_t *mappings_desc;window_modes_setup_t *modes_desc;

};

La seconde structure, vorbis_codec rassemble les informations glanées lors de la lecture des entêtes.Ces informations correspondent exactement aux informations lues dans la première en-tête pour le premiergroupe. Le dernier groupe est l’ensemble des structures synthétiques produites lors de la lecture de latroisème en-tête. Les types de ces différentes structures vous sont présentées dans les différents modulessuivant.

5.2 Module Time domain transform (time_domain)Ce module est responsable de la gestion des transformées en domaine temporel. Cette partie de la norme

Vorbis est une extension possible pour les futures versions de la norme, mais n’est pas utilisée aujourd’hui,puisque la MDCT est utilisée systématiquement.

5.2.1 Fonctions• Lecture de l’entête :

int time_domain_transforms_setup_init(vorbis_stream_t *stream);

La fonction time_domain_transforms_setup_init est responsable uniquement de lire la partie de l’en-tête 3 qui est prévue pour cette extension (cf 4.2.3.b).

5.3 Module Mode (mode)

5.3.1 Structure• window_modes_setup_t :

typedef struct window_modes_setup window_modes_setup_t;struct window_modes_setup{

uint8_t mode_count;window_mode_t *modes;

uint8_t mode_code_nbbits;};

La structure window_modes_setup_t regroupe l’ensemble des descripteurs de mode utilisés dans le fluxVorbis en cours de décodage. Cette structure contient donc un tableau de modes (modes) de mode_countcases. Ce chiffre n’étant connu qu’à la lecture du flux, le tableau est alloué dynamiquement.

Pour finir cette structure contient un champ supplémentaire qui représente le nombre de bit nécessairepour coder (dans les paquets audio) l’identifiant du mode utilisé (cf ilog).• window_mode_t :

Page 43: Projet Logiciel en C - Ensimag

5.4. MODULE MAPPING (MAPPING) 43

typedef struct window_mode window_mode_t;struct window_mode{

uint8_t blockflag;uint16_t windowtype; /* SBZ */uint16_t transformtype; /* SBZ */mapping_t *mapping;

};

La structure window_mode_t décrit le mode de la fenêtre courante. Cette structure contient donc unchamp qui définit la taille de la fenêtre (blockflag).

Les deux champs suivant (windowtype et transformtype) sont réservés pour des extensions futures deVorbis, ils sont donc toujours égaux à zéro, sinon le descripteur de mode n’est pas valide.

Le dernier champ donne le mapping du mode courant (mapping). Il s’agit d’un pointeur sur le descripteurde mapping (mapping_t *).

5.3.2 Fonctions• Allocation et libération :

int window_modes_setup_init(vorbis_stream_t *stream, window_modes_setup_t **pset);

void window_modes_free(window_modes_setup_t *set);

Les deux fonctions exportées par le module mode sont des fonctions de gestion de l’ensemble des modes.En effet, window_modes_setup_init permet la lecture des modes dans l’entête 3 (cf 4.2.3 paragraphe mode).Cette focntion prend donc en argument un stream pour réaliser les opérations de lecture dans le flux vorbis.Cette fonction est en charge de l’allocation et de l’initialisation de la structure window_modes_setup_t. Lastructure allouée sera renvoyée par le second argument, un pointeur sur le pointeur qui permettra d’accéderà ce window_modes_setup_t dans le reste de l’application, donc un window_modes_setup_t ** (pset).

La seconde fonction permet tout simplement de libérer la mémoire alouée par l’opération précédente.window_modes_free prend donc en argument un pointeur sur la structure window_modes_setup_t à libérer(set).

5.4 Module Mapping (mapping)

5.4.1 Structure• mappings_setup_t :

typedef struct mappings_setup mappings_setup_t;

struct mappings_setup{

uint8_t mapping_count;mapping_t **maps;

};

La structure mappings_setup_t regroupe les descriptions de mapping. À l’image de modes_setup_t,cette structure contient donc deux champs, le tableau de descripteurs (maps) et la dimension de ce tableau(mapping_count).• mapping_t :

typedef struct mapping mapping_t;

struct mapping{

Page 44: Projet Logiciel en C - Ensimag

44 CHAPITRE 5. L’API FOURNIE

int type;int id;

int (*decode)(vorbis_stream_t *stream, mapping_t *map,vorbis_packet_t *data);

void (*free)(mapping_t *map);

};

La structure mapping_t est le descripteur générique de mapping. Même si la norme Vorbis ne comptequ’un type de mapping à l’heure actuelle (le type 0), la norme reste extensible de ce point de vue, et lareprésentation doit donc respecter cette possibilité. Ce type générique contient donc un identificateur detype (type) et un identifiant (id) correspondant à l’ordre dans le tableau des descripteurs. Les deux autreschamps sont des pointeurs sur les fonctions spécifiques du type réel du mapping courant, pour décoder lepaquet courant (au regard des informations du mapping courant) (decode) et de libération de la mémoire(free). Cet usage permet d’offrir aux modules utilisant le module mapping de toujours utiliser la mêmefonction quelque soit le type de mapping, et ce sans payer le prix d’un aiguillage dépendant du champtype. Les pointeurs sur fonction, même s’ils peuvent paraître un peu barbares, sont une technique couranteutilisée en C. Sans rentrer dans les détails, la ligne void (*free)(mapping_t *map) définit une fonctionqui ne renvoie rien (void), et qui prend en argument un mapping_t * (map).• mapping_type0_t :

typedef struct mapping_type0 mapping_type0_t;

struct mapping_type0{

mapping_t base;

uint8_t submaps;uint8_t coupling_steps;

uint8_t *magnitude;uint8_t *angle;

uint8_t *mux;

floor_t **submap_floor;residue_t **submap_residue;

};

Le type mapping_type0_t correspond à la spécialisation de la structure mapping_t pour le type 0 demapping. Cette structure contient donc en premier champ la structure générique présentée précédement.

On notera que puisqu’elle est placée en première position dans la structure, un pointeur sur une structurede type mapping_type0_t est aussi un pointeur sur une structure de type mapping_t. On utilise donc cettebonne propriété pour peupler le mappings_setup_t de mapping_t * même si la structure est effectivementplus grande en mémoire. Pour toutes les fonctions du type 0, qui auront besoin des informations supplé-mentaires, on pourra récupérer la structure d’origine en utilisant simplement le cast du C. Il s’agit d’unemanière de faire un héritage en C.

Les autres champs décrivent la configuration du mapping courant. Le premier champ, submaps, donnele nombre de submaps dans ce mapping, et le second, coupling_steps, le nombre d’étape de couplage.

On retrouve ensuite deux tableaux, magnitude et angle, qui sont de taille coupling_steps et qui défi-nisse les indices des canaux qui représentent l’amplitude et l’angle pour chaque couplage.

Le champ mux donne, pour chaque canal, le submap utilisé. Il s’agit donc d’un tableau ayant pourdimension le nombre de canaux.

Pour finir les champs submap_floor et submap_residue donnent respectivement pour chaque submap-ping le floor et le residue devant être utilisé pour décoder le paquet courant. Il s’agit donc de tableaux de

Page 45: Projet Logiciel en C - Ensimag

5.4. MODULE MAPPING (MAPPING) 45

taille submaps de floors et residues qui sont obtenus par le biais de pointeur sur les structures floor_t etresidue_t.

5.4.2 Fonctions• Allocation et libération :

int mappings_setup_init(vorbis_stream_t *stream,mappings_setup_t **pmap);

void mappings_free(mappings_setup_t *map);

Les deux premières fonctions exportées par le module mapping sont les fonctions de gestion de l’en-semble des descripteurs de mapping. Il s’agit des fonctions mappings_setup_init et mappings_free.

La fonction mappings_setup_init effectue l’allocation de la structure de type mappings_setup_t, l’ini-tialise en lisant les informations de configurations de l’entête 3 et la renvoie par le biais du pointeur depointeur (comme c’est le cas pour modes_setup_init. Cette fonction prend aussi en argument un streamqui permettra les opérations de lecture du paquet d’entête, et d’avoir accès aux descripteurs de floor et deresidue. Cette fonction implante donc la partie générale présentée au paragraphe mapping du 4.2.3

La seconde fonction, mappings_free, permet tout simplement de faire le ménage de la mémoire allouéepar la fonction précédente.• Utilisation des mappings :

int mapping_decode(vorbis_stream_t *stream, mapping_t *map,vorbis_packet_t *data);

La fonction mapping_decode permet, à partir d’un mapping donné par l’argument map, de décoder leflux Vorbis d’un paquet. Vous remarquerez que le pointeur de fonction de la structure mapping_t peut vouspermettre d’être plus efficace lors de l’implémentation de cette fonction, en vous évitant tout un tas de testslongs et fastidieux pour appeler la fonction de décodage spécifique au type de mapping.• Fonction spécifiques au Type 0 :

int mapping_type0_hdr_decode(vorbis_stream_t *stream, int id,mapping_t **pmap);

void mapping_type0_free(mapping_t *mapping);

Ces trois fonctions sont des fonctions spécifiques au mapping de type 0. La premièremapping_type0_hdr_decode permet l’allocation et l’initialisation d’un mapping de type 0 par décodage del’entrée dans l’entête. En gros dans cette fonction on effectura l’allocation d’une structure mapping_type0_tdont on fera passer le pointeur pour un pointeur de type mapping_t * en utilisant le cast du C. Cette fonc-tion prend donc en argument un stream et un pointeur sur le pointeur qui recevra l’adresse de la structure(castée en mapping_t *). Il faut bien penser lors de l’implantation de cette fonction à initialiser les champsde la structure de base, le type, l’identifiant et surtout les deux pointeurs de fonctions.

La seconde fonction peuplera le champ free.

int mapping_type0_decode(vorbis_stream_t *stream, mapping_t *map,vorbis_packet_t *data);

La dernière fonction, mapping_type0_decode, est la fonction qui devra être utilisée pour initialiser lepointeur de fonction decode. Cette fonction est donc en charge de réaliser les tâches décrites au 4.3.1.cdonc les paragraphes Floor, Non-zero propagate et Residues ainsi que le decoupling présenté au 4.3.2.Pour réaliser les parties concernant les floors et residues, elle utilisera les modules floor et residue. Lesarguments de cette fonctions sont donc un stream pour les opération de lecture dans le paquet, le mappingcourant (map) et enfin un pointeur sur une structure de type vorbis_packet_t qui contient les tampons dedécodage.

Page 46: Projet Logiciel en C - Ensimag

46 CHAPITRE 5. L’API FOURNIE

5.5 Module Floor (floor)

5.5.1 Structure• floor_data_t :

typedef struct floor_data floor_data_t;struct floor_data{

int type;int occ;

};

La structure floor_data_t sert de base pour les structures de travail des différents types de floor. Eneffet, chaque type de floor requiert des éléments différents lors de leur manipulation, et l’allocation de ceséléments peut vite se révéler compliquée. Cette structure permet d’allouer les éléments une bonne foispour tout pour chaque type, indépendemment du nombre de configurations de floor. On y retrouve juste unidentifiant de type (type) et un nombre d’occurence (occ). Ce dernier champ permet de recencer le nombrede floor du type correspondant (ie. le nombre de configurations qui utilise ce type de floor). On imagineraaisément que dans le cas où cette valeur est nulle, aucune allocation de zone de travail supplémentaire n’estnécessaire.• floor_t :

typedef struct floor floor_t;struct floor{

int type;int id;

int (*decode)(vorbis_stream_t *stream, floor_t *floor_cfg ,sample_t *v, uint16_t v_size);

void (*free)(floor_t *floor);

};

Les floors peuvent être de deux types différents dans la norme actuelle Vorbis. La structure floor_tcontient les informations basiques génériques (indépendants du type de floor). On y retrouve un type

(type) et un identifiant (id) correspondant à l’ordre dans la configuration. Les deux derniers champs sontdes pointeurs de fonctions, permettant de décoder les données des paquets audio correspondant au floor(decode) et de libérer la structure du floor une fois l’ensemble du décodage du flux terminé free.decode est donc un pointeur sur la fonction spécifique au type du floor en question qui réalisera le

décodage du paquet audio et la synthèse de la courbe à gros grain. Cette fonction utilise un paramètrestream pour réaliser les opérations de lecture dans le paquet, un paramètre qui correspond à la configurationdu floor courant floor_cfg et enfin un vecteur de sortie v et la taille à décoder v_size (correspondant à N

2 ).Concernant l’opération free, elle réalise la libération de la mémoire de la structure de floor pointée par

l’argument floor, cette opération est elle aussi dépendante du type de floor.• floors_setup_t :

typedef struct floors_setup floors_setup_t;struct floors_setup {

uint8_t floor_count;

floor_t **floors;

floor_data_t *data0;floor_data_t *data1;

};

Page 47: Projet Logiciel en C - Ensimag

5.6. MODULE FLOOR TYPE 1 (FLOOR1) 47

La structure de type floor_setup_t rassemble l’ensemble des informations concernant la configurationdes différents floors du flux vorbis lu. Le champ floor_count donne donc le nombre de configurations defloor présentes dans le tableau floors. Ce tableau est donc constitué de floor_count pointeurs sur desstructures de type floor_t. Les éléments data0 et data1 de type floor_data_t * sont des pointeurs sur leszones de travail partagées utilisables par les floors de type respectifs 0 et 1 pour les opérations de décodagedes packets audio et la génération de la courbe gros grain. Il faut noter qu’un tel usage n’est pas obligatoire,vous pourrez opter pour l’allocation en pile(c’est à dire sans utiliser de pré-allocation), mais ceux qui optentpour l’utilisation de zones de travail partagées (plus compliquée, mais plus propre et plus efficace) ont lapossibilité de le faire.

5.5.2 Fonctions• Allocation et libération :

int floors_setup_init(vorbis_stream_t *stream, floors_setup_t **pset);

void floors_free(floors_setup_t *set);

• Utilisation des floors :

int floor_decode(vorbis_stream_t *stream, floor_t *floor, sample_t *v,uint16_t v_size);

Le module floor est composé de trois fonctions :– La première permet d’initialiser un floor_setup_t par la lecture de la section correspondante dans

l’en-tête 3 du flux vorbis. La structure floor_setup_t est allouée et initialisée par cette fonction. Onlui passe pour celà un pointeur sur une variable pset qui est de type pointeur de floor_setup_t, d’oùun floor_setup_t **. Le premier argument est nécessaire pour avoir accès aux structures nécessairesà la lecture du flux. Cette fonction s’affranchit donc de la partie décrite au paragraphe floor du 4.2.3

– La seconde fonction, floors_free, a pour unique objectif de faire le ménage dans l’ensemble desstructures allouées à l’initialisation du floor_setup_t *set.

– La dernière fonction, floor_decode, est responsable du décodage de la partie floor des paquets audioVorbis. Cette fonction prend en paramètre un stream pour les mêmes raisons que précédement. Le se-cond paramètre est un pointeur sur le floor à utiliser pour le paquet courant, on aura récupéré ce floorgrâce au mapping. Le deux derniers arguments sont simplement le vecteur dans lequel la fonction doitproduire la courbe de gros grain et la taille devant être produite. Elle fera appelle à la fonction spéci-fique au type de floor courant grâce au pointeur de fonction de la structure floor_t. La valeur de re-tour de cette fonction indique si un floor est inutilisé en renvoyant la valeur VBS_FLOOR_UNUSED.

5.6 Module Floor type 1 (floor1)

5.6.1 StructureLe module floor1 est constitué de deux structures de données :• floor_type1_t :

#define FLOOR_TYPE1 1

typedef struct floor_type1 floor_type1_t;

struct floor_type1 {

floor_t base;

/* Your additional fields */

Page 48: Projet Logiciel en C - Ensimag

48 CHAPITRE 5. L’API FOURNIE

};

La structure floor_type1_t contient l’ensemble des informations de configurations spécifiques au floorde type 1. Elle est donc une extension du type générique floor_t. Cette extension est faite de la manièreillustrée dans le module mapping. Nous ne vous fournissons pas ce squelette de structure. A vous de l’im-plémenter et de l’étendre selon les besoins de votre implémentation du module.• floor_type1_data_t :

typedef struct floor_type1_data floor_type1_data_t;struct floor_type1_data{

floor_data_t base;

uint16_t max_points;

/* Your buffers here */

};

La structure floor_type1_data_t est, elle, une extension de la structure de travail de base floor_data_t. On retrouve donc, en plus du champ base, un champ qui permet de connaître la taille maximum des zonesde travail utilisées dans l’ensemble des floor de type 1, en gros il s’agira du maximum des valeurs de valuesvu au 4.4.1.

La structure est elle aussi une extension de type “par héritage”, mais pour le type floor_data_t. Ce typevous permettra de placer l’ensemble des zones de travail utiles aux floor de type 1 pour décoder un paquetaudio et faire la synthèse de la courbe gros grain (par exemple Y_final, . . .). Attention, cette structure n’arien d’obligatoire, mais permettra à ceux qui opteraient pour une autre méthode que l’allocation en pilede le faire, comme précisé précédemment. Une fois encore, le squelette ne vous est pas fourni. À vous del’implémenter et de l’étendre si vous décidez de l’utiliser.

5.6.2 Fonctions• Fonctions spécifiques au floor_type1_t :

int floor_type1_hdr_decode(vorbis_stream_t *stream, int id,floor_t **pfloor, floor_data_t *data);

int floor_type1_decode(vorbis_stream_t *stream, floor_t *floor_cfg ,sample_t *v, uint16_t v_size);

Le module floor1 est donc composé de 4 fonctions. La première permet la création d’unfloor_type1_t à partir de lectures dans l’entêtes 3 (cf 4.4.1). Cette fonction est donc appelée dans la fonc-tion floor_hdr_decode du module floor, pour permettre les actions spécifiques au type 1. Cette fonction abesoin d’un stream pour lire dans le flux, de l’identifiant du floor à lire, d’un pointeur sur le pointeur de typefloor_t qui permettra au module floor de conserver une trace de ce floor et enfin d’un pointeur sur unezone de travail de type floor_type1_data_t (préalablement initialisée, et si vous utilisez cette méthode).

Le mécanisme d’héritage présenté précédemment impose certaines contraintes. C’est cette fonction quidoit allouser la structure pfloor, puisque cette structure sera de type floor_type1_t, même si le restede l’application ne le considérera que comme un floor_t. N’oubliez pas d’initialiser tous les champs de lastructure de base, pour éviter les (mauvaises) surprises. En particulier, n’oubliez pas de fournir les fonctionsde traitement dans la structure. Par exemple, le pointeur decode devra être initialisé avec une fonctionayant la signature adéquate, et qui permette le décodage des floors de type 1, c’est-à-dire une fonction quiressemble étrangement à floor_type1_decode. . .

Dans le même esprit, vous pouvez vous interroger sur le paramètre data, qui n’est pas vraiment unfloor_data_t.• Fonctions spécifiques au floor_type1_data_t :

Page 49: Projet Logiciel en C - Ensimag

5.7. MODULE FLOOR TYPE 0 (FLOOR0) 49

int floor_type1_data_new(floor_data_t **pfl_data ,uint16_t *blocksize);

int floor_type1_data_allocate(floor_data_t *fl_data);

void floor_type1_data_free(floor_data_t *fl_data);

Les trois dernières fonctions servent à manipuler la zone de travail dédiée aux floors de type 1.La première, floor_type1_data_new effectue une allocation simple d’une structure de type

floor_type1_data_t que l’on fait passer pour un floor_data_t, par le pointeur de pointeur pfl_data.Cette initialisation utilise uniquement un pointeur sur le tableau des tailles de fenêtres.

La seconde fonction permet l’allocation des tampons internes de la structure (une fois que l’on a peupléle champs max_points bien sûr au cours des décodage des différentes configurations de floor de type 1),elle sera évidement appelée (si vous optez pour ce choix) après avoir décodé l’ensemble des configurationsde floor dans l’en-tête.

Enfin la dernière permet de libérer la mémoire de la structure pointée par l’argument.

5.7 Module Floor type 0 (floor0)

5.7.1 Structure

Le module de floor de type 0 comprend lui aussi plusieurs structures. Elles ne seront pas décrites icipour deux raisons principales :

– il ne s’agit pas d’un type de floor utilisé fréquement, il n’y a donc aucun intérêt à coder ce module audépart.

– vous devrez donner libre cours à votre imagination pour définir ces structures si vous en arriver àimplanter ce module, en vous inspirant bien sûr du module Floor 1 (ce module est un module super-bonus, vous ne croyiez pas qu’on allait vous macher le travail !)

5.7.2 Fonctions

• Fonctions exportées par le module floor0 :

int floor_type0_hdr_decode(vorbis_stream_t *stream, int id,floor_t **pfloor, floor_data_t *data);

int floor_type0_data_new(floor_data_t **pfl_data ,uint16_t *blocksize);

int floor_type0_data_allocate(floor_data_t *fl_data);

void floor_type0_data_free(floor_data_t *fl_data);

Le module Floor 0 exporte donc le même type de fonction que le Floor de type 1 au module Floor. Pourla description, veuillez vous référer au module Floor 1.

5.8 Module : Dot Product (dot_product)

5.8.1 Fonctions

• Dot product :

int dot_product(sample_t **floors, sample_t **residues , int nb_chan,int n2);

Page 50: Projet Logiciel en C - Ensimag

50 CHAPITRE 5. L’API FOURNIE

La fonction dot_product effectue l’opération de produit case à case des échantillons en fréquence pro-duit grace au floor (floors) par les échantillons obtenus grace aux residues (residues). Cette opération està l’image du .* de Matlab. Les deux tableaux (floors et residues) sont de tailles nb_chan par n2.

5.9 Module Residues (residue)

5.9.1 Structure

• residues_setup_t :

typedef struct residues_setup residues_setup_t;struct residues_setup {

uint8_t residue_count;residue_t **residues;

};

La structure residues_setup_t rassemble l’ensemble des définition de residues du flux vorbis lu, dans letableau residues. Ce tableau est de dimension residue_count et est constitué de pointeur sur des structuresde type residue_t.• residue_t :

typedef struct residue residue_t;struct residue {

int type;int (*decode)(vorbis_stream_t *stream, residue_t *resid, int ch,

int64_t N2, sample_t **v, uint8_t *do_not_decode);void (*free)(residue_t *res);

};

La structure residue_t permet d’identifier les différents residues utilisés dans le flux vorbis décodé.Cette structure est composée d’un champ identifiant son type. Dans la norme actuelle, il existe 3 types deresidues (0, 1 et 2), mais cette norme est extensible, et ce nombre n’est pas figé.

Les deux derniers champs sont des pointeurs de fonction qui permettent la lecture du residue dans lepaquet audio vorbis (decode) et de libérer la structure courante (free).

5.9.2 Fonctions

Le module residue n’exporte que trois fonctions aux autres modules.• Allocation et libération :

int residues_setup_init(vorbis_stream_t *stream,residues_setup_t **pres);

void residues_free(residues_setup_t *set);

La première est une fonction de décodage de la partie residue de l’entête 3 (4.2.3.d). Cette fonction,residues_setup_init, alloue et initialise la structure de type residues_setup_t, peuple le champ residueset la renvoie par le biais du pointeur de pointeur pset.

NB : Cette fonction est largement similaire à la fonction floors_setup_init, inspirez vous en.La fonction residues_free effectue l’opération contraire, à savoir, elle libère la mémoire allouée par

l’opération précédente.• Utilisation :

Page 51: Projet Logiciel en C - Ensimag

5.10. MODULE VORBIS I/O (VORBIS_IO) 51

int residue_decode(vorbis_stream_t *stream, residue_t *residue, int ch,uint16_t n2, sample_t **v, uint8_t *do_not_decode);

La dernière fonction publique du module, residue_decode, est responsable du décodage dans les pa-quets audio des residues. Cette fonction prend en paramètre un stream principalement pour les opérationsde lecture dans le flux. Le second paramètre est un pointeur sur la configuration de residue à utiliser pourobtenir les coefficients permettant de calculer la courbe de grain fin. Les deux arguments suivants permettentde définir la taille des residues à décoder, ch donne le nombre de canaux à décoder (il peut être inférieurau nombre réel de canaux) alors que n2 donne leur taille. L’argument suivant v, correspond aux vecteursqui permettront de conserver les residues décodés dans le paquet. Le dernier argument, do_not_decode,correspond au vecteur de fanions correspondant aux canaux n’ayant pas de residue à décoder. Cette fonc-tion utilisera le pointeur de fonction du type residue_t pour sélectionner la fonction spécifique au type deresidue.

5.10 Module Vorbis I/O (vorbis_io)Le module vorbis_io est l’interface de Vorbis avec son conteneur. Il réalise donc l’ensemble des

opérations de lecture, de gestion de paquets et d’informations sur le conteneur.

5.10.1 Structure• vobis_io_t :

typedef struct __vorbis_io_t vorbis_io_t;

struct __vorbis_io_t {logical_stream_t *ogg_desc;uint64_t buffer;int32_t offset; /* bit offset, represents the number of read

bits in the current buffer */uint32_t nbits; /* number of non-read bits in the buffer */int32_t status; /* current status of the stream */

uint64_t readbits;};

Ce type est le type de base du module, qui contient donc toutes les informations nécessaires à sonbon fonctionnement. Le champ ogg_desc contient le descripteur de flux logique Ogg qui représente le fluxVorbis en cours de décodage. Le champ buffer est le tampon de lecture. La lecture bit à bit dans un fichiern’est en effet pas immédiate en C. Les fonctions offertes par les bibliothèques ne permettent de lire dans lesfichiers que des nombres entiers d’octets. L’utilisation du buffer permet donc de stocker les données en coursde traitement. Les champs offset et nbits permettent de stocker l’état courant de lecture en conservant lenombre de bits déja lus dans le tampon courant, et le nombre de bits valides restants. Le champ statuspermet de stocker l’état courant du flux. Finalement le champ readbits sert à stocker le nombre de bits lusdans le paquet courant (et non pas dans le tampon). Il est principalement utilisé à des fins de contrôle descas d’erreur.

5.10.2 Fonctions• Allocation et libération :

vorbis_io_t *vorbis_io_init(logical_stream_t *ogg_desc);

void vorbis_io_free(vorbis_io_t *io);

Les deux premières fonctions du module vorbis_io permettent l’allocation et l’initialisationd’une structure de type vorbis_io_t (vobis_io_init) et sa libération (vorbis_io_free). La fonction

Page 52: Projet Logiciel en C - Ensimag

52 CHAPITRE 5. L’API FOURNIE

vorbis_io_init prend en argument un pointeur sur une structure logical_stream_t fournie par OGG,et renvoie un pointeur sur la structure fraîchement allouée et initialisée. La fonction vorbis_io_free, quantà elle, prend en argument un pointeur sur la structure à désallouer.• Lecture des bits :

int vorbis_read_nbits(uint32_t nb_bits, uint32_t *dst,vorbis_io_t *io, uint32_t *p_count);

La fonction principale du module vorbis_io est la fonction qui permet à l’ensemble des autres mo-dules de vorbis d’effectuer des opérations de lecture au bit. Cette fonction, vorbis_read_nbits, prend enargument un nombre de bits à lire (nb_bits – le maximum accepté est de 32 bits), un pointeur sur la zonedans laquelle la valeur sera stockée (un entier de 32bits, dst), un pointeur sur la structure vorbis_io_tcontenant les informations permettant la lecture (io) et enfin un pointeur sur un entier qui contiendra lenombre de bits réellement lus.• Gestion des paquets :

int vorbis_io_next_packet(vorbis_io_t *stream);

int64_t vorbis_io_limit(vorbis_io_t *io);

Les deux dernières fonctions sont des fonctions de gestion de conteneur. La première,vorbis_io_next_packet, permet de passer au paquet suivant dans le flux.

La seconde fonction permet de récupérer une information mise à la disposition du codec par le conte-neur (OGG en l’occurence). Cette information est la limite du nombre d’échantillon à produire grâce auogg_packet_position (cf 5.12.2.a).

5.11 Module Ogg Core (ogg_core)La gestion du conteneur Ogg a été divisé en deux modules : ogg_core et ogg_packet. Le module

ogg_core s’occupe du désentrelacement des pages des différents flux logiques.

5.11.1 Structures5.11.1.a Structures visibles des modules extérieurs à Ogg

Deux types de structures sont visibles par les modules utilisant les modules Ogg. Ces structures sontdéfinies dans le fichier ogg.h• physical_stream_t :

typedef struct _physical_stream_t physical_stream_t;struct _physical_stream_t {

uint32_t num_streams;logical_stream_t * list;

};

Cette structure représente un flux physique, elle contient 2 champs. Le premier – num_streams – indiquele nombre de flux logiques contenus dans le flux physique. Le deuxième – list – est une liste chainée desflux logiques, c’est un pointeur vers le premier flux de la liste.• logical_stream_t :

typedef struct _ogg_codec_t ogg_codec_t;typedef struct _logical_stream_t logical_stream_t;enum _ogg_codec_t {unknown,vorbis

};

Page 53: Projet Logiciel en C - Ensimag

5.11. MODULE OGG CORE (OGG_CORE) 53

struct _logical_stream_t {logical_stream_t * next;uint32_t stream_id;ogg_codec_t codec;

};

Cette structure représente un flux logique, elle contient 3 champs. next est un pointeur vers le prochainélément de la liste (NULL si c’est le dernier élément). stream_id est l’identifiant du flux logique au sein duflux physique. codec indique le format/codec du flux logique.

5.11.1.b Structures internes aux modules Ogg

Deux structures internes qui permettent l’interface entre les deux modules Ogg sont définies dans lefichier ogg_internal.h.• ogg_stream_t :

typedef struct _ogg_stream_t ogg_stream_t;typedef struct _ogg_packet_hdlr_t ogg_packet_hdlr_t;struct _ogg_stream_t {

logical_stream_t lstream;physical_stream_t * pstream;ogg_packet_hdlr_t * packet;ogg_page_hdr_t * header;uint8_t * table;uint8_t * data;

};

ogg_stream_t est une extension de la structure logical_stream_t. Elle contient des champs suplémen-taires nécessaires pour la lecture du flux mais qui n’ont pas besoin d’être vus par les autres modules.pstream est un pointeur vers le flux physique auquel appartient le flux logique. packet est un pointeur

vers une structure qui doit être définie dans le module ogg_packet.header, table et data sont des pointeurs vers les différents éléments de la page en cours de lecture du

flux logique. header est un pointeur vers l’en-tête (sans la page des segments) définie ci-dessous. table estun pointeur vers la table des segments (un tableau contenant la taille de chacun des segments de la page).data est un pointeur vers les données de la page.

Les 3 champs doivent être cohérents entre eux, table et data ne peuvent être à NULL que si la pagecourante ne contient aucun segment.• ogg_page_hdr_t :

typedef struct _ogg_page_hdr_t ogg_page_hdr_t;struct _ogg_page_hdr_t {

uint8_t magic[4];uint8_t version;uint8_t type;int64_t gran_pos;uint32_t stream_id;uint32_t page_id;uint32_t crc;uint8_t num_segs;

} __attribute__((__packed__));

ogg_page_hdr_t est une en-tête de page sans sa table des segments. Celle-ci correspond exactement(ordre et taille des champs) à celle présente dans le fichier de manière à permettre une copie directe ducontenu du fichier vers cette structure1. L’attribut __attribute__((__packet__)) permet de spécifier aucompilateur qu’aucun espace libre ne doit être laissé entre les champs (aucun alignement ne sera fait enmémoire).

1Dans le cas où l’architecture utilisée est little-endian. Dans le cas contraire il faut en plus intervertir les octets des champs multi-octets

Page 54: Projet Logiciel en C - Ensimag

54 CHAPITRE 5. L’API FOURNIE

5.11.2 FonctionsCette section spécifie les fonctions que contient le module ogg_core. Trois sont utilisables par le pro-

gramme principal. Une est utilisable par le module ogg_packet.

5.11.2.a Interface avec le module main

int ogg_init (FILE *file, physical_stream_t **phy);int ogg_decode (logical_stream_t *stream, pcm_handler_desc_t *pcm);int ogg_term (physical_stream_t *phy);

• ogg_init :La fonction ogg_init permet d’allouer et d’intialiser un physical_stream_t ainsi que les

logical_stream_t qu’il contient à partir d’un fichier ouvert en lecture. Elle lit les premières pages afinde lister tous les flux logiques que contient le flux physique.ogg_init retourne 0 en cas de succès et un nombre négatif en cas d’erreur. En cas de succès le pointeur

vers le physical_stream_t dont l’adresse est passé en argument est positionné sur celui alloué.Chacun des logical_stream_t de la liste chainée renvoyée avec le physical_stream_t est en pratique

un ogg_stream_t. Comme le premier champ d’un ogg_stream_t est le logical_stream_t correspondant,leurs pointeurs sont identiques, ce qui permet cette manipulation.• ogg_decode :La fonction ogg_decode décode un flux logique au format vorbis. Le flux logique est passé en argu-

ment à travers un pointeur vers un logical_stream_t. Ce logical_stream_t doit être issu d’une listed’un physical_stream_t renvoyé par un appel à ogg_init. Le second argument est un pointeur vers unpcm_handler_desc_t qui sera utilisé par les modules vorbis pour traiter les échantillons décodés.ogg_decode retourne 0 en cas de succès et un nombre négatif en cas d’erreur.ogg_decode ne peut être appelée qu’une seule fois par physical_stream_t. Il n’est pas possible de

décoder plusieurs flux logiques d’un même flux physique.ogg_decode fait appel à la fonction decode_stream du module vorbis_main pour effectuer le décodage.

Un ogg_packet_hdlr_t aura été préalablement attaché au flux logique fourni à decode_stream afin queles modules vorbis puissent faire appels aux fonctions de lecture des paquets. Le ogg_packet_hdlr_t estdétaché une fois le décodage terminé. Le ogg_packet_hdlr_t est attaché et détaché grâce aux fonctions dumodules ogg_packet.• ogg_term : La fonction ogg_term permet de libérer toutes les zones mémoires liées au

physical_stream_t passé en argument ainsi que le physical_stream_t.ogg_term retourne 0 en cas de succès et un nombre négatif en cas d’erreur.ogg_term peut être appelée directement après l’appel à la fonction ogg_decode ou directement après

ogg_init si on ne veut décoder aucun flux.

5.11.2.b Interface avec le module ogg_packet

int ogg_get_next_page (ogg_stream_t *stream);

• ogg_get_next_page :La fonction ogg_get_next_page permet de mettre à jour les champs du ogg_stream_t passé en argument

avec la page suivante du flux logique. Seuls les champs header, table, data sont modifiés.Si il n’y a plus de page dans le flux logique, ogg_get_next_page retourne 1 et mets les 3 champs à

NULL. Sinon elle retourne 0 en cas de succès et un nombre négatif en cas d’erreur.

5.12 Module Ogg Packet (ogg_packet)ogg_packet permet de reconstituer les paquets d’un flux logique à partir de ses pages.Le module ogg_packet s’occupe uniquement de la reconstitution des paquets d’un flux logique. Il

contient deux fonctions utilisables par le module ogg_core et quatre fonctions utilisables par les modulesVorbis.

Page 55: Projet Logiciel en C - Ensimag

5.12. MODULE OGG PACKET (OGG_PACKET) 55

5.12.1 Structures• Le type ogg_packet_hdlr_t :

struct _ogg_packet_hdlr_t {/* A remplir en fonction de votre implantation */

};

Ce type est déclaré comme étant la structure struct~_ogg_packet_hdlr_t dans le fichierogg_internal.h. La définition de cette structure est à faire dans le module ogg_packet. Son contenu n’estpas spécifié et dépend de la manière choisie pour implanter ce module. Elle doit contenir toutes les infor-mations nécessaires à partager entre les appels aux différentes fonctions de ce module.

5.12.2 Fonctions5.12.2.a Interface avec les modules vorbis

Les fonctions de cette interface permettent aux modules vorbis d’accéder aux paquets du flux logique.Toutes ces fonctions prennent en argument un pointeur vers un logical_stream_t qui a été passé en argu-ment à vorbis_decode par la fonction ogg_decode. Ce logical_stream_t est en réalité le ogg_stream_tétendant le logical_stream_t.

int ogg_packet_read (logical_stream_t *lstream, uint8_t *buf, int nbytes);int ogg_packet_next (logical_stream_t *lstream);int ogg_packet_size (logical_stream_t *lstream);int ogg_packet_position (logical_stream_t *lstream, int64_t *position);

• ogg_packet_read :La fonction ogg_packet_read permet de lire nbytes octets du paquet courant du flux logique lstream.

les octets demandés sont placés à l’adresse indiquée par buf.La fonction retourne le nombre d’octets lus (et donc copiés dans buf). Ce nombre est idéalement égal à

nbytes, mais il peut y être inférieur dans le cas où le paquet ne contient plus assez d’octets.En cas d’erreur ogg\_packet\_read retourne un nombre négatif. Demander plus d’octets qu’il n’en

reste dans le paquet courant n’est pas une erreur. Une fois le paquet courant terminé (il ne reste plus d’octetsdedans), ce n’est pas non plus une erreur d’appeler ogg\_packet\_read, elle doit logiquement retourner 0puisqu’elle n’a pu lire aucun octet. C’est une erreur d’appeler cette fonction si le dernier paquet du flux aété sauté avec la fonction ogg_packet_next.• ogg_packet_next :La fonction ogg_packet_next permet de passer au paquet suivant. Tous les octets éventuellement restant

dans le paquet courant sont sautés. L’appel suivant de ogg_packet_read copiera les premiers octets dupaquet nouvellement sélectionné.ogg_packet_next retourne 0 en cas du succès, un nombre négatif en cas d’erreur et 1 si le paquet courant

est le dernier paquet du flux. Un appel à ogg_packet_next une fois le dernier paquet sauté est une erreur.• ogg_packet_position :La fonction ogg_packet_position permet d’accéder à la position absolue telle qu’elle apparaît dans les

en-têtes de page. Cette fonction doit indiquer la position qu’il y aura à la fin du paquet courant ou −1 sicelle-ci est inconnue. Comme la position indiquée dans l’en-tête d’une n’est valable que pour le dernierpaquet qui se termine sur la page, cette position inconnue pour les éventuels paquets le précédant dans lapage.

La position est indiquée en l’écrivant à l’adresse indiquée par le pointeur position passé en argument.Cette fonction retourne 0 en cas du succès et un nombre négatif en cas d’erreur. Quand le dernier paquet estsauté, la position indiquée doit être celle du dernier paquet.• ogg_packet_size :La fonction ogg_packet_size permet d’estimer la taille en octets du paquet courant, elle est surtout

utile pour le débogage afin de vérifier si le paquet entier à été lu.Elle retourne une estimation de la taille du paquet courant. Si le paquet est terminé elle doit retourner la

taille réelle. Si celui-ci ne l’est pas, la taille retournée doit être strictement suérieure au nombre d’octets lusjusqu’à présent dans le paquet.

Page 56: Projet Logiciel en C - Ensimag

56 CHAPITRE 5. L’API FOURNIE

5.12.2.b Interface avec le module ogg_core

Deux fonctions font l’interface de ce module avec le module ogg_core, elles permettent d’attacher etde détacher un ogg_packet_hdlr_t à un ogg_stream_t.

int ogg_packet_attach (ogg_stream_t *stream);int ogg_packet_detach (ogg_stream_t *stream);

• ogg_packet_attach :La fonction ogg_packet_attach permet d’attacher un ogg_packet_hdlr_t au ogg_stream_t passé en

argument. Cette fonction doit allouer et initialiser un ogg_packet_hdlr_t et remplir le champ packet duflux logique (ce champ doit être initialement à NULL).

Lors de l’appel de cette fonction la première page du flux logique doit avoir été mise dans les champsheader, table et data. Cette fonction doit initialiser le ogg_packet_hdlr_t de manière à ce que le paquetcourant soit le premier paquet du flux logique. Un appel à la fonction ogg_packet_read doit renvoyer lespermiers octets du premier paquet.

Cette fonction retourne 0 en cas de succès et un nombre négatif en cas d’erreur.• ogg_packet_detach :La fonction ogg_packet_detach permet de libérer le champ packet du ogg_stream_t passé en argu-

ment. Ce champ doit être mis à NULL ensuite.Cette fonction retourne 0 en cas de succès et un nombre négatif en cas d’erreur.

5.13 Module iMDCT (imdct)

5.13.1 Structure• imdct_t :

typedef struct imdct imdct_t;struct imdct {

int type;};

Le type imdct_t permet d’identifier le type de module iMDCT utilisé et de véhiculer les informationsnécessaires à l’application de la transformée.

NB : Le module qui vous est fourni implante une iMDCT rapide (fast-imdct), il est donc normal quevotre version soit (beaucoup) plus lente.

5.13.2 Fonctions• Initialisation et libération :

imdct_t *imdct_init(uint16_t *sizes);

void imdct_free(imdct_t *im);

La fonction imdct_init permet tout d’abord d’initialiser les données recquises par l’iMDCT. En gros,la version de base n’a besoin d’initialiser aucune données, mais des versions plus évoluées le doivent.L’argument de cette fonction est un pointeur sur un tableau contenant les tailles des deux types de fenêtres.Cette fonction renvoie un pointeur sur une structure de type imdct_t placée en zone mémoire allouée (parcette fonction).

La fonction imdct_free permet de nettoyer toutes les données initialisées lors de l’appel à imdct_init.• iMDCT :

int imdct(imdct_t *im, sample_t *fsamp, sample_t *tsamp,sample_t *filter, int mode);

Page 57: Projet Logiciel en C - Ensimag

5.14. MODULE : COMMON HEADER (COMMON_HEADER) 57

Enfin, la fonction imdct effectue l’opération de transformée en cosinus modifiée inverse. Le premierparamètre est un pointeur sur une structure de travail au préalable initialisée (imdct_init), le second cor-respond au tableau d’échantillon dans le domaine spéctral, tsamp est le tableau de sortie, il contiendra leséchantillons dans le domaine temporel. Le paramètre filter correspond à la forme de la fenêtre (produitepar le module envelope) et pour finir mode donne le type de fenètre (petite ou grande).

5.14 Module : Common Header (common_header)

5.14.1 Fonctions• Common Header :

int vorbis_common_header(vorbis_stream_t *stream);

La fonction vorbis_common_header effectue l’opération de décodage de la partie commune aux troisentêtes, les 7 octets contenant l’identification du type de paquet, du type de header et du flux (“vorbis”).Cette fonction prend en argument un stream et renvoie le type de header.

5.15 Module Header 1 (header_1)

5.15.1 Fonctions• Header 1 :

int vorbis_header1_decode(vorbis_stream_t *stream);

La fonction vorbis_header1_decode effectue l’opération de décodage de l’en-tête 1 du flux Vorbis, en-tête d’identification. Cette fonction ne fait que lire un paquet. Le flux doit donc être correctement alignéau moment de l’appel, et la fonction ne fera aucune opération sur le paquet autre que sa lecture. Toutes lesopérations de changement de paquet seront à faire au niveau de l’appelant.

5.16 Module Header 2 (header_2)

5.16.1 Fonctions• Header 2 :

int vorbis_header2_decode(vorbis_stream_t *stream);

La fonction vorbis_header2_decode effectue l’opération de décodage de l’en-tête 2 du flux Vorbis, en-tête de commentaire. Comme sa petite sœur, vorbis_header1_decode, cette fonction ne fait pas de change-ment de paquet.

5.17 Module Header 3 (header_3)

5.17.1 Fonctions• Header 3 :

int vorbis_header3_decode(vorbis_stream_t *stream);

La fonction vorbis_header1_decode effectue l’opération de décodage de l’en-tête 3 du flux Vorbis,en-tête de configuration (cf 4.2.3).

On effectue dans cette fonction que la lecture du paquet d’en-tête 3, il n’y a pas d’opération de change-ment de paquet faite dans cette fonction.

Page 58: Projet Logiciel en C - Ensimag

58 CHAPITRE 5. L’API FOURNIE

5.18 Module Vorbis main (vorbis_main)

5.18.1 Fonctions• Vorbis main :

int decode_stream(logical_stream_t *ogg_stream , pcm_handler_desc_t *pcm_hdler);

La fonction decode_stream gère la machine d’état globale du décodage du flux vorbis. En résumé,c’est cette fonction qui se charge de faire les initialisations nécessaires (aux différents moments – pré oupost lecture d’en-tête), qui lance les lecture d’en-têtes, en s’occupant des changements de paquets et quigère la lecture des différents paquets audio et leur changement de paquet. Cette fonction effectue enfin leslibérations nécessaires avant de rendre la main à l’appelant.

5.19 Module Helpers (helpers)

5.19.1 Fonctions• Fonctions utiles :

uint32_t ilog(int32_t val);

La fonction ilog a pour but de renvoyer le nombre de bits nécessaires pour coder la valeur passée enargument val.

uint32_t lookup1_values(uint32_t,uint32_t);

sample_t float32_unpack(uint32_t);

5.20 Module : Codebook (codebook)

5.20.1 Structure• codebook_setup_t :

typedef struct codebook_setup codebook_setup_t;struct codebook_setup {uint8_t nb_cb;codebook_t *codebooks;

};

La structure globale du module Codebook comporte seulement deux champs. Le premier nb_cb contientle nombre de codebooks inclus dans la structure. Le deuxième champ, codebooks, est le tableau qui com-prend tous les codebooks. Ainsi, codebooks[i] sera le codebook d’index i.• codebook_t :Les codebooks sont définis par le type codebook_t, qui comprend 4 champs.

typedef struct codebook codebook_t;struct codebook {codebook_tree_t *cb_root;uint32_t dimensions;uint32_t entries;uint32_t index;

};

Page 59: Projet Logiciel en C - Ensimag

5.20. MODULE : CODEBOOK (CODEBOOK) 59

Le premier champ cb_root est le noeud racine de l’arbre de Huffman associé au codebook. Les deux champssuivants, dimensions et entries contiennent les variables codebook_dimensions et codebook_entries dé-finies dans la section 4.2.3.a.• Structure d’arbre :Les trois derniers types servent à représenter la structure du codebook, c’est à dire l’arbre et le vecteur

de quantification.

typedef struct codebook_tree codebook_tree_t;typedef struct codebook_entry codebook_entry_t;typedef struct vq_vect vq_vect_t;struct codebook_tree {codebook_tree_t *left;codebook_tree_t *right;codebook_entry_t *leaf;

};struct codebook_entry {vq_vect_t *vq;uint32_t entry;

};struct vq_vect {uint32_t dim;sample_t *vector;

};

L’arbre de Huffman est un arbre binaire. La représentation de l’arbre se fait donc en indiquant, pour chaquenoeud, son noeud fils droit (right) et son noeud fils gauche (left). Si l’un de ces deux noeuds est vide, ilprend la valeur NULL. Si les deux noeuds fils sont vides, alors l’élément courant est une feuille (c’est-à-direun noeud terminal, dans lequel l’information est stockée). Dans ce cas, le champ leaf pointe sur la structurequi contient les valeurs associées à cette feuille. Dans tous les cas où le noeud n’est pas une feuille, leafdoit valoir NULL.

Les feuilles terminales contiennent soit le scalaire associé au mot de code entry, soit le vecteur dequantification vq. Le vecteur de quantification est représenté par un tableau d’échantillons vector, et par lataille de ce tableau dim qui correspond à la variable codebook_dimensions.

5.20.2 Fonctions• Allocation et libération :

uint32_t codebook_setup_init(vorbis_stream_t *stream, codebook_setup_t **pset);uint32_t codebooks_free(codebook_setup_t *cb_desc);

La fonction codebook_setup_init récupère les dictionnaires stockés dans le flux Vorbis stream, pourles stocker dans la structure des dictionnaires *pset. pset est un pointeur sur un pointeur sur une struc-ture codebook_setup_t pour permettre d’allouer le pointeur dans la fonction et le renvoyer à la fonctionappelante. La fonction utilise les algorithmes présentés dans la section 4.2.3.a, et stocke chacun des diction-naires ainsi construit dans le tableau contenu dans la structure. Les arbres de Huffman qui représentent lesdictionnaires sont stockés en utilisant la structure d’arbre codebook_tree_t.

La fonction codebooks_free libère la structure pointée par cb_desc. En sortie de fonction, le descripteurdes dictionnaires doit être complètement libéré, c’est à dire que le descripteur est libéré ainsi que l’ensembledes structures allouées dans le descripteur.• Utilisation du dictionnaire :

uint32_t codebook_translate(vorbis_stream_t *stream,codebook_t *book,codebook_entry_t** result);

coodebook_translate est la fonction qui permet d’utiliser les dictionnaires sur le flux Vorbis. stream estle flux décodé. Elle travaille avec le dictionnaire book, et lit le fichier jusqu’à trouver un mot de code valide

Page 60: Projet Logiciel en C - Ensimag

60 CHAPITRE 5. L’API FOURNIE

pour ce dictionnaire. Une fois trouvé, elle renseigne result avec l’entrée correspondante au mot de code.Pour vous faciliter le déverminage de votre code, la fonction codebook_translate vous est fournie dans unmodule séparé, le module codebook_read. Cependant, pour plus de cohérence, vous pouvez l’implémenterdans le module codebook complet.

5.21 Module de gestion des échantillons audios (pcm_handler)Ce module sert à traiter les échantillons audios décodés par Vorbis. Plusieurs sorties sont possibles pour

les échantillons : par exemple les stocker dans un fichier, ou les envoyer sur les hauts parleurs. Ce modulepermet de gérer plusieurs sorties. Notre version en propose 2 :

– "wav" : met les échantillons dans un fichier au format WAV,– "raw" : met les échantillons directement dans un fichier sans traitement.D’autres sorties vous seront peut-être proposées plus tard.

5.21.1 StructurePour ce module il vous est demandé de ne gérer qu’une seule sortie : de type WAV.• pcm_handler_t :

typedef struct _pcm_handler_t pcm_handler_t;struct _pcm_handler_t {

int (*init) (pcm_handler_t *hdlr, unsigned int sampl, unsigned int nchan);int (*process) (pcm_handler_t *hdlr, unsigned int num, int16_t **samples);int (*finalize) (pcm_handler_t *hdlr);

};

Cette structure représente un traitant d’échantillons qui est utilisé par les modules vorbis. Elle contient3 champs qui sont des pointeurs sur des fonctions qui sont spécifiées ci-dessous. Cette structure est déclaréedans le fichier pcm_handler.h.

Pour appeler ces fonctions, il faut que le premier argument donné soit le pointeur vers le pcm_handler_tqui contient le pointeur vers la fonction appelée. Ceci est illustré dans l’exemple ci-dessous :

pcm_handler_t * handler;

...

/* appel à la fonction init du traitant */handler->init(handler, ...);

• init :Cette fonction permet d’initialiser le traitant avec les paramètres du flux audio (fréquence d’échantillo-

nage et nombre de canaux). La taille d’un échantillon n’est pas paramétrable, seuls des échantillons signéssur 16 bits sont utilisés dans ce projet. Cette fonction doit être appelée une seule fois avant tout appel à lafonction process.init prend en argument le traitant d’échantillon, la fréquence d’échantillonage (le nombre d’échantillon

qui doivent lus par seconde pour chaque canal audio) et le nombre de canaux audios (1 pour du mono, 2pour de la stéréo, etc).init retourne 0 en cas de succès et un nombre négatif en cas d’erreur.• process :Cette fonction permet de traiter des échantillons audios. En plus du traitant, elle prend en argument le

nombre d’échantillons par canal, ainsi que les échantillons. Cette fonction peut être appelée autant de foisque nécessaire

Les échantillons sont donnés dans un tableau de tableaux. La première dimension est le canal, ladeuxième est le numéro de l’échantillon. Les échantillons sont ordonnés par ordre croissant dans le temps.process retourne 0 en cas de succès et un nombre négatif en cas d’erreur.• finalize :

Page 61: Projet Logiciel en C - Ensimag

5.22. MODULE ENVELOPE (ENVELOPE) 61

Cette fonction permet de finaliser le traitement des échantillons. Une fois appelée, il n’est plus possiblede traiter des échantillons avec process.finalisze retourne 0 en cas de succès et un nombre négatif en cas d’erreur.

5.21.2 Fonctions du module

Le module contient trois fonctions utilisables par le programme principal. Ces fonctions sont déclaréesdans le fichier pcm_handler.h.

void pcm_handler_list (const char *prefix);pcm_handler_t * pcm_handler_create (const char *format, const char *arg);void pcm_handler_delete (pcm_handler_t *hdlr);

• pcm_handler_list :Cette fonction permet de lister tous les formats de sortie disponibles. Elle affiche sur la sortie standard

la liste de ces formats. Chaque nom de format est affiché sur une ligne différente précédé de la chaîne decaractères prefix passée en argument. Dans le cas d’un module ne gérant que le format de sortie "wav",un appel à cette fonction avec comme argument la chaîne vide "" doit uniquement imprimer "wav\n" sur lasortie standard.• pcm_handler_create :La fonction pcm_handler_create alloue et renvoie un pcm_handler_t du format format demandé. Le

troisième argument est un argument pour le gestionnaire d’échantillon. Les chaînes de caractère formatcorrectes sont celles listées par la fonction pcm_handler_list. Dans le cas d’un gestionnaire stockant leséchantillons dans un fichier (par exemple "wav", c’est le nom de ce fichier qui est mis dans cette argument.

Les trois champs du pcm_handler_t renvoyé doivent être valides. Ainsi il sera nécessaire d’avoir aussiimplémenté dans ce module les 3 fonctions à mettre dans les champs du pcm_handler_t pour pouvoir lesinitialiser.

Cette fonction renvoie un pointeur vers le pcm_handler_t créé en cas de succès ou NULL en cas d’erreur.• pcm_handler_delete :Cette fonction supprime toute zone mémoire allouée pour le pcm_handler_t dont le pointeur est passé

en argument.

5.22 Module Envelope (envelope)

5.22.1 Structure

• envelope_t :

typedef struct envelope envelope_t;struct envelope{

uint16_t *blocksize;

uint8_t prev_window;uint8_t curr_window;uint8_t next_window;

int initialized;};

La structure envelope_t est la structure qui permet au module envelope de stoquer ses données detravail. Il contient un premier champ qui correspond aux tailles des fenêtres du flux courant. Les troischamps suivants (prev_window, curr_window et next_window) représentent des informations sur l’état de lafenêtre courante, par des variables 0/1. En gros si la fenêtre est grande c’est un 1 sinon c’est un 0. On a doncdes informations sur la fenêtre précédente et sur la fenêtre suivante, afin de connaître les pentes gauche etdroite. Le dernier champ est relatif au démarrage du flux. En effet, la première moitié de la toute première

Page 62: Projet Logiciel en C - Ensimag

62 CHAPITRE 5. L’API FOURNIE

fenêtre du flux doit être jetée. Le champ initialized doit alors être égale à 0 (faux). À partir de la fenêtresuivante il devra valoir 1 (vrai).

5.22.2 Fonctions• Initialisation et libération :

envelope_t *envelope_init(uint16_t *blocksize);

void envelope_free(envelope_t *env);

L’allocation des données interne au module envelope se fait grâce à la fonction envelope_init prenantpour seul argument un pointeur qui permettra d’avoir accés au deux tailles de fenêtre.

La libération mémoire s’effectue par la fonction envelope_free, qui libère l’ensemble de la structureallouée et pointée par l’argument env.• Utilisation :

int envelope_prepare(envelope_t *env, sample_t *filter);

uint16_t envelope_overlap_add(envelope_t *env, sample_t *in,sample_t *cache, sample_t *out);

L’utilisation de ce module passe par deux fonctions. La première fonction, envelope_prepare, permetde générer le filtre qui sera utilisé pour l’iMDCT (comme décrit au 4.3.1.b). Cette fonction nécessite doncun pointeur sur un stream, un pointeur sur la structure de travail env et un vecteur de sortie filter danslequel sera stocké le filtre généré. N’oubliez pas de mettre à jour prev_window, curr_window et next_windowavant de faire appel à cette fonction. Elle se sert de c’est informations pour calculer le filtre.

La seconde fonction réalise l’opération de recouvrement (cf 4.3.5). Cette fonction,envelope_overlap_add, prend en argument, la structure de travail env, un vecteur d’entrée in conte-nant les échantillons temporels fraichement décodés, un vecteur de cache (cache) et un vecteur de sortie(out). Le vecteur de cache doit contenir la seconde moitié de la fenêtre précédente à l’appel, et contiendrala seconde moitié de la fenêtre courante après traitement. Le vecteur de sortie ne contiendra alors que deséchantillons complètement traités et prêts à être écouté (ou presque). La valeur de retour de cette fonctioncorrespond au nombre de valeurs produites par l’opération de recouvrement (comme décrit au 2.4. Pourrésumer, à chaque appel de cette fonction on produit les données correspondant au recouvrement de lafenêtre précédente et de la fenêtre courante, donc de la moitié de la fenêtre précédente (d’où le cache) à lamoitié de la fenêtre courante.

Le champ initialized de la structure envelope_t est utilisé par cette fonction pour savoir si on est enrégime constant ou en initialisation.

NB : au cours de l’initialisation (première fenêtre/paquet audio) le cache ne contient aucune valeur, onne peut donc pas produire de données. Par contre l’appel remplira le cache pour l’appel suivant. Il faudrapenser à changer la valeur du initialized pour le paquet suivant.

5.23 Module Vorbis Packet (packet)

5.23.1 Structure• vorbis_packet_t :

typedef struct vorbis_packet vorbis_packet_t;

struct vorbis_packet{

int nb_chan; /* number of channels */uint16_t N; /* current packet N size */

Page 63: Projet Logiciel en C - Ensimag

5.23. MODULE VORBIS PACKET (PACKET) 63

int64_t pcm_offset; /* number of PCM samples produced */

/** Spectral buffers:* Sizes are: nb_channels x (N/2)*/sample_t **spectral; /* floor output buffer */

sample_t **dec_residues; /* residue decoded buffers */sample_t **residues; /* re-ordering of residues buffers */

/** Residues decoding helpers.* Sizes: nb_channels*/uint8_t *no_residue; /* no residues flags */uint8_t *do_not_decode; /* do_not_decode flags */

/** Temporal buffer:* Size is: nb_channels x N*/sample_t **temporal; /* iMDCT output buffer */

};

Le type vorbis_packet_t contient des informations et zones de travails relatives au décodage d’unpaquet.

Il contient donc le nombre de canaux à décoder (nb_chan) et la taille de la fenêtre en cours de décodage(N). Il contient de même les principaux tampons de travail pour le décodage :

– spectral contiendra les courbes gros grain après décodage des floor ainsi que les courbes spectralesissues du produit scalaire.

– dec_residues contiendra les résultats de décodage de residues dans le paquet.– residues n’est en fait qu’un réordonnancement des précédents vecteurs puisque les vecteurs de grain

fin sont encodés dans l’ordre des submap.– no_residue et do_not_decode sont des vecteurs de fanions utiles dans les étapes de décodage de floor

et surtout residues.– temporal contiendra les courbes en domaine temporel (après iMDCT).Pour finir, pcm_offset donne le nombre d’échantillons PCM produits.

5.23.2 Fonctions• Vorbis Packet :

vorbis_packet_t *vorbis_packet_init(uint16_t *blocksize , int nb_chan);void vorbis_packet_free(vorbis_packet_t *pkt);

Les deux fonctions vorbis_packet_init et vorbis_packet_free permettent d’allouer et libérer la struc-ture qui contient les zones de travail du décodage de paquet. L’allocation doit bien sûr être effectuée avantde commencer le décodage des paquets audio.

int vorbis_packet_decode(vorbis_stream_t *stream, vorbis_packet_t *pkt);

La fonction vorbis_packet_decode effectue l’opération de décodage des paquets audio du flux Vorbiscomme décrit au 4.3. On a donc dans cette fonction les opération de lecture du mode, le décodage du flux parle mapping courant (mapping), et les opération qui suivent, du dot_product à la production des échantillonsPCM.

Attention 1 : La chaîne de traitement (décodage) fonctionne sur des sample_t or les PCM produitdevront être de type int16_t. Une opération de conversion est donc obligatoire (production des échantillon),mais vous devrez faire attention à la saturation.

Page 64: Projet Logiciel en C - Ensimag

64 CHAPITRE 5. L’API FOURNIE

Attention 2 : Vous produirez ici les échantillons PCM pour le module pcm_handler, mais vous devrezfaire attention à ne pas en produire trop. En effet, avec l’aide de la fonction vorbis_io_limit vous pourrezconnaître la limite d’échantillon à produire et donc tronquer la fin du flux.

Attention 3 : La fonction vorbis_packet_decode est à l’image des fonctions vobis_header1_decode,vobis_header2_decode et vobis_header3_decode, elle ne s’occupe que de la lecture d’un paquet, elle neprend pas en charge le changement de paquet qui devra être géré au niveau supérieur.

5.24 Programme principal (main)Le module main ne contient que la fonction principale qui doit suivre le classique prototype suivant :

int main (int argc, char **argv);

Le programme prend un argument principal sur la ligne de commande qui est le fichier ogg/vorbisd’entrée à décoder. Le programme essaye de décoder le premier flux logique du premier flux physique dufichier (appelé ici chemin/nom) et stocke le résultat dans un fichier au format WAV nommé nom.wav dans lerepertoire courant.

Quelques options viennent modifier le fonctionnement du programme

usage: vorbis_decoder [-hl] [-s <id>] [-f <outformat>] [-o <outfile>] <infile>

-h : affiche l’aide-l : ne décode pas de flux logique, liste à la place les flux logiques du premier flux physique et affiche

pour chacun son codec et son id-s <id> : spécifie l’id du flux logique à décoder, celui-ci doit être parmis ceux listés par l’option -l-f <outformat> : spécifie le format de sortie, outformat doit être parmis les gestionnaires d’échan-

tillons disponibles (au minimum il y’a le format "wav").-o <outfile> : spécifie le nom du fichier de sortie.Pour implanter le traitement des options de la ligne de commande, l’utilisation de librairie getopt est

conseillée. Une documention et des exemples sont dans la page de man (man 3 getopt pour obtenir la pagede manuel de la librairie c).

Page 65: Projet Logiciel en C - Ensimag

Chapitre 6

Travail attendu

Nous avons associé à chacun des modules présentés précédemment un niveau de difficulté.

module difficultétime_domain P

Obligatoiresmode PPPfloor PPPimdct PPPmapping PPPPP

dot_product P

À la carte

common_header Pheader1 Pheader2 PPheader3 PPPvorbis_io PPPPhelpers PPenvelope PPPPmain PPPfloor1 PPPPPcodebook PPPPPresidue PPPPPvorbis_packet PPPPvorbis_main PPPogg_core PPPPogg_packet PPPPpcm_handler PPP

floor0 PPPPPP Extensionsfast_imdct PPPPPP

Vous devez développer durant le projet au minimum les modules obligatoires, ainsi que l’équivalent de10 étoiles en modules à la carte. Cette base correctement réalisée vous assurera la moyenne. Tout modulesupplémentaire, choisi parmi les modules à la carte, terminé et fonctionnel fera monter votre note. Il estévident que le nombre d’étoiles de ces modules supplémentaires influera bien évidement sur la hausse de lanote. Pour finir, ceux qui auront terminé l’ensemble des modules à la carte pourront s’attaquer aux modulesd’extensions. Ces modules sont des modules super-bonus.

D’un point de vue pratique, pour vous aider à développer plus facilement votre décodeur, nous vousproposons différents outils et méthodes qui vous permettront de valider votre code. Ces outils et méthodessont présentés sur le Wiki. Dans tous les cas, n’hésitez pas à faire appel aux encadrants si vous restez blo-qués trop longtemps, ils pourront vous aider sur des problèmes courants ou moins courants, et ils pourrontclarifier les points obscurs de la norme.

Il ne nous reste plus qu’à vous souhaiter bon courage !

65

Page 66: Projet Logiciel en C - Ensimag

66 CHAPITRE 6. TRAVAIL ATTENDU

Page 67: Projet Logiciel en C - Ensimag

Annexe A

Description du format WAV

Le format WAV stocke les échantillons de manière non compressée et est donc très simple. Il est consti-tué d’une partie donnant des informations sur l’audio (nombre et taille des échantillons, nombre de canaux,etc) suivie des échantillons.

A.1 Structure principaleLe format WAV utilise le format de stockage RIFF qui utilise des chunks comme structure pour organiser

un fichier. Un chunk est constitué d’une courte en-tête et de son corps (voir T. A.1). Tous les champs dansces chunks sont little-endian.

Nom Taille (en octets) DescriptionID 4 4 caractères identifiants le chunk

Size 4 Taille du corps du chunk (sans les champs ID et SizeData Size Corps du chunk de taille Size

T. A.1 – Structure d’un chunk

Un fichier WAV est formé d’un chunk principal : RIFF.

Nom Taille (en octets) DescriptionID 4 "RIFF"

Size 4 Taille du fichier moins 8 octetsType 4 Les 4 caractères : "WAVE"

Format 24 Un sous chunk donnant les caractéristiquent de l’audioData variable Un sous chunk contenant tous les échantillons

T. A.2 – Chunk principal RIFF

A.2 Chunk décrivant les caractéristiquesLe chunk format donne les caractéristiques du fichier audio. Certains sont redondants, deux relations

sont vérifiées entre les champs :– Data_rate = S lice_rate ∗ Alignement– Alignement = Channels ∗ ceil(Depth/8)

Le terme Slice est utilisé ici pour représenté le groupement des échantillons de chaque canal correspondantau même instant (la taille d’une Slice est donc égale à la taille d’un échantillon multiplié par le nombre decanaux).

Dans notre cas, les échantillons sont codés sur 16 bits donc Depth = 16 et aucune compression n’estutilisée donc Compression = 1.

67

Page 68: Projet Logiciel en C - Ensimag

68 ANNEXE A. DESCRIPTION DU FORMAT WAV

Nom Taille (en octets) DescriptionID 4 "fmt " (le quatrième caractère est un espace)

Size 4 16Compression 2 Indique la compression utilisée

Channels 2 Nombre de canauxSlice_rate 4 Nombre d’échantillons par seconde par canalData_rate 4 Octets par seconde

Alignement 2 Octets par SliceDepth 2 Nombre de bits par échantillon

T. A.3 – Chunk de description des caractéristiques

A.3 Chunk donnant les échantillonsLe deuxième chunk contient tous les échantillons, ceux-ci sont mis les uns à la suite des autres dans

l’ordre suivant : premier échantillon du premier canal, premier échantillon du deuxième canal, ..., premieréchantillon du deuxième canal, deuxième échantillon du deuxième canal, etc.

Nom Taille (en octets) DescriptionID 4 "data"

Size variable (taille du fichier - 44 octets)Samples variable Les échantillons

T. A.4 – Chunk contenant les échantillons