Modulación de secuencias de audio

Vicente González Ruiz

September 26, 2014

Contents

1 El espectro de una seal de audio
2 Operaciones en el dominio del tiempo y de la frecuencia
3 Modulacin = desplazamiento en la frecuencia del espectro
4 El modulador de seales
5 Procesado en tiempo cuasi-real
6 modulator.c

En esta prctica vamos a utilizar los pipes de Unix para transmitir datos entre las aplicaciones ”arecord” y ”aplay”, realizando un procesamiento de la seal de audio transferida.

1 El espectro de una seal de audio

Las seales de audio, como el resto de seales unidimensionales y dependientes del tiempo, pueden representarse tanto en el dominio del tiempo como en el dominio de la frecuencia. En el primero, una muestra de audio indica, para el instante de tiempo que referencia la muestra, la amplitud del sonido. En el segundo, un coeficiente especifica, para todo el tiempo que comprende la ventana de anlisis, la amplitud de la sinusoide de frecuencia igual al ndice del coeficiente, que dicho sonido.

2 Operaciones en el dominio del tiempo y de la frecuencia

En el caso de las seales de audio, el dominio del tiempo y el dominio de la frecuencia son equivalentes y por tanto, una determinada operacin que se realiza en uno de los dominios siempre tiene una equivalente en el otro.

Un ejemplo de esto lo encontramos en el filtrado de seales. Cuando filtramos, en el dominio de la frecuencia lo que realizamos (atenuando unas determinadas frecuencias con respecto a otras) es multiplicar el espectro de la seal de audio (la que queremos filtrar) con el espectro de la funcin de transferencia de filtro (la funcin que resulta de hacer pasar a travs del filtro la funcin impulso unitario). Como sabemos, por el teorema de convolucin, multiplicar los espectros de dos seales es equivalente a convolucionar las transformadas inversas de dichos espectros en el dominio del tiempo, es decir, para filtrar una seal en el dominio del tiempo podemos convolucionar dicha seal con la respuesta del filtro a la funcin impulso unitario.

3 Modulacin = desplazamiento en la frecuencia del espectro

Por definicin, modular una seal es desplazar su espectro en el dominio de la frecuencia. Para conseguir dicho desplazamiento en ω0 Hz, desde el punto de vista de la teora de seales, podemos convolucionar el espectro (por tanto, en el dominio de la frecuencia) con la funcin impulso unitario de frecuencia ω0 Hz. Sea dicha funcin δ(ω0).

Aplicando de nuevo el teorema de convolucin, dicha convolucin sera equivalente a multiplicar la seal por la transformada inversa de la funcin impulso unitario δ(ω0). Por definicin, la funcin cuyo espectro es una funcin impulso es una funcin exponencial compleja. Como esta funcin no existe realmente podemos “simularla” utilizando una sinusoide que, aunque presenta dos impulsos, es perfectamente vlida al no poderse representar fsicamente las frecuencias negativas.

Por tanto, para desplazar el espectro de una seal de audio debemos multiplicar cada una de las muestras por una funcin sinusoidal de una determinada frecuencia. La cantidad de desplazamiento que sufra el espectro va a depender de la frecuencia de la funcin sinusoidal, que por cierto, se suele llamar en el contexto de la modulacin de seales seal moduladora y seal portadora en el contexto de la transmisin de seales.

4 El modulador de seales

Un modulador de seales es un sistema al que entra una seal (la seal a modular, la seal de datos original) y del que sale otra seal (la seal modulada, la nueva seal de datos). Adems, normalmente se le especifica la frecuencia de la seal moduladora como un parmetro de entrada (frecuencia de la seal moduladora o portadora).

Como ya se indic al comienzo de este documento, vamos a utilizar los pipes de Unix para leer de la tarjeta de sonido una seal, modularla y a continuacin reproducirla. Este sistema queda construido escribiendo:

arecord -f cd -t raw | modulator | aplay -f cd -t raw

Ntese que sin el flag -t raw modularamos tambin la cabecera WAV.

5 Procesado en tiempo cuasi-real

Esta forma de procesar la seales es muy cmoda pero tiene un ligero inconveniente: el retraso que los pipes introducen. Los pipes son colas FIFO de un cierto tamao (4K bytes normalmente). Esto significa que el tiempo que transcurre desde que la seal es capturada hasta que la modulada es reproducida es apreciable y puede suponer un problema en algunos contextos. Adems, a esta latencia hay que sumar la que ya poseen las aplicaciones arecord y aplay.

