Capturar y reproducir las seales de audio es una misma computadora puede ser suficiente en muchas situaciones, pero no en todas. Supongamos que queremos visualizar el espectro de una seal en una computadora que no es la que realiza la captura. Qu podemos hacer?
La primera posibilidad consiste en hacer lo que se propone en la prctica anterior, pero lanzando la aplicacin de forma remota y exportando el display. As, todo el procesamiento se realiza en la mquina que tiene la tarjeta de sonido y la visualizacin en la que estamos sentados.
La alternativa es transmitir el audio hacia la mquina local que adems sea la que calcula el espectro. Esto tiene sus ventanas y sus inconvenientes. El principal, que necesitamos transmitir el audio a travs de la red y esto puede ser costoso. Sin embargo, al disponer de la seal de audio de forma local podemos escucharla, por ejemplo, y aplicar cualquier tipo de procesamiento sin que sobrecargemos la mquina con el hardware de captura.
El C es un lenguaje verstil, potente y muy velz. No es caualidad que sea el lenguaje de programacin ms usado y probado de la historia. Grandes programas que han funcionado correctamente durante aos han sido creados en este lenguaje. Por poner algunos buenos ejemplos, el ncleo de Linux y el ncleo de Windows estn escritos en C.
Antes de comenzar a describir el cdigo de los programas descritos en esta prctica vamos a dar unas nociones muy bsicas de cmo se puede escribir un programa escrito en el lenguaje de programacin C. Usaremos este lenguaje por los siguientes motivos: (1) es muy eficiente tanto en trminos de memoria como de CPU, (2) es el lenguaje de programacin bsico en los sistemas Unix (y por supuesto en Linux) y (3) es muy fcil programar los problemas que vamos a resolver en esta prctica, como vamos a ver.
Un programa escrito en C es una coleccin de funciones que se llaman entre s. Cuando desde el shell ejecutamos el programa compilado, la ejecucin comienza por la funcin llamada main. El resto de funciones pueden tener cualquier otro nombre. Para que el compilador pueda hacer la comprobacin de los parmetros de las funciones, stas deberan ser declaradas antes de su uso (aunque esto no es obligatorio). A continuacin se presenta la escructura general de un programa en C:
Los programas manipulan datos simples y estructuras de datos compuestas por datos simples y otras estructuras de datos. Estos pueden declararse de forma global o pasarse como parmetros a las funciones. Cualquier ”dato” debe ser declarado antes de ser usado.
En C es muy frecuente incluir uno o ms ficheros cabecera que declaran funciones y datos de alguna biblioteca (normalmente, de la bilbioteca estndar). Como ya hemos dicho, dicha inclusin es opcional (aunque conveniente) cuando invocamos a funciones de la biblioteca y obligatoria si hacemos uso de definiciones de tipos de datos, o datos, declarados en ella. Esta inclusin se hace mediante la directiva #include <fichero> del preprocesador de C. El preprocesador interpreta las directivas utilizadas antes de compilar realizando algunas de las funciones que realizara un editor de ficheros ASCII. Todas las directivas comienzan por el smbolo #.
La compilacin de un programa escrito en C genera un fichero binario que contiene instrucciones ejecutables por la CPU de la computadora (cdigo nativo). La forma comn de invocar al compilador de C en un sistema Unix es:
Este comando generar un fichero ejecutable llamado a.out. Si queremos dar otro nombre diferente (por ejemplo, programa) a este fichero escribieremos:
Para ejecutar el fichero programa hay que escribir:
o simplemente
si el directorio actual (en el que se encuentra almacenado programa) est incluido en la lista de directorios de la variable del shell PATH. Podemos conocer el valor de esta variable escribiendo:
En C se manejan dos flujos de datos conocidos como la entrada y la salida estndar. Estos flujos se manipulan mediante varias funciones estndar. Dos de ellas son read() y write(). Estas funciones necesitan una serie de argumentos que pueden conocerse escribiendo en el intrprete de comandos:
En la ayuda on-line proporcionada por el comando man (de MANual), tambin figura el fichero cabecera que debe ser includo (unistd.h en este caso) para la comprobacin del nmero de argumentos y del tipo de los mismos, y una descripcin del funcionamiento de las funciones. En la ayuda veremos que tanto read() como write() poseen 3 parmetros: (1) un descriptor de fichero (en ingls, file descriptor) que referencia al flujo de datos, (2) un puntero a la posicin de la memoria que es usada como fuente (write()) o destino (read()) y (3) el nmero de bytes transferidos.
Lo interesante de usar la entrada y la salida estandares, como ya hemos dicho antes, es que podemos conectar la salida estndar de un programa a la entrada estndar de otro usando un pipe. Esto se hace mediante el smbolo |. Los pipes son canales de comunicacin entre procesos buffereados lo que significa que la transferencia se produce por bloques de datos de un determinado tamao (4 Kbytes tpicamente), aunque nostros transfiramos bloques de un tamao diferente.
Existen dos tipos de sockets, los que utilizan el protocolo de datagramas de usuario o UDP (User Datagram Protocol) y los que utilizan el protocolo de control de la transmisin o TCP (Transmission Control Protocol). La principal diferencia entre ambos es que el UDP necesita que le entregemos paquetes de datos que el usuario debe construir, mientras el TCP admite bloques de datos (cuyo tamao puede ir desde 1 byte hasta muchos K bytes, dependiendo de la implementacin) que sern empaquetados de forma transparente antes de ser transmitidos.
Existe adems otra diferencia importante. Tanto los paquetes de datos UDP como los segmentos TCP (este es el nombre que reciben los paquetes TCP) pueden perderse (muy rara vez llegan al destino correcto con errores). Si un paquete se pierde el UDP no hace nada. Por el contrario, si un segmento se pierde el TCP lo retransmitir, y este proceso durar hasta que el segmento ha sido correctamente entregado al host receptor, o se produzca un nmero mximo de retransmisiones.
Finalmente, en aplicaciones en tiempo real es necesario tambin tener en cuenta una cosa. En el UDP controlamos qu datos viajan en cada paquete. En el TCP esto no es posible porque el empaquetamiento es automtico. De hecho, el TCP espera un tiempo prudencial a tener bastantes datos que transmitir antes de enviar un segmento ya que esto ahorra ancho de banda. Si es importante que los datos tarden el mnimo tiempo posible en llegar al receptor el UDP es la mejor opcin. En este sentido se dice que el UDP tiene una menor latencia que el TCP.
La aplicacin send funciona bsicamente como un pipe, copiando la entrada estndar a la salida estndar, sin realizar ningn tipo de procesamiento. Simultaneamente, emite una secuencia de paquetes a una direccin IP destino que puede ser la direccin de un canal multicast. Tambin puede especificarse un puerto destino.
La aplicacin receive captura todos los paquetes enviados al puerto de escucha, extrae el audio que contienen y lo escribe sobre su salida estndar.
La transmisin de datos a travs de la red, o incluso a travs de la pila de protocolos TCP/IP dentro de un mismo host introduce un retraso aperciable en la transmisin de las seales. Para poder controlar dicho retraso se ha implementado un sencillo programa en C llamado delay que retrasa la salida estndar respecto de la entrada estndar un nmero determinado de milisegundos. Ms tarde veremos algn ejemplo de su uso.
Estos son los ficheros Makefile.