Transmisión de Secuencias de Audio

Vicente González Ruiz

June 24, 2014

Contents

1 Programacin en C
 1.1 Estructura de un programa escrito en C
 1.2 Los ficheros “cabecera”
 1.3 Compilacin y ejecucin de un programa escrito en C
2 La entrada y la salida estndar
3 Sockets UDP y TCP
4 La aplicacin send
 4.1 send.c
5 La aplicacin receive
 5.1 receive.c
6 La aplicacin delay
 6.1 delay.c
7 Sobre la compilacin
 7.1 Compilacin de send
 7.2 Compilacin de receive
8 Ejemplos de utilizacin

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.

1 Programacin en C

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.

1.1 Estructura de un programa escrito 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:

/* Directivas de inclusin de los ficheros .h (ficheros cabecera). */  
 
/* Directivas de declaracin de definiciones (constantes). */  
 
/* Varibles globales. */  
 
/* Funciones, que pueden encapsular a otras funciones. */  
 
/* Funcin main(): */  
int main(int argc, char *argv[]) {  
  /* Slo existe una funcin main(). */  
  /* Llamadas a otras funciones. */  
}

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.

1.2 Los ficheros “cabecera”

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 #.

1.3 Compilacin y ejecucin de un programa escrito en C

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:

cc programa.c

Este comando generar un fichero ejecutable llamado a.out. Si queremos dar otro nombre diferente (por ejemplo, programa) a este fichero escribieremos:

cc programa.c -o programa

Para ejecutar el fichero programa hay que escribir:

./programa

o simplemente

programa

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:

echo $PATH

2 La entrada y la salida estndar

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:

man 2 read  
man 2 write

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.

3 Sockets UDP y TCP

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.

4 La aplicacin send

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.

4.1 send.c

 
/****************************************************************************** 
 * udp_send.c -- Emisor de datos que usa el UDP. 
 * 
 * Enva una copia de la entrada estndar hacia un host usando el 
 * protocolo UDP. La entrada es tamben copiada a la salida estndar. 
 * 
 * gse. 2010. 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Ficheros cabecera. 
 * 
 *****************************************************************************/ 
 
/* Entrada y salida de streams buffereados. */ 
#include <stdio.h> 
 
/* Biblioteca de funciones estndar (exit(), EXIT_SUCCESS, 
   EXIT_FAILURE, ...) */ 
#include <stdlib.h> 
 
/* Manipulacin de cadenas y movimiento de memoria (memset(), ...). */ 
#include <string.h> 
 
/* Biblioteca estndar de funciones relacionadas con el sistema 
   operativo Unix (read(), write(), getopt(), ...). */ 
#include <unistd.h> 
 
/* Llamadas al sistema para manejo de descriptores de ficheros, 
   sockets, etc. */ 
#include <fcntl.h> 
 
/* Sockets. */ 
/* Ms info en: 
 * 
 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
 * http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm 
 * http://beej.us/guide/bgnet/ 
 */ 
 
/* Tipos de datos primitivos del sistema. */ 
#include <sys/types.h> 
 
/* Sockets. */ 
#include <sys/socket.h> 
 
/* Direcciones IP, opciones y definiciones. */ 
#include <netinet/in.h> 
 
/* Servicio de resolucin de nombres. */ 
#include <netdb.h> 
 
/* Signals (interrupciones). */ 
#include <signal.h> 
 
/****************************************************************************** 
 * 
 * Definiciones. 
 * 
 *****************************************************************************/ 
 
/* Tamao (en bytes) del payload de los datagramas. */ 
#define PAYLOAD_SIZE 512 
 
/* Host destino de los datagramas. */ 
#define RECEIVER_HOST "localhost" 
 
/* Puerto destino de los datagramas en el host destino. */ 
#define RECEIVER_PORT 6666 
 
/* Time to live. */ 
#define TTL 32 
 