Para minimizar estos problemas podemos hacer varias cosas:

  1. Reducir el tamao de las colas de los pipes. Esto se hace normalmente modificando los fuentes del kernel, compilndolo y usndolo. Un camino largo y tenebroso, sin duda.
  2. Reducir el tamao de los buffers de las aplicaciones ”arecord” y ”aplay”. Esto se hace emplando el flag ”-B” que especifica el tamao de los buffers de audio en microsegundos (vase la documentacin de estas aplicaciones).
  3. Utilizando la mxima frecuencia de muestreo posible. De esta forma llenaremos los buffers y colas lo antes posible y la latencia ser menor.

Por tanto, para minimizar la latencia usando una tarjeta de sonido tpica escribiremos:

arecord -r 48000 -c 2 -f S16_LE -B 0 | modulator | aplay -r 48000 -c 2 -f S16_LE -B 0  
# Ojo, repasar lo del -B

6 modulator.c

Muesto modulador va a ser un programa escrito en C que lea un conjunto de muestras de la entrada estndar, realice la modulacin tal y como se ha indicado anteriormente y finalmente escriba las muestras procesadas a la salida estndar. Para controlar la frecuencia de la seal moduladora podemos usar la aplicacin FreqControl desarrollada para la prctica anterior. Adems, el cdigo de modulator.c es muy similar al de sinusoidal.c porque ambos escriben en la salida estndar una sea que ellos generan. Bien, a continuacin aparece el cdigo de modulator.c:

 
/* 
 * modulator.c 
 * 
 * Lee desde la entrada estndar una seal de audio (digital), a 16 bits 
 * por muestra, usando el endian de la mquina. Modula la seal a 
 * partir de una frecuencia especificada por uno o ms clientes que la 
 * especifican a travs de un socket TCP. Finalmente escribe la seal 
 * modulada en la salida estndard. 
 * 
 * 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(), ...). */ 
#include <unistd.h> 
 
/* Biblioteca estndar de clculos matemticos en punto flotante 
   (sin(), rint(), ...).*/ 
#include <math.h> 
 
/* POSIX Threads (pthread_t, pthread_create(), ...). */ 
#include <pthread.h> 
 
/* Sockets. */ 
/* Ms info en: 
 * 
 * http://www.csce.uark.edu/~aapon/courses/os/examples/concurrentserver.c 
 * http://beej.us/guide/bgnet/ 
 */ 
 
/* Tipos de datos primitivos del sistema. */ 
#include <sys/types.h> 
 
/* Sockets (socket(), listen(), ...). */ 
#include <sys/socket.h> 
 
/* Direcciones IP, opciones y definiciones. */ 
#include <netinet/in.h> 
 
/* Servicio de resolucin de nombres. */ 
#include <netdb.h> 
 
/* Signals (interrupciones) (signal(), SIGINT). */ 
#include <signal.h>  /* signal(), SIGINT */ 
 
/* Manejo de parmetros desde la lnea de comandos. */ 
#include <getopt.h> 
 
/****************************************************************************** 
 * 
 * Definiciones. 
 * 
 *****************************************************************************/ 
 
/* Nmero de bytes del buffer de audio. */ 
#define BUFFER_SIZE 32 
 
/* Nmero de muestras por segundo (calidad CD). */ 
#define SAMPLES_PER_SECOND 44100 
 
/* Frecuencia por defecto en Hz. */ 
#define DEFAULT_FREQ 440 
 
/* Dos canales. */ 
#define NUMBER_OF_CHANNELS 2 
 
/* Puerto usado para atender a los clientes. */ 
#define LISTENING_PORT 6789 
 
/* Puerto destino de los paquetes UDP enviados a la direccin de broadcast. */ 
#define MULTICAST_PORT 6789 
 
/* Direccin multicast por defecto. Todos los clientes escuchan dicha 
   direccin multicast. */ 
#define MULTICAST_ADDR (char *)"224.0.0.1" 
 
/* Nmero mximo de clientes que esperan a que la conexin sea 
   establecida. */ 
#define QLEN 1 
 
/****************************************************************************** 
 * 
 * Variables globales. 
 * 
 *****************************************************************************/ 
 
/****************************************************************************** 
 * 
 * Funciones. 
 * 
 *****************************************************************************/ 
 
