PRÁCTICAS TXÓN. DATOS MULTIMEDIA COMPRESIÓN DE AUDIO 1. Introducción En esta práctica vamos a tratar la compresión de audio. En concreto vamos a estudiar la conversión de una serie de muestras de audio sin comprimir (codificadas en formato PCM, con 16 bits por muestra y en un único canal, o sea “mono”) a codificación ADPCM. Particularmente, usaremos la versión de ADPCM de IMA (también conocido como DVI ADPCM), en la que la predicción de la muestra actual se basa simplemente en la muestra previamente codificada, y la adaptación del paso de cuantización se hace de manera adaptativa usando para ello unas tablas estándar. Además, estudiaremos el formato WAV diseñado inicialmente por IBM y Microsoft para almacenar ficheros de audio en ordenadores PC. Veremos cuál es su estructura para guardar sonido en ambos formatos, tanto PCM como IMA ADPCM. Como en el resto de prácticas, proporcionaremos el código fuente con una implementación parcial del compresor, y que se deberá de analizar y completar. En concreto, la función a implementar es la codificación de una muestra de formato PCM a ADPCM, usando el algoritmo adaptativo estudiado en las sesiones de teoría. De nuevo, esta implementación se ha escrito en lenguaje ANSI C++, y la interacción con el usuario se realiza por medio de paso de comandos en una ventana de sistema, por lo que su compilación se puede realizar en cualquier entorno de desarrollo C++, tanto Windows como Linux. Un requerimiento adicional en esta práctica, si se desea poder escuchar el fichero resultante de la codificación, es disponer de un ordenador con capacidad de reproducción de audio, además de un programa reproductor que sea compatible con ambos formatos .wav, tanto PCM como IMA ADPCM. En general, en los sistemas operativos MS Windows se incluye por defecto tanto el decodificador de IMA ADPCM como algún reproductor de audio compatible (por ejemplo, la “grabadora de sonidos” o el “reproductor de windows media”). 2. Codificación IMA ADPCM La codificación ADPCM es un tipo de codificación diferencial con pérdidas, en la que la diferencia de la muestra codificada respecto a la muestra anterior se cuantiza con un paso de cuantización (step) adaptativo. Este paso de cuantización es adaptativo porque se va incrementando o decrementando en función de la magnitud de las diferencias previamente codificadas. En esta práctica nos vamos a centrar en la versión concreta de ADPCM propuesta por la extinta Asociación de Multimedia Interactiva (Interactive Multimedia Association, IMA). Esta versión de ADPCM, que a veces también es denominada como Intel DVI ADPCM, es capaz de comprimir muestras usando 3 ó 4 bits por muestra. En el caso que vamos a tratar en esta práctica convertiremos muestras de 16 bits en muestras de 4 bits, 1 con lo que el nivel de compresión alcanzado será de 4:1. Aunque la tasa de compresión alcanzada con este tipo de compresión no es mucha, su sencillez de implementación tanto en hardware como en software ha provocado que tenga una gran difusión dentro de la industria audiovisual. Así, prácticamente todos los sistemas operativos MS Windows incluyen un codificador de esta versión de ADPCM. Además, muchas grabadoras de audio digital de modestas prestaciones utilizan este formato para almacenar el audio de una forma más compacta. Por ejemplo, muchos reproductores MP3 con capacidad de grabar audio directamente a través de un micrófono incorporado codifican el resultado de la grabación en este formato. 2.1 Algoritmo IMA ADPCM Veamos a continuación cómo funciona este algoritmo, cuya implementación se realizará en el fichero ADPCM.cpp proporcionado en esta práctica. Como ya hemos dicho, básicamente se realiza una codificación diferencial respecto a la muestra anterior, pero usando una cuantización adaptativa, de forma que si el resultado de cuantizar las muestras anteriores ha dado un valor muy grande, se aumenta el paso de cuantización, y si ha sido más bien pequeño se disminuye. Para indicar los posibles pasos de cuantización a aplicar se dispone de una tabla (cuyos pasos se incrementan siguiendo una escala logarítmica). Dentro del código fuente a completar en esta práctica, esta tabla viene definida como sigue: static const unsigned short IMA_ADPCMStepTable[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442,11487,12635,13899, 15289,16818,18500,20350,22385,24623,27086,29794, 32767 }; Para la compresión de una muestra m codificada en formato PCM (es decir, sin compresión) se parte de uno de los pasos de esta tabla (en concreto de aquel indexado por la variable global int StepIndex del fichero ADPCM.cpp), y de la muestra que se codificó en el paso anterior, tal y como la leerá el decodificador (variable global signed short PredictedValue). Por tanto, lo primero que hay que calcular es la diferencia (delta, ∆) entre esta muestra y la anterior ∆ = m - PredictedValue siendo este valor de delta lo que vamos a codificar con el paso de cuantización (step) que nos indique la tabla anterior (que lo podremos obtener como step=IMA_ADPCMStepTable[StepIndex]). 2 A continuación vamos a ver exactamente cómo codificar esta diferencia usando los cuatro bits de ADPCM (del bit 3 al bit 0): • El bit 3 tiene un tratamiento especial ya que nos indica el signo de la diferencia. Codificamos 1 para signo negativo, 0 en otro caso. Además, para continuar con la codificación, aplicaremos valor absoluto a la diferencia delta: ∆ = ∆ A continuación, el bit 2 se codifica con un 1, si ∆ > step . En ese caso, actualizamos delta como sigue: ∆ = ∆ − step step . En ese caso, también • Ahora, el bit 1 se codifica con un 1, si ∆ > 2 step actualizaremos delta como sigue: ∆ = ∆ − 2 step • Por último, el bit 0 se codifica con un 1, si ∆ > . 4 Es importante destacar que todas las operaciones que se realizan en la codificación IMA ADPCM son siempre enteras, para lo que bastará con usar tipos de datos enteros (o sea, operandos enteros). • A continuación hay que decodificar el valor que acabamos de codificar para asignarlo a la variable global PredictedValue, de forma que la próxima muestra se pueda codificar diferencialmente a partir de este valor. Observa que usamos el valor de la muestra tal y como lo leerá el decodificador (es decir, después de la cuantización y la decuantización) y no la muestra original. Esto se hace así porque el decodificador no dispone de los valores de estas muestras originales sino tan sólo de su decodificación. Por último, hay que realizar la adaptación de la cuantización. Para esto debemos de actualizar el índice del paso de cuantización (la variable global int StepIndex) según el resultado de la codificación ADPCM que acabamos de realizar. Para esta actualización usaremos los tres bits de menor peso del resultado de la muestra codificada1 como índice a otra tabla que se usa para incrementar o decrementar la variable global StepIndex. En concreto, esta tabla está definida en ADPCM.cpp como sigue: static const int IMA_ADPCMIndexTable[8] = { -1, -1, -1, -1, 2, 4, 6, 8, }; Como se puede observar, resultados entre 0 y 3 provocan que el índice del paso de cuantización (StepIndex) se decremente en uno, mientras que un resultado de 4, 5, 6 ó 7 hace que este índice aumente en 2, 4, 6 u 8 respectivamente (lo que resultará en aumentos en el paso de cuantización mucho mayores). De esta forma es como adaptamos el paso de cuantización al resultado de previas codificaciones. 1 Para obtener los tres bits de menor peso basta con aplicar una máscara usando la operación “and” entre “7” y el valor resultante de la codificación. En lenguaje C esta operación se puede hacer utilizando el operador “&”. 3 Algo a tener en cuenta es que la tabla IMA_ADPCMStepTable sólo contiene 89 valores, y por tanto la variable StepIndex debe de mantenerse siempre dentro del rango de [0 a 88]. 3. Ficheros WAV Los ficheros WAV (cuya extensión es .wav) se basan en un formato diseñado por Microsoft e IBM para almacenar ficheros de audio. Aunque su uso más habitual es guardar información sin comprimir en formato PCM, también se puede usar como un “contenedor” de otros tipos de formatos, como es el caso de IMA ADPCM. En este punto vamos a describir cuál es el formato habitual de este tipo de ficheros. Sin embargo, es importante destacar que existe una gran variedad dentro de este tipo de ficheros, que a su vez son una variante del formato RIFF para almacenar un stream de datos multimedia en fichero a base de “chunks” (o por “trozos”). 0 Identificador (“RIFF”) 4 Longitud de fichero - 8 8 Formato (“WAVE”) 12 Sub-identificador de formato (“fmt “) 16 Longitud de formato de audio 20 Formato de audio 24 Frecuencia de muestreo (muestras/seg.) 28 Tasa de bytes (bytes/seg.) Alineamiento de Bits por muestra bloques 32 Número de canales 36/40 Sub-identificador de datos (“data”) 40/44 Longitud del campo de datos 44/48 DATOS Tamaño de parámetros extra Cuenta de muestras por 36 canal por bloque Figura 1: Formato de los ficheros .wav (tanto PCM como ADPCM) En la Figura 1 se describe cuál es el formato de un fichero .wav canónico, para su uso tanto en codificación de muestras PCM como IMA ADPCM. A continuación explicaremos brevemente los campos (de 4 bytes en la mayor parte de casos) que lo forman, según cada una de las tres partes que componen un fichero .wav: • Descripción general del fichero RIFF. Se incluye un identificador del propio formato general (codificación ASCII de la cadena “RIFF”), la longitud del fichero (a partir de esa etiqueta, por tanto sin contar los 8 primeros bytes) y otro identificador de formato del fichero (en este caso la cadena “WAVE”). 4 • Descripción del formato de audio. Está formado a su vez por: - El identificador en código ASCII “fmt “ - El campo longitud del formato de audio (a partir de esa posición). En concreto será de 16 para codificación PCM y de 20 para IMA ADPCM. - Formato de audio. Indica cómo se encuentran representadas las muestras de audio en los datos. Puede ser 0x01 para PCM o 0x11 para IMA ADPCM. Observa que este campo es de sólo dos bytes. - Número de canales. Uno para mono y dos para estéreo. Por sencillez, en esta práctica sólo manejaremos ficheros mono. - Frecuencia de muestreo. En el caso de IMA ADPCM, tan sólo son válidas las frecuencias de 8000 Hz, 11025 Hz, 22050 Hz y 44100 Hz. - Tasa de bytes por segundo. Indica el número de bytes de datos que se deben de leer por segundo para reproducir el audio. A partir de este valor, el software de reproducción puede hacer las reservas de memoria oportunas. - Alineamiento de bloques. Para PCM representa el número de bytes por muestra (2 con las muestras de 16 bits de la práctica). En IMA ADPCM indica el tamaño de los bloques de datos en los que se guardan las muestras (ver más adelante). - Bits por muestra. En nuestro caso, debe de ser 16 bits para los datos PCM y 4 bits para los ADPCM. - Tamaño de parámetros extra. Este campo y el siguiente (de dos bytes cada uno) sólo aparecen en los ficheros IMA ADPCM. Este campo indica la longitud de estos datos extra que vamos a añadir a la cabecera estándar WAV. Debe de ser 2 bytes. - Cuenta de número de muestras por canal y por bloque. Se calcula como: (( Alineamiento − (4 * NumeroDeCanales)) * 8 cuenta = +1 ( BitsPorMuestra )( NumeroDeCanales) En nuestro caso será de 0x1F9. • Por último están almacenados los datos, con un identificador previo del campo de datos (la cadena “data”) y otro campo indicando la longitud en bytes de los propios datos. Dentro del campo de datos, en los ficheros de tipo PCM simplemente se encuentra el conjunto de muestras de 16 bits. Sin embargo, la codificación para IMA ADPCM es un poco más compleja. En este caso los datos se almacenan utilizando bloques cuyo tamaño viene indicado por el campo “Alineamiento de bloques” que se encuentra en la descripción del formato de audio. En general, los bloques de datos de IMA ADPCM que tratamos (mono con 4 bits/muestra) van a ser de 256 bytes, y van a constar de una cabecera de 4 bytes y un cuerpo de datos de 252 bytes. En la cabecera se tiene la siguiente información: - PredictedValue (2 bytes): La primera muestra del bloque. Cuando se decodifica, éste será el valor que se tomará como referencia, como muestra anterior a partir de la que empezar con la decodificación. 5 - StepIndex (1 byte): Índice a la tabla de pasos de cuantización (tiene un valor de entre 0 y 88) que indica el paso de cuantización inicial a emplear en el bloque. - Reservado (1 byte): No se usa y normalmente es 0. Posteriormente, los 252 bytes de datos tendrán el resultado de la codificación de 504 muestras en formato IMA ADPCM. 4. Código fuente Esta práctica consta sólo de tres ficheros en lenguaje C. • • • El fichero audio.cpp sirve para obtener los datos del usuario por parámetro (o si no se indica ningún dato, se asignan valores por defecto) y llama a las rutinas necesarias para realizar la compresión y descompresión ADPCM. En FichWav.cpp se encuentra completamente implementadas las funciones de carga y grabación de datos en ficheros en formato .wav, a partir de la estructura descrita en el anterior punto. Por último, ADPCM.cpp implementa los algoritmos de codificación y decodificación de IMA ADPCM. En concreto la codificación ADPCM se hará pasando un vector M_PCM con NMuestras a la función unsigned int ConviertePCMMono16aADPCMMono4 (short *M_PCM, int NMuestras, unsigned char **M_ADPCM); El vector resultante con la codificación se guardará en *M_ADPCM, y la función devolverá la longitud de este vector (en bytes). Para realizar el proceso inverso hay que usar la función unsigned int ConvierteADPCMMono4aPCMMono16 (unsigned char *M_ADPCM, int longDatos, short **M_PCM) que devuelve en este caso el número de muestras PCM de 16 bits que se ha decodificado y se han dejado en *M_PCM. Además, dentro de ADPCM.cpp podemos destacar la siguiente función en la que hay que implementar la codificación de una muestra PCM a IMA ADPCM unsigned char Codifica(signed int pcm16) 5. Trabajo a realizar El trabajo que debe realizarse en esta práctica es el siguiente: 1) Leerse la memoria, analizar los ficheros fuentes proporcionados y entender cómo funciona la codificación IMA ADPCM. 2) En esta práctica hay que implementar una única función: Codifica(...) del fichero ADCPM.cpp. Para implementar esta función podremos seguir los pasos descritos en el punto anterior (codificación ADPCM). Fíjate que para la 6 codificación hay que usar también la decodificación, ya que el valor diferencial se obtiene a partir de la decodificación de la muestra previa. Sin embargo, este proceso se encuentra ya implementado por la función int DecodificaValor(unsigned int adpcm, int step) a la que simplemente tenemos que pasar la muestra actual que acabamos de codificar y el paso de cuantización que hemos empleado, y nos devuelve el valor decodificado, que podremos asignar a la variable global PredictedValue para poder usarlo la siguiente vez que codifiquemos otra muestra. 3) Por último, debes de comprobar que la implementación es correcta. Para esto puedes ejecutar el programa con los parámetros por defecto (por defecto se comprime el fichero “homerPCM.wav” con muestras PCM y el resultado se deja en el fichero “homerADPCM.wav”), o tecleando audio –c –i homerPCM.wav –o homerADPCM.wav Si todo ha ido bien, se debe de haber generado un fichero con la codificación IMA ADPCM de las muestras del fichero de entrada, que podrá ser reproducido con un reproductor de audio adecuado (por ejemplo, el reproductor de windows media). Además, el propio programa facilita el PSNR de las muestras de audio comprimida, que indica el nivel de calidad de la señal comprimida respecto a la original. En el caso del fichero “homerPCM.wav”, el PSNR debe de ser de 34.72 dB. En caso de no disponer de ningún programa de reproducción de audio, o de un ordenador con capacidad de reproducción de audio, se puede comprobar que el PSNR alcanzado en vuestra implementación es coincidente con éste para validar la implementación realizada. Además comprueba también que el nivel de compresión alcanzado finalmente es el esperado. Aunque la descompresión no hay que implementarla en esta práctica, sino que ya viene resuelta, si se desea realizar el proceso inverso de recuperación de muestras PCM se puede teclear audio –d –i homerADPCM.wav –o homerPCMrec.wav 6. Posibles extensiones de la práctica Como posible ampliación a partir de la práctica, se propone implementar alguno de los codificadores de audio vistos en las sesiones de teoría, junto con el correspondiente descompresor. Por tanto, se puede implementar un compresor basado en la codificación logarítmica (como el u-law ó A-law), algunas versiones estándar de ADPCM (G.721, G.722, G.723, G.726 y G.727), un codificador especifico para voz usando técnicas vo-coding (como 7 LPC, CELP, GSM, etc), o incluso algún codificador destinado a audio de mayor calidad, como el descrito en el estándar MPEG1 (en su versiones 1, 2 ó 3, siendo esta última el famoso MP3). Evidentemente, la complejidad de los compresores de audio mencionados anteriormente es distinta, y el trabajo recibirá mejor puntuación cuanto mayor dificultad entrañe el códec seleccionado. Para realizar las implementaciones se permite (e incluso en algunos casos, como en MPEG1, es aconsejable) consultar otras implementaciones disponibles, que pueden servir de referencia, y se pueden encontrar buscando por diversas fuentes en Internet. Es importante destacar en la memoria de la ampliación cuál es la estructura del programa, y cómo se ha efectuado la implementación del mismo, indicando el grado de aportación de otras fuentes. Además, hay que incluir una descripción teórica, que se relacionará con las distintas partes del programa. 8