/* Indica si el socket es bloqueante o no. Los sockets normalmente son 
   bloqueantes, lo que signfifica que la llamada al sistema que 
   realiza el envo de datos a travs de la red se bloquea mientras el 
   hardware de red no pueda hacerse cargo de los datos, por ejemplo, 
   porque todava se est transmitiendo un paquete anterior. Sin 
   embargo, hay situaciones donde puede ser interesante que la llamada 
   al sistema no sea bloqueante. En este caso, si "la red" no est 
   lista, simplemente la llamada retorna y es posible seguir 
   realizando alguna otra tarea. As, por ejemplo, cuando transmitimos 
   audio por la red, si la red no est lista lo que experimenta el 
   receptor son cortes, pero no pausas. */ 
#define BLOCKING 1 
 
/****************************************************************************** 
 * 
 * Variable globales. 
 * 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Funciones. 
 * 
 *****************************************************************************/ 
 
/* Cuerpo principal del programa. */ 
main(int argc, char *argv[]) { 
  work(argc, argv); 
  return EXIT_SUCCESS; 
} 
 
work(int argc, char *argv[]) { 
 
  int payload_size = PAYLOAD_SIZE; 
  char *receiver_host = RECEIVER_HOST; 
  int receiver_port = RECEIVER_PORT; 
  unsigned char ttl = TTL; 
  int blocking = BLOCKING; 
 
  /* Manipulamos los parmetros de entrada. */ 
  int c; 
  while ((c = getopt (argc, argv, "ns:h:p:t:H")) != -1) { 
    switch (c) { 
    case n: 
      blocking = 0; 
      break; 
    case s: 
      payload_size = atoi(optarg); 
      break; 
    case h: 
      receiver_host = optarg; 
      break; 
    case p: 
      receiver_port = atoi(optarg); 
      break; 
    case t: 
      ttl = atoi(optarg); 
      break; 
    case H: 
      fprintf(stderr, "Usage:send[OPTION...]\n"); 
      fprintf(stderr, "\n"); 
      fprintf(stderr, "-n(forsenddatawithoutblocking)\n"); 
      fprintf(stderr, "-spayload_size\n"); 
      fprintf(stderr, "-hreceiver_host\n"); 
      fprintf(stderr, "-preceiver_port\n"); 
      fprintf(stderr, "-tTTL\n"); 
      fprintf(stderr, "\n"); 
      fprintf(stderr, "CopiesthestdintothestdoutandaUDPsocket\n"); 
      return; 
    case ?: 
      if (isprint (optopt)) 
        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
      else 
        fprintf (stderr, 
                 "Unknownoptioncharacter‘\\x%x’.\n", 
                 optopt); 
      return 1; 
    default: 
      abort (); 
    } 
  } 
 
  fprintf(stderr,"%s:payloadsize=\"%d\"\n", argv[0],payload_size); 
  fprintf(stderr,"%s:receiverhost=\"%s\"\n", argv[0],receiver_host); 
  fprintf(stderr,"%s:receiverport=\"%d\"\n", argv[0],receiver_port); 
  fprintf(stderr,"%s:TTL=\"%d\"\n", argv[0],ttl); 
  fprintf(stderr,"%s:blocking=", argv[0]); 
  if(blocking) { 
    fprintf(stderr,"yes\n"); 
  } else { 
    fprintf(stderr,"no\n"); 
  } 
 
  /* Preguntamos al DNS por la dir IP destino. */ 
  struct hostent *receiver_IP; 
  if ((receiver_IP = gethostbyname(receiver_host)) == NULL) { 
    perror("gethostbyname"); 
    exit(EXIT_FAILURE); 
  } 
 
  int sd; /* Socket descriptor */ { 
 
    /* Obtenemos el nmero del protocolo. */ 
    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
    ptrp = getprotobyname("udp"); 
    if(ptrp == NULL) { 
      perror("getprotobyname"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Creamos el socket descriptor. */ 
    sd = socket(AF_INET, SOCK_DGRAM, ptrp->p_proto); 
    if(sd < 0) { 
      perror("socket"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  if(!blocking) { 
    fprintf(stderr,"%s:usingnon-blockingsocket...", argv[0]); 
    /* Configuramos el socket no bloqueante. */ 
    fcntl(sd, F_SETFL, O_NONBLOCK); 
  } 
 
  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
     end(). */ 
  void end() { 
    fprintf(stderr,"%s:CTRL+Cdetected.Exiting...",argv[0]); 
    close(sd); 
    fprintf(stderr,"done\n"); 
    exit(EXIT_SUCCESS); 
  } 
  /* Activamos la seal SIGINT. */ 
  signal(SIGINT, end); 
 
  /* Especificamos el host y puerto destino de los datagramas. */ 
  struct sockaddr_in socket_addr; { 
    /* Borramos la estructura. */ 
    memset((char  *)&socket_addr,0,sizeof(socket_addr)); 
    /* Usaremos Internet. */ 
    socket_addr.sin_family = AF_INET; 
    /* IP destino de los datagramas */ 
    socket_addr.sin_addr = *((struct in_addr *)receiver_IP->h_addr); 
    /* Puerto destino de los datagramas */ 
    socket_addr.sin_port = htons(receiver_port); 
  } 
 
  /* Establecemos el TTL de los datagramas. */ 
  setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); 
 
  /* Reservamos memoria para el payload de los datagramas. */ 
  char *payload = (char *)malloc(payload_size*sizeof(char)); 
 
  for(;;) /* Enviamos, enviamos y enviamos. */ { 
 
    /* Lemos la stdin. */ 
    int bytes_read = read(0, (void *)payload, payload_size); 
    fprintf(stderr,"r"); fflush(stderr); 
    if(bytes_read < 1) break; 
 
    /* Escribimos la stdout, para poder continuar el pipe. */ 
    write(1, (void *)payload, bytes_read); 
    fprintf(stderr,"w"); fflush(stderr); 
 
    /* Enviamos el datagrama. */ 
    sendto(sd, (void *)payload, bytes_read, 0, 
      (struct sockaddr *)&socket_addr, sizeof(struct sockaddr)); 
    fprintf(stderr,"s"); fflush(stderr); 
 
  } 
}

5 La aplicacin receive

La aplicacin receive captura todos los paquetes enviados al puerto de escucha, extrae el audio que contienen y lo escribe sobre su salida estndar.

5.1 receive.c

 
/****************************************************************************** 
 * udp_receive.c -- Receptor de datos que usa el UDP. 
 * 
 * Recibe un flujo de datos a travs de un puerto y lo copia a la 
 * salida estandard. 
 * 
 * gse. 2010. 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Ficheros cabecera. 
 * 
 *****************************************************************************/ 
 
/* Entrada y salida de streams buffereados. */ 
#include <stdio.h> 
 
/* Biblioteca de funciones estndar (exit(), EXIT_SUCCESS, 
   EXIT_FAILURE, ...) */ 
#include <stdlib.h> 
 
/* Manipulacin de cadenas y movimiento de memoria (memset(), ...). */ 
#include <string.h> 
 
/* Biblioteca estndar de funciones relacionadas con el sistema 
   operativo Unix (read(), write(), getopt(), ...). */ 
#include <unistd.h> 
 
/* Llamadas al sistema para manejo de descriptores de ficheros, 
   sockets, etc. */ 
#include <fcntl.h> 
 
/* Sockets. */ 
/* Ms info en: 
 * 
 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
 * http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm 
 * http://beej.us/guide/bgnet/ 
 */ 
 
/* Tipos de datos primitivos del sistema. */ 
#include <sys/types.h> 
 
/* Sockets. */ 
#include <sys/socket.h> 
 
/* Direcciones IP, opciones y definiciones. */ 
#include <netinet/in.h> 
 
/* Servicio de resolucin de nombres. */ 
#include <netdb.h> 
 
/* Signals (interrupciones). */ 
#include <signal.h> 
 
/****************************************************************************** 
 * 
 * Definiciones. 
 * 
 *****************************************************************************/ 
 
/* Tamao del payload del datagrama. */ 
#define PAYLOAD_SIZE 512 
 
/* Puerto de escucha por defecto. */ 
#define LISTENING_PORT 6666 
 
/****************************************************************************** 
 * 
 * Variable globales. 
 * 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Funciones. 
 * 
 *****************************************************************************/ 
 
/* Cuerpo principal del programa. */ 
int main(int argc, char *argv[]) { 
  work(argc, argv); 
  return EXIT_SUCCESS; 
} 
 
work(int argc, char *argv[]) { 
 
  /* Tamao de payload del datagrama. */ 
  int payload_size = PAYLOAD_SIZE; 
 
  /* Puerto en el que el receptor escucha. */ 
  int listening_port = LISTENING_PORT; 
 
  /* Manipulamos los parmetros de entrada. */ 
  int c; 
  while ((c = getopt (argc, argv, "s:p:H")) != -1) { 
    switch (c) { 
    case s: 
      payload_size = atoi(optarg); 
      break; 
    case p: 
      listening_port = atoi(optarg); 
      break; 
    case H: 
      fprintf(stderr, "Usage:receive[OPTION...]\n"); 
      fprintf(stderr, "\n"); 
      fprintf(stderr, "-spayload_size\n"); 
      fprintf(stderr, "-plistening_port\n"); 
      fprintf(stderr, "\n"); 
      fprintf(stderr, "Receivesdatafromthelistening_portandcopiesthe"); 
      fprintf(stderr, "datatothestdout\n"); 
      return; 
    case ?: 
      if (isprint(optopt)) 
        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
      else 
        fprintf(stderr, 
                 "Unknownoptioncharacter‘\\x%x’.\n", 
                 optopt); 
      return 1; 
    default: 
      abort (); 
    } 
  } 
 
  fprintf(stderr,"%s:payloadsize=\"%d\"\n", argv[0],payload_size); 
  fprintf(stderr,"%s:listeningport=\"%d\"\n", argv[0],listening_port); 
 
  int sd; /* Socket descriptor */ { 
 
    /* Obtenemos el nmero del protocolo. */ 
    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
    ptrp = getprotobyname("udp"); 
    if(ptrp == NULL) { 
      perror("errroringetprotobyname()...aborting"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Creamos el socket descriptor. */ 
    sd = socket(AF_INET, SOCK_DGRAM, ptrp->p_proto); 
    if(sd < 0) { 
      perror("socket"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
     end(). */ 
  void end() { 
    fprintf(stderr,"%s:CTRL+Cdetected!Exiting...", argv[0]); 
    close(sd); 
    fprintf(stderr,"done.\n"); 
    exit(EXIT_SUCCESS); 
  } 
  /* Activamos la seal SIGINT. */ 
  signal(SIGINT, end); 
 
  /* Cuando hagamos el bind(), usaremos el puerto de servicio sin 
     esperar a que ste est a la espera de un time-out de 
     liberacin. */ 
  int yes = 1; 
  if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { 
    perror("setsockopt"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Realizamos el bind() (peticin al SO para que a este proceso se 
     le entregue todo lo que llega a travs de un determinado 
     puerto). A la llamada bind() hay que pasarle tres parmetros 
     importantes: (1) el tipo de socket que vamos a utilizar, (2) la 
     direccin IP del adaptador de red por el que vamos a recibir los 
     datos y (3) el puerto de escucha. */ { 
 
    /* Estructura de datos que necesita bind(). */ 
    struct sockaddr_in socket_addr; 
    memset((char  *)&socket_addr,0,sizeof(socket_addr)); 
 
    /* (1) Usaremos Internet. */ 
    socket_addr.sin_family = AF_INET; 
 
    /* (2) Cualquiera de los adaptadores de red (debidamente 
       configurados con el IP) pueden recibir los datos. */ 
    socket_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
 
     /* (3) Definimos el puerto de escucha. */ 
    socket_addr.sin_port = htons((u_short)listening_port); 
 
    /* Y finalmente, el bind(). */ 
    if (bind(sd, (struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0) { 
      perror("bind"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Asignamos memoria para almacenar el payload del datagrama entrante. */ 
  char *payload = (char *)malloc(payload_size*sizeof(char)); 
 
  for(;;) /* Bucle infinito de recepcin de datagramas. */ { 
 
    /* Recibimos un datagrama. */ 
    int bytes_read = recv(sd, (void *)payload, payload_size, 0); 
    fprintf(stderr,"r"); fflush(stderr); 
 
    /* Escribimos el datagrama a stdout. */ 
    write(1, (void *)payload, bytes_read); 
    fprintf(stderr,"w"); fflush(stderr); 
 
  } 
}

6 La aplicacin delay

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.

6.1 delay.c

 
/****************************************************************************** 
 * delay.c -- Retrasa la salida estndar respecto de la entrada estndar 
 *            un nmero determinado de milisegundos. 
 * 
 * gse. 2006. 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Ficheros cabecera. 
 * 
 *****************************************************************************/ 
 
/* Entrada y salida de streams buffereados. */ 
#include <stdio.h> 
 
/* Biblioteca de funciones estndar (exit(), EXIT_SUCCESS, 
   EXIT_FAILURE, ...) */ 
#include <stdlib.h> 
 
/* Manipulacin de cadenas y movimiento de memoria (memset(), ...). */ 
#include <string.h> 
 
/* Biblioteca estndar de funciones relacionadas con el sistema 
   operativo Unix (read(), write(), getopt(), ...). */ 
#include <unistd.h> 
 
/* Signals (interrupciones) (signal(), SIGINT). */ 
#include <signal.h> 
 
/****************************************************************************** 
 * 
 * Definiciones. 
 * 
 *****************************************************************************/ 
 
/* Retraso por defecto (en milisegundos). */ 
#define DELAY 800 
 
/* Frecuencia de muestreo por defecto. */ 
#define RATE 44100 
 
/* Nmero de canales por defecto. */ 
#define CHANNELS 2 
 
/* Bytes por muestra por defecto. */ 
#define PRECISSION 2 
 
/****************************************************************************** 
 * 
 * Variable globales. 
 * 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Funciones. 
 * 
 *****************************************************************************/ 
 
/* Cuerpo principal del programa. */ 
int main(int argc, char *argv[]) { 
  work(argc, argv); 
  return EXIT_SUCCESS; 
} 
 
work(int argc, char *argv[]) { 
 
  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
     end(). */ 
  void end() { 
    fprintf(stderr,"%s:CTRL+Cdetected.Exiting...",argv[0]); 
    fprintf(stderr,"done\n"); 
    exit(EXIT_SUCCESS); 
  } 
  signal(SIGINT, end);  //activando la senal SIGINT 
 
  /* Retraso en segundos. */ 
  float delay = DELAY; 
 
  /* Frecuencia de muestreo. */ 
  int rate = RATE; 
 
  /* Canales. */ 
  int channels = CHANNELS; 
 
  /* Precisin de las muestras (en bytes). */ 
  int precission = PRECISSION; 
 
  int c; 
  while ((c = getopt (argc, argv, "d:r:c:p:")) != -1) { 
    switch (c) { 
    case d: 
      delay = atof(optarg); 
      break; 
    case r: 
      rate = atoi(optarg); 
      break; 
    case c: 
      channels = atoi(optarg); 
      break; 
    case p: 
      precission = atoi(optarg); 
      break; 
    case ?: 
      if (isprint (optopt)) 
        fprintf (stderr, "Unknownoption‘-%c’.\n", optopt); 
      else 
        fprintf (stderr, 
                 "Unknownoptioncharacter‘\\x%x’.\n", 
                 optopt); 
      return 1; 
    default: 
      abort (); 
    } 
  } 
 
  fprintf(stderr,"%s:delay(inmiliseconds)=\"%f\"\n", 
          argv[0],delay); 
  fprintf(stderr,"%s:samplingrate=\"%d\"\n", 
          argv[0],rate); 
  fprintf(stderr,"%s:numberofchannels=\"%d\"\n", 
          argv[0],channels); 
  fprintf(stderr,"%s:numberofbytespersample=\"%d\"\n", 
          argv[0],precission); 
 
  int buffer_size = (int)(delay*rate*channels*precission/1000.0); 
 
  fprintf(stderr,"%s:buffersize(inbytes)=\"%d\"\n", 
          argv[0],buffer_size); 
 
  char *buffer = (char *)malloc(buffer_size*sizeof(char)); 
  char *beg_ptr = buffer; 
  char *end_ptr = buffer + buffer_size; 
 
  int i; 
  for(i=0; i<buffer_size; i++) { 
    buffer[i] = 0; 
  } 
 
  for(;;) { 
    /* Buffer con el audio */ 
    //char buffer[BUFFER_SIZE]; 
    /* Lemos la stdin */ 
    char buff[512]; 
    write(1, (void *)buffer, 512); 
    int bytes_read = read(0, (void *)buffer, 512); 
    buffer += bytes_read; 
    if(buffer >= end_ptr) buffer = beg_ptr; 
  } 
 
}

7 Sobre la compilacin

Estos son los ficheros Makefile.

7.1 Compilacin de send

 
BIN    =      ../bin 
CFLAGS+=    -g 
LDFLAGS+=    #-lpthread 
EXE    +=    send 
 
all:  $(EXE) 
 
clean: 
        rm -f $(EXE) 
 
bin:  all 
        cp $(EXE) $(BIN)

7.2 Compilacin de receive

 
BIN    =      ../bin 
CFLAGS+=    -g 
LDFLAGS+=    #-lpthread 
EXE    +=    receive 
EXE    +=    join_mcast 
EXE    +=    leave_mcast 
 
all:  $(EXE) 
 
clean: 
        rm -f $(EXE) 
 
bin:  all 
        cp $(EXE) $(BIN)

8 Ejemplos de utilizacin

  1. Visualizando el espectro de un fichero MP3 mientras se reproduce:
    mpg321 file.mp3 -s | send | aplay -t raw -f cd &  
    receive | delay | java RTASA

  2. Visualizando el espectro y el volumen de un fichero MP3 mientras se reproduce:
    mpg321 file.mp3 -s | send -h 224.0.0.1 | aplay -t raw -f cd &  
    receive | delay | java RTASA &  
    receive | delay | java VolMeter &

  3. Captura de audio en un equipo y reproduccin en otro:
    host1$ arecord -f cd | flac - -c | send -h host2 > /dev/null  
    host2$ receive | flac - -d -c | play -f cd

  4. Dos personas, cada una en un host, conversan entre s:
    host1$ arecord -t raw -f cd | oggenc - | send -h host2 > /dev/null &  
    host1$ receive | mplayer -  
     
    host2$ arecord -t raw -f cd | oggenc - | send -h host1 > /dev/null &  
    host2$ receive | mplayer -

  5. Tres personas, cada una en un host (dentro de la misma red local), conversan entre s:
    host1$ arecord -t raw -f cd | oggenc - | send -h 224.0.0.1 -p 6001 > /dev/null &  
    host1$ receive -p 6002 | mplayer -  
    host1$ receive -p 6003 | mplayer -  
     
    host2$ arecord -t raw -f cd | oggenc - | send -h 224.0.0.1 -p 6002 > /dev/null &  
    host2$ receive -p 6001 | mplayer -  
    host2$ receive -p 6003 | mplayer -  
     
    host3$ arecord -t raw -f cd | oggenc - | send -h 224.0.0.1 -p 6003 > /dev/null &  
    host3$ receive -p 6001 | mplayer -  
    host3$ receive -p 6002 | mplayer -

  6. Tres personas, cada una en un host (en cualquier parte de Internet), conversan entre s:
    host1$ arecord -t raw -f cd | oggenc - | send -h host2 -p 6001 -t 255 | send -h host3 -p 6001 -t 255 > /dev/null &  
    host1$ receive -p 6002 | mplayer -  
    host1$ receive -p 6003 | mplayer -  
     
    host2$ arecord -t raw -f cd | oggenc - | send -h host1 -p 6002 -t 255 | send -h host3 -p 6002 -t 255 > /dev/null &  
    host2$ receive -p 6001 | mplayer -  
    host2$ receive -p 6003 | mplayer -  
     
    host3$ arecord -t raw -f cd | oggenc - | send -h host1 -p 6003 -t 255 | send -h host2 -p 6003 -t 255 > /dev/null &  
    host3$ receive -p 6001 | mplayer -  
    host3$ receive -p 6002 | mplayer -