/* Cuerpo principal del programa. */ 
int main(int argc, char *argv[]) { 
  work(argc, argv); 
  return EXIT_SUCCESS; 
} 
 
/* Funcin que realiza todo el trabajo. */ 
work(int argc, char *argv[]) { 
 
  /* Si pulsamos CRTL+C, el programa acaba ejecutando la funcin 
     end(). */ 
  void end() { 
    fprintf(stderr,"%s: CTRL+C detected. Exiting ... ",argv[0]); 
    fprintf(stderr,"done\n"); 
    exit(EXIT_SUCCESS); 
  } 
  signal(SIGINT, end); 
 
  /* Un semforo para acceder "en exclusin mutua" a una zona de 
     cdigo. */ 
  pthread_mutex_t mut; 
 
  /* Creamos el semforo. */ 
  pthread_mutex_init(&mut, NULL); 
 
  /* Frecuencia de la seal sinusoidal en Hz. */ 
  int freq = DEFAULT_FREQ; 
 
  /* Puerto de escucha de los clientes. */ 
  short listening_port = LISTENING_PORT; 
 
  /* Puerto destino de los datagramas multicast. */ 
  short multicast_port = MULTICAST_PORT; 
 
  /* Canal multicast destino. */ 
  char *multicast_addr = MULTICAST_ADDR; 
 
  /* Manipulacin de los parmetros de entrada. */ { 
    int c; 
    while(1) { 
 
      static struct option long_options[] = { 
        {"frequency", required_argument, 0, f’}, 
        {"listening_port", required_argument, 0, l’}, 
        {"multicast_addr", required_argument, 0, a’}, 
        {"multicast_port", required_argument, 0, m’}, 
        {"help", no_argument, 0, ’?’}, 
        {0, 0, 0, 0} 
      }; 
 
      int option_index = 0; 
 
      c = getopt_long(argc, argv, "f:l:a:m:?", long_options, &option_index); 
      if(c==-1) { 
        /* No more options. */ 
        break; 
      } 
 
      switch (c) { 
      case 0: 
        /* If this option set a flag, do nothing else now. */ 
        if (long_options[option_index].flag != 0) 
          break; 
        fprintf(stderr, "option %s", long_options[option_index].name); 
        if (optarg) 
          fprintf(stderr, " with arg %s", optarg); 
        fprintf(stderr, "\n"); 
        break; 
 
      case f’: 
        freq = atoi(optarg); 
        break; 
 
      case l’: 
        listening_port = atoi(optarg); 
        break; 
 
      case a’: 
        multicast_addr = optarg; 
        break; 
 
      case m’: 
        multicast_port = atoi(optarg); 
        break; 
 
      case ’?’: 
        fprintf(stdout, 
                " -[-f]requency = frequency (in Hz) of the modulating signal (%d)\n", 
                freq); 
        fprintf(stdout, 
                " -[-l]istening port = port to serve the ModulatorControl clients (%d)\n", 
                listening_port); 
        fprintf(stdout, 
                " -[-]multicast_[a]ddress = multicast address to comunicate "); 
        fprintf(stdout, 
                "to the clients a change in the value of the frequency (%s)\n", 
                multicast_addr); 
        fprintf(stdout, 
                " -[-]multicast_[p]ort = port to which the multicast "); 
        fprintf(stdout, 
                "packets are sent (%d)\n", 
                multicast_port); 
        exit(1); 
 
      default: 
        fprintf(stderr, "%s: Unrecognized argument. Aborting ...\n", argv[0]); 
        abort(); 
      } 
    } 
  } 
 
  fprintf(stderr, "%s: frequency = %d Hz\n" ,argv[0], freq); 
  fprintf(stderr, "%s: listening port = %d\n", argv[0], listening_port); 
  fprintf(stderr, "%s: multicast address = %s\n", argv[0], multicast_addr); 
  fprintf(stderr, "%s: multicast port = %d\n", argv[0], multicast_port); 
 
  /* Hilo que realiza la sntesis de la sinusoide. */ 
  void *modu(void *arg) { 
 
    /* Angulo de la sinusoide, en radianes. */ 
    double angle_in_radians = 0; 
 
    /* La sinusoide ser generada hasta pulsar CTRL+C. */ 
    for(;;) { 
      int i; 
 
      /* El buffer de audio. */ 
      short buffer[BUFFER_SIZE]; 
 
      /* Leemos la entrada estndar. */ 
      int bytes_read; 
      bytes_read = read(0/* stdin */, buffer, BUFFER_SIZE*sizeof(buffer[0])); 
 
      /* Nmero de muestras ledas (sumando ambos canales). */ 
      int samples = bytes_read / sizeof(buffer[0]); 
 
      //fprintf(stderr,"%d\n",samples); 
 
      /* Media del canal izquierdo y derecho. */ 
      double ave_l = 0.0, ave_r = 0.0; 
      for(i=0; i<samples; i+=2) { 
        ave_l += buffer[i]; 
        ave_r += buffer[i+1]; 
      } 
      ave_l /= samples; 
      ave_r /= samples; 
 
      //fprintf(stderr,"*%f %f\n",ave_l, ave_r); 
 
      /* Eliminamos la componente continua en cada canal. */ 
      for(i=0; i<samples; i+=2) { 
        buffer[i] -= ave_l; 
        buffer[i+1] -= ave_r; 
      } 
 
      ave_l = 0; ave_r = 0; 
      for(i=0; i<samples; i+=2) { 
        ave_l += buffer[i]; 
        ave_r += buffer[i+1]; 
      } 
      ave_l /= samples; 
      ave_r /= samples; 
 
      //fprintf(stderr,"%f %f\n",ave_l, ave_r); 
 
      /* Modulamos. */ 
      for(i=0; i<samples; i+=2) { 
 
        /* Calculamos la portadora. */ 
        double portadora = sin(angle_in_radians); 
 
        /* Multiplicamos el valor de cada canal por el valor de la 
           portadora. */ 
        buffer[i] *= portadora; 
        buffer[i+1] *= portadora; 
 
        /* Angulo de la siguiente muestra de la portadora. */ 
        angle_in_radians += 2*M_PI*freq/SAMPLES_PER_SECOND; 
      } 
 
      /* Escribimos buffer en la salida estndar. */ 
      write(1/* stdout */, (void *)buffer, bytes_read); 
    } 
 
    /* Finalizamos el thread. */ 
    pthread_exit(0); 
 
  } 
 
  /* Lanzamos el hilo anterior. */ 
  pthread_t modu_tid; 
  if(pthread_create(&modu_tid, NULL, modu, NULL) == -1) { 
    perror("pthread_create"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Creamos el socket TCP de escucha. */ 
  int listen_sd; { 
 
    /* Obtenemos el nmero del protocolo. */ 
    struct protoent *ptrp; /* Pointer to a protocol table entry. */ 
    ptrp = getprotobyname("tcp"); 
    if((long)(ptrp) == 0) { 
      perror("getprotobyname"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Creamos el socket. */ 
    listen_sd = socket (PF_INET, SOCK_STREAM, ptrp->p_proto); 
    if(listen_sd < 0) { 
      perror("socket"); 
      exit(EXIT_FAILURE); 
    } 
 
    /* Usaremos el puerto de servicio sin esperar a que ste est 
       libre. */ 
    int yes = 1; 
    if(setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0) { 
      perror("setsockopt"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Asignamos una direccin (dir IP, puerto) al socket de escucha. */ { 
    struct sockaddr_in sad;              /* Direccin del servidor. */ 
    memset((char  *)&sad,0,sizeof(sad)); /* Borramos la estructura. */ 
    sad.sin_family = AF_INET;            /* Usaremos Internet. */ 
    sad.sin_addr.s_addr = INADDR_ANY;    /* Cualquiera de las IP 
                                            asignadas al host vale. */ 
    sad.sin_port = 
      htons((u_short)LISTENING_PORT);    /* Asignamos el puerto de escucha. */ 
    if (bind(listen_sd, (struct sockaddr *)&sad, sizeof (sad)) < 0) { 
      perror("bind"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Comenzamos a escuchar. */ 
  if (listen(listen_sd, QLEN) < 0) { 
    perror("listen"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Creamos el socket UDP. */ 
  int info_sd; 
  if((info_sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { 
    perror("socket"); 
    exit(EXIT_FAILURE); 
  } 
 
  /* Dir IP y puerto del socket. */ 
  struct sockaddr_in info_addr; { 
    /* Usaremos Internet. */ 
    info_addr.sin_family = AF_INET; 
    /* IP multicast destino de los datagramas. */ 
    info_addr.sin_addr.s_addr = inet_addr(multicast_addr); 
    /* Puerto destino de los datagramas. */ 
    info_addr.sin_port = htons(multicast_port); 
    memset(&(info_addr.sin_zero), ’\0’, 8); 
  } 
 
  //fprintf(stderr,"%s: Frequency value = \"%d\"\n",  argv[0], freq); 
 
  /* Lazo del servidor. */ 
  while(1) { 
 
    fprintf(stderr,"%s: waiting for a connection ...\n", argv[0]); 
 
    /* Socket para "servir" a los clientes. */ 
    long serve_sd; 
    /* Esperamos a que un cliente se conecte. */ { 
      struct sockaddr_in cad;        /* Clients address. */ 
      socklen_t alen = sizeof(cad);  /* Tamao de la direccin. */ 
      serve_sd = accept(listen_sd, (struct sockaddr *)&cad, &alen); 
      if(serve_sd<0) { 
        perror("accept"); 
        exit (EXIT_FAILURE); 
      } 
    } 
 
    /* Hilo que controla a un cliente. */ 
    void *service(void *arg) { 
 
      /* El socket de conmunicacin con el cliente. Ntese que cada 
         cliente posee una variable "sd" diferente. */ 
      long sd = (long)arg; 
 
      /* Mientras la frecuencia recibida sea >= 0. */ 
      for(;;) { 
        int new_freq; 
        receive_from_client(sd, &new_freq); 
        if(new_freq<0) break; 
 
        /* Seccin crtica. */ 
        pthread_mutex_lock(&mut); 
        freq = new_freq; 
        fprintf(stderr,"%s: Frequency value = \"%d\"\n",  argv[0], freq); 
        pthread_mutex_unlock(&mut); 
        /* Fin de la seccin crtica. */ 
 
        inform_to_client(info_sd, info_addr, freq); 
      } 
 
      /* Cerramos la conexin con el cliente. */ 
      close(sd); 
 
      /* Finalizamos el thread. */ 
      pthread_exit(0); 
    } 
 
    /* Lanzamos el hilo. */ 
    pthread_t service_tid; /* Thread identificator. */ 
    if(pthread_create(&service_tid, NULL, service, (void *)serve_sd) == -1) { 
      perror("pthread_create"); 
      exit(EXIT_FAILURE); 
    } 
  } 
 
  /* Cerramos el socket de escucha (cuando pulsamos CTRL+C). */ 
  close(listen_sd); 
} 
 
/* Enva un paquete UDP con la frecuencia. */ 
inform_to_client(int sd, struct sockaddr_in addr, int freq) { 
  unsigned char message[2]; 
  //fprintf(stderr,"--> %d\n",freq); 
  message[0] = freq/256;//((freq & 0x000F) >>  0); 
  message[1] = freq%256;//((freq & 0x00F0) >>  8); 
  //message[2] = 3;//((freq & 0x0F00) >> 16); 
  //message[3] = 4;//((freq & 0xF000) >> 24); 
  sendto(sd, message, 2, 0,(struct sockaddr *) &addr, sizeof(addr)); 
} 
 
/* Recibe del cliente la nueva frecuencia. */ 
receive_from_client(int sd, int *freq) { 
  char message[256]; 
  read_text_line(sd, message); 
  sscanf(message,"%d",freq); 
} 
 
/* Lee una lnea de texto ASCII. */ 
read_text_line(int sd, char *str) { 
  int n; 
  do { 
    n = recv(sd, str, 1, 0); 
  } while (n>0 && *str++ != ’\n’); 
}

Y de su Makefile:

 
%.class: %.java 
        javac -classpath ../Server $*.java 
 
%: %.c 
        gcc $< -o $@ -lm -lpthread 
 
%: %.py 
        cp $*.py $@ 
 
EXE =  modulator \ 
        ModulatorControl.class \ 
        ModulatorControl \ 
        ../Server/InFromServer.class \ 
        ../Server/OutToServer.class 
 
all:  $(EXE) 
 
clean: 
        rm -f $(EXE) 
 
install: 
        cp $(EXE) *.class ~/bin 
 
publish: 
        rm -rf /tmp/modulator 
        svn export . /tmp/modulator 
        tar --create --file=/tmp/modulator.tar -C /tmp modulator 
        gzip -9 /tmp/modulator.tar 
        scp /tmp/modulator.tar.gz www.ace.ual.es:~/public_html/imyso 
        rm /tmp/modulator.tar.gz