Introducción a la arquitectura de LoLa

Anuncio
Introducción a la arquitectura de LoLa
Sabemos que la función de LoLa es capturar sonido, aplicarle distintas transformaciones y
reproducirlo, todo ello tratando de cumplir los requisitos de baja latencia, pasemos entonces a explicar
cómo se ha abordado su arquitectura.
Buffers de sonido
Antes de explicar los módulos diferenciados de LoLa y cómo y cuándo se trasmiten el sonido entre
sí, hay que explicar cuál es el “vehículo” que transportará el sonido que ha sido capturado.
El sonido dentro de LoLa no se transportará como un simple flujo de bytes, sino que se utilizarán
unos objetos contenedores, que llamaremos buffers, que lo encapsularán y que proporcionarán
información acerca del sonido que almacenan, como puede ser la frecuencia de muestreo a la que se
capturó, el número de bits que ocupa cada muestra y el número de canales que tiene.
Estos buffers serán instancias de alguna clase descendiente de CBuffer. LoLa cuenta con una
jerarquía de buffers, donde cada clase de la jerarquía se utilizará para encapsular un tipo de sonido
capturado con unos parámetros determinados. Por ejemplo tendremos clases diferenciadas para
almacenar sonido mono o estéreo, y dentro de estas a su vez habrá clases específicas según el número
de bits que tenga cada muestra.
Así, al tener el sonido almacenado en buffers, podemos conocer con facilidad la configuración de
ese sonido, esto es, la frecuencia a la que ha sido muestreado, el tamaño de cada muestra y el número
de canales diferentes. Además mediante el uso de estos valores podemos saber cuántos bytes ocupa a
partir de su duración en tiempo, y viceversa, cuál es su duración en tiempo a partir del número de
bytes que ocupa.
Partes de LoLa
La arquitectura de LoLa constará de cuatro partes o módulos fundamentales(sin contar la interfaz,
que la consideraremos como algo independiente a LoLa), que son las que colaborarán entre sí para
que LoLa funcione y haga aquello para lo que ha sido construido. Dichas partes se enumeran a
continuación, junto con la labor que realizan:
 Módulo capturador:
Encargado de recoger el sonido capturado por la tarjeta de sonido y de dárselo a sus clientes
en la forma apropiada.
 Módulo reproductor:
Encargado de reproducir el sonido transformado en una ruta de audio.
 Módulo transformador:
Encargado de aplicar distintas transformaciones al sonido.
 Módulo de control:
Encargado de coordinar los tres módulos anteriores entre sí, y de ocultarlos por completo a
la interfaz(o a cualquier otro módulo que desee usar LoLa).
A continuación se explicarán más detalladamente cada uno de estos módulos:
Módulo capturador
El módulo capturador será el encargado de recoger el sonido que llega a la tarjeta de sonido. Para
ello utiliza las DirectX(en concreto las DirectSound 8.1) como medio de comunicación con la tarjeta.
Su labor es muy simple, retornar al cliente del módulo el sonido capturado por la tarjeta de sonido en
forma de buffer.
Éste módulo consta de dos partes diferenciadas para llevar a cabo sus funciones. Por un lado
cuenta con un objeto que es el que se encargará de recoger el sonido de la tarjeta usando las
DirectSound 8.1, como ya se dijo antes. Dicho sonido lo devolverá como un flujo de bytes de tamaño
variable, sin proporcionar ninguna clase de información sobre el número de canales, ni la frecuencia a
la que ha sido muestreado ni el número de bits que ocupa cada muestra. El motivo por el que no
devuelve un buffer es para hacer este objeto más portable, y así poder llevarlo a otros sistemas que no
reconozcan objetos buffer. Y por otro lado cuenta con otro objeto encargado de recoger el sonido del
objeto anterior y encapsularlo dentro de buffers del mismo tamaño. La razón para hacer esto es que las
rutas de audio deben trabajar con buffers de tamaño fijo debido a las realimentaciones(ver apartado
del módulo de transformación)
Módulo reproductor
Encargado de reproducir sonido por medio de la tarjeta de sonido. Como el módulo capturador
también usa las DirectX 8.1 para llevar a cabo sus funciones.
Al igual que el módulo capturador cuenta con dos partes diferenciadas. Una que usa las
DirectSound 8.1 para reproducir el sonido que le llega en forma de un flujo de bytes, y otra que recibe
los buffers que se desean reproducir. La labor de esta última consistirá en sacar el sonido del buffer y
enviárselo al objeto anterior. El motivo de haber organizado el módulo de esta manera responde a las
mismas razones que el módulo capturador.
Módulo transformador
Sin duda el módulo más complejo de los que componen LoLa. Tras la aparente simplicidad que
oculta la labor de transformar sonido, se esconde una compleja red de filtros que realizan esta labor.
Antes de entrar en el funcionamiento del módulo hay que aclarar lo que es un filtro, sus distintos
tipos y cómo funcionan éstos.
Una ruta de audio logra la transformación de sonido mediante el trabajo conjunto de diferentes
objetos que llamaremos filtros. Cada uno de esos filtros no es más que un objeto capaz de trabajar con
el sonido contenido en un buffer, ya sea proporcionando buffers, consumiendo buffers o
transformándolos. Dichos filtros estarán relacionados entre sí formando una red arbitrariamente
compleja, de forma que cada uno sabe qué filtro o filtros le preceden y/o le siguen. Así cuando
queremos transformar un buffer de sonido, este llega a la ruta de audio, la cual no es más que la citada
red de filtros, y a partir del filtro inicial va pasando al que le sigue, así hasta llegar al filtro terminal de
la ruta de audio. Una vez llegado a este punto el sonido que llega será el que se va a reproducir usando
el módulo reproductor.
No todos los filtros son iguales, sino que se subdividen en cinco familias, cada una con una
función diferente, pero todas ellas con la misma filosofía de ser objetos capaces de trabajar, de una
forma u otra, con buffers de sonido. Las cinco familias de filtros son:
1. Efectos:
Filtros que sólo tienen una entrada y una salida. Su labor será la de modificar, de una forma
u otra, el sonido que les llega en forma de buffer en su entrada y transmitirlo al filtro que
tienen a su salida.
2. Filtros unión:
Filtros con dos entradas y una salida. Fusionan, de una manera u otra, los dos buffers que le
llegan en un único buffer, que transmiten al filtro que tienen a su salida.
3. Filtros disyunción:
Filtros con una entrada y dos salidas. Dividen el buffer que les llega a la entrada en dos
buffers, transmitiendo cada uno por cada una de sus salidas hacia los filtros que le siguen
en la ruta de audio.
4. Filtros iniciales:
Filtros sin entrada y con una salida. No pueden estar precedidos por ningún filtro, por lo
que no se les puede enviar buffers. Su labor consiste en generar buffers y enviárselos al
filtro que les sigue en la ruta de audio. La manera en la que generan sonido es
independiente de ellos, ya que ellos lo único que hacen es obtener sonido de algún objeto
capaz de producirlo(fichero WAV, objeto generador de ondas senoidales, etc...) y
encapsularlo dentro de un buffer.
5. Filtros terminales:
Filtros con una entrada y ninguna salida. Son capaces de recibir buffers, pero no de
enviarlo. Su labor es la de permitir la utilización del buffer entrante por objetos externos a
la ruta de audio, como puede ser un objeto que almacene el sonido en disco a modo de
ficheros WAV o un objeto que almacene el buffer temporalmente para ser leído
posteriormente por un objeto que lo represente gráficamente.
Es importante destacar una característica de los efectos, y es que están especializados en función
del número de bits por muestra que tenga el buffer de sonido a transformar, pero no para el número de
canales ni para la frecuencia de muestreo. ¿Qué se quiere decir con especializados? Pues
sencillamente que la clase CEfecto es una clase base abstracta, y que tiene clases derivadas que son las
que saben cómo transformar buffers que almacenen sonido cuyas muestras ocupen uno u otro número
de bits. Pero ¿por qué se especializan para el parámetro bits por muestra y no para la frecuencia de
muestreo ni para el número de canales? La respuesta es sencilla: porque no hace falta. Cuando
transformamos un sonido la transformación se realiza muestra a muestra, no nos importa la cadencia
con la que se tomaron dichas muestras, ni si cuenta con uno u ochenta canales. Lo único que nos
importa para la transformación de sonido son las muestras. Pero como sabemos las muestras pueden
estar almacenadas en un número de bits variable(8 ó 16 en la versión actual de LoLa), y por tanto no
será lo mismo transformar muestras que ocupen uno u otro número de bits. Esto se explica claramente
con un ejemplo:
El efecto volumen lo que hace es aumentar o disminuir la amplitud de una onda, por lo que su
implementación consiste simplemente en multiplicar cada muestra por un número. Si tras dicha
multiplicación se produce un overflow o un underflow porque el valor resultante no puede ser
representado con el número de bits de que disponemos, debemos truncar ese valor al valor límite
permitido con ese número de bits, ya que si no se hiciese así podría ocurrir, por ejemplo, que una
señal que se amplifique mucho pase a tener valores negativos tras la aplicación del efecto, y por
tanto estaría funcionando mal.
Pero la labor del módulo transformador no se limita al mantenimiento de la red de filtros, sino que
también debe crearla. Las redes se crearán a partir de ficheros externos que contendrán la estructura de
la misma, indicando los diferentes tipos de filtros que deseamos. Dichos ficheros se llamaran ficheros
LML y no se explicarán aquí, ya que hay una sección dedicada a ellos.
Pero algo habrá que decir de la creación de la red. Estos ficheros están escritos en un lenguaje
XML, y por tanto se necesitará un parser para leerlos. En LoLa se usa el API de Xerces para leer estos
ficheros, en lugar de hacerlo LoLa directamente. Así, utilizando ese API crearemos una estructura de
datos en memoria que lo represente, y esa estructura de datos será lo que utilice el módulo
transformador para crear la red de filtros. Nótese que así se hace independiente éste módulo de los
ficheros LML y del API que se utilice para leerlos.
Módulo controlador
Éste módulo, a pesar de ser el más simple de todos, realiza las labores de control de los tres
anteriores, además de la ocultación de los mismos con respecto a las clases clientes de LoLa(como por
ejemplo la interfaz). Su función se limita a recoger sonido del módulo capturador, enviárselo al
módulo transformador, recoger el buffer de sonido transformado y enviárselo finalmente al módulo
reproductor. Esta secuencia de acciones se realizará constantemente mientras LoLa esté funcionando,
por lo que este modulo creará un hilo cuya única función será hacer esto ininterrumpidamente,
mientras no se desee parar.
También será mediante el módulo controlador como la interfaz modificará los parámetros de los
efectos constituyentes de la ruta de audio, porque recordemos que la interfaz está aislada de los tres
módulos anteriores por medio de éste.
Documentos relacionados
Descargar