Difference between revisions of "Упражнение 7. Аудио контролер"

From Ilianko
 
(37 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
== Цел на упражнението ==
 
== Цел на упражнението ==
Запознаване със средствата за работа със звук.
+
Запознаване със средствата за управление на аудио хардуера.
  
Антидот
 
"Музиката, която не изобразява нищо е само шум."
 
[http://en.wikipedia.org/wiki/Jean_le_Rond_d'Alembert|Ж. Даламбер]
 
  
 +
== Play ==
 +
PCM - pulse code modulation ('''ИКМ''' Импулсно Кодова Модулация)
 +
 +
 +
ALSA - Advanced Linux Sound Architecture
 +
 +
ALSA се състои от набор от драйвери за различен аудио хардуер, API функции и инструменти.
 +
 +
 +
Основни стъпки на работа на аудио приложение:
 +
*отваряне на интерфейса за запис или възпроизвеждане;
 +
*задаване на параметрите на хардуера (тип на достъп, формат на данните, канали, честота на семплиране ...);
 +
*докато има данни: прихващане на PCM данни или възпроизвеждане на PCM данни;
 +
*затваряне на интерфейса.
 +
 +
Използвайте Synaptic Package Manager за да инсталирате:
 +
*libasound2-dev;
 +
*ffmpeg.
 +
 +
Преди да компилирате програмата, добавете към командата за изграждане на приложението ''-lasound''  .
  
libasound2-dev
+
''' Задача 1.''' Компилирайте и тествайте програмата Play.
 +
'' '''Упътване:''' Програмата чете от stdin  и изпраща данните към буфера на звуковата карта. За да тествате програмата пренасочете stdin към /dev/urandom. ( $ ./ime_programa < /dev/urandom) ''
  
 
<code><pre>
 
<code><pre>
#include <stdio.h>
+
/*********************************************************************\
#include <stdlib.h>
+
* Title: Play
 +
* Description: Извеждане на аудио информация с ALSA API
 +
* Edited by: ilianko
 +
* Source:  Jeff Tranter, http://www.linuxjournal.com/article/6735
 +
*
 +
\*********************************************************************/
 +
 
 +
/* Use the newer ALSA API */
 +
//#define ALSA_PCM_NEW_HW_PARAMS_API
 
#include <alsa/asoundlib.h>
 
#include <alsa/asoundlib.h>
     
 
int main (int argc, char *argv[])
 
{
 
  /* Handle for the PCM device */
 
  snd_pcm_t *pcm_handle;         
 
  
   /* Playback stream */
+
int main() {
   snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
+
 
 +
  long loops;
 +
  int size, rc;
 +
  snd_pcm_t *handle;
 +
  snd_pcm_hw_params_t *params;
 +
  unsigned int val;
 +
  int dir;
 +
  snd_pcm_uframes_t frames;
 +
  char *buffer;
 +
 
 +
   /* Отваряне на устройство с ИКМ за четене. */
 +
   if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
 +
    fprintf(stderr, "unable to open pcm device. \n");
 +
    exit(1);
 +
  }
  
   /* This structure contains information about    */
+
   /* Задаване на аудио параметрите */
   /* the hardware and can be used to specify the  */     
+
    
   /* configuration to be used for the PCM stream. */  
+
   /* инициализиране на памет за обекта на хардуерните параметри  */
   snd_pcm_hw_params_t *hwparams;
+
   snd_pcm_hw_params_alloca(&params);
  
 +
  /* Зареждане на стойностете по подразбиране */
 +
  snd_pcm_hw_params_any(handle, params);
  
  /* Name of the PCM device, like plughw:0,0          */
+
  /* Interleaved mode */
    /* The first number is the number of the soundcard, */
+
  snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
     /* the second number is the number of the device.   */
+
 
    char *pcm_name;
+
  /* Signed 16-bit little-endian format */
 +
  snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
 +
 
 +
  /* Two channels (stereo) */
 +
  snd_pcm_hw_params_set_channels(handle, params, 2);
 +
 
 +
  /* 44100 bits/second sampling rate (CD quality) */
 +
  val = 44100;
 +
  snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);
 +
 
 +
  /* Set period size to 32 frames. */
 +
  frames = 2;
 +
  snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);
 +
 
 +
  /* Write the parameters to the driver */
 +
  if (snd_pcm_hw_params(handle, params) < 0) {
 +
     fprintf(stderr,"unable to set hw parameters:\n");
 +
    exit(1);
 +
  }
 +
 
 +
  /* Use a buffer large enough to hold one period */
 +
  snd_pcm_hw_params_get_period_size(params, &frames, &dir);
 +
   size = frames * 4; /* 2 bytes/sample, 2 channels */
 +
  buffer = (char *) malloc(size);
 +
 
 +
  /* We want to loop for 5 seconds */
 +
  snd_pcm_hw_params_get_period_time(params, &val, &dir);
 
    
 
    
   /* Init pcm_name. Of course, later you */
+
   /* 5 seconds in microseconds divided by
    /* will make this configurable ;-)    */
+
  * period time */
  pcm_name = strdup("default");
+
  loops = 5000000 / val;
 
+
 
     /* Allocate the snd_pcm_hw_params_t structure on the stack. */
+
  while (loops > 0) {
     snd_pcm_hw_params_alloca(&hwparams);
+
    loops--;
 
+
     /* Четене от stdin */
     /* Open PCM. The last parameter of this function is the mode. */
+
     rc = read(0, buffer, size);
     /* If this is set to 0, the standard mode is used. Possible  */
+
     /* Проверка на буфера */
    /* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC.       */
+
     if (rc == 0) {
    /* If SND_PCM_NONBLOCK is used, read / write access to the    */
+
       fprintf(stderr, "end of file on input\n");
    /* PCM device will return immediately. If SND_PCM_ASYNC is    */
+
      break;
    /* specified, SIGIO will be emitted whenever a period has    */
+
     } else if (rc != size) {
    /* been completely processed by the soundcard.                */
+
       fprintf(stderr,
     if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
+
              "short read: read %d bytes\n", rc);
       fprintf(stderr, "Error opening PCM device %s\n", pcm_name);
 
      return(-1);
 
 
     }
 
     }
      
+
     /* Извеждане на информацията */
      
+
     rc = snd_pcm_writei(handle, buffer, frames);
     /* Init hwparams with full configuration space */
+
     if (rc == -EPIPE) {
     if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
+
      /* EPIPE means underrun */
       fprintf(stderr, "Can not configure this PCM device.\n");
+
      fprintf(stderr, "underrun occurred\n");
       return(-1);
+
      snd_pcm_prepare(handle);
 +
     } else if (rc < 0) {
 +
       fprintf(stderr,
 +
              "error from writei: %s\n",
 +
              snd_strerror(rc));
 +
    }  else if (rc != (int)frames) {
 +
       fprintf(stderr,
 +
              "short write, write %d frames\n", rc);
 
     }
 
     }
   
+
  }
   
+
 
   
+
  snd_pcm_drain(handle);
   
+
  snd_pcm_close(handle);
   
+
  free(buffer);
    unsigned int rate = 44100; /* Sample rate */
+
 
     unsigned int exact_rate;  /* Sample rate returned by */
+
  return 0;
                      /* snd_pcm_hw_params_set_rate_near */  
+
}
    int dir;         /* exact_rate == rate --> dir = 0 */
+
</pre></code>
                      /* exact_rate < rate  --> dir = -1 */
+
[[Image:PCM.png|thumb|300px|right|Импулсно кодова модулация]]
                       /* exact_rate > rate  --> dir = 1 */
+
[[Image:framePeriod.jpg|frame|right|Данни - Период - Фрейм - Канал - Отчет/Sample]]
    int periods = 2;       /* Number of periods */
+
За преобразуване на аудио сигнала в цифров вид, на определен период от време се взема '''отчет''' за нивото на сигнала (PCM -Pulse-Code Modulation - ИКМ Импулсно Кодова Модулация).
    snd_pcm_uframes_t periodsize = 512; /* Periodsize (bytes) */
+
 
   
+
Sample rate (дискретизираща честота) е количеството отчети, които се вземат за 1 секунда. Примерно при дискретизираща честота 44100 kHz (CD-Качество), за една секунда се вземат 44100 отчета. От формАта на модулация се определя, с колко бита се описва всеки отчет.  Примерно  SND_PCM_FORMAT_S16_LE - 16 бита за всеки отчет.
    /* Set sample format */
+
 
    if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) {
+
Каналите (channels) при стерео сигнал са 2, при 5.1 са 6.
      fprintf(stderr, "Error setting format.\n");
+
 
      return(-1);
+
Един фрейм (frame - кадър) съдържа по един отчет за всеки от каналите.
    }
+
 
   
+
Периодът съдържа няколко фрейма, това е обемът от данни, с които наведнъж се зарежда буфера на аудио контролера. След всеки един период (като се просвири), се изпраща прекъсване за ново зареждане на буфера.
    /* Set sample rate. If the exact rate is not supported */
+
 
    /* by the hardware, use nearest possible rate.        */  
+
'''Задача 2. '''Да се изчисли колко байта ще е количеството информация за 80 минути стерео аудио сигнал със CD-качество (използва формат 16 бита за всеки отчет с честота 44.1kHz).
    exact_rate = rate;
+
 
      
+
'''Задача 3.''' Да се свали от Интернет mp3 файл. С програмата ffmpeg  да се декомпресира и декомпресираният файл да се подаде като вход на  програмата Play.
     if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
+
'' '''Упътване:''' Използвайте следния синтаксис: $ffmpeg -i ime.mp3 ime.wav ''.
       fprintf(stderr, "Error setting rate.\n");
+
 
       return(-1);
+
== Capture ==
     }
+
 
   
+
'''Задача 4.''' Да се компилира и тества следващата програма Capture за запис. Да се изпълни програмата като изходът се запише във файл.
    if (rate != exact_rate) {
+
 
       fprintf(stderr, "The rate %d Hz is not supported by your hardware.\n ==> Using %d Hz instead.\n", rate, exact_rate);
+
 
     }
+
<code><pre>
   
+
#define ALSA_PCM_NEW_HW_PARAMS_API
    /* Set number of channels */
+
/*********************************************************************\
    if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0) {
+
* Title: Capture
       fprintf(stderr, "Error setting channels.\n");
+
* Description: Прихващане на аудио информация с ALSA API (5 секунди)
      return(-1);
+
* Edited by: ilianko
 +
* Source:  Jeff Tranter, http://www.linuxjournal.com/article/6735
 +
*
 +
\*********************************************************************/
 +
 
 +
/* Use the newer ALSA API */
 +
#define ALSA_PCM_NEW_HW_PARAMS_API
 +
#include <alsa/asoundlib.h>
 +
 
 +
int main() {
 +
  long loops;
 +
  int rc;
 +
  int size;
 +
  snd_pcm_t *handle;
 +
  snd_pcm_hw_params_t *params;
 +
  unsigned int val;
 +
  int dir;
 +
  snd_pcm_uframes_t frames;
 +
  char *buffer;
 +
 
 +
  /* Open PCM device for recording (capture). */
 +
  rc = snd_pcm_open(&handle, "default",
 +
                    SND_PCM_STREAM_CAPTURE, 0);
 +
  if (rc < 0) {
 +
     fprintf(stderr,
 +
            "unable to open pcm device: %s\n",
 +
            snd_strerror(rc));
 +
    exit(1);
 +
  }
 +
 
 +
   /* Allocate a hardware parameters object. */
 +
  snd_pcm_hw_params_alloca(&params);
 +
 
 +
  /* Fill it in with default values. */
 +
  snd_pcm_hw_params_any(handle, params);
 +
 
 +
  /* Set the desired hardware parameters. */
 +
 
 +
  /* Interleaved mode */
 +
  snd_pcm_hw_params_set_access(handle, params,
 +
                       SND_PCM_ACCESS_RW_INTERLEAVED);
 +
 
 +
  /* Signed 16-bit little-endian format */
 +
  snd_pcm_hw_params_set_format(handle, params,
 +
                              SND_PCM_FORMAT_S16_LE);
 +
 
 +
  /* Two channels (stereo) */
 +
  snd_pcm_hw_params_set_channels(handle, params, 2);
 +
 
 +
  /* 44100 bits/second sampling rate (CD quality) */
 +
  val = 44100;
 +
  snd_pcm_hw_params_set_rate_near(handle, params,
 +
                                  &val, &dir);
 +
 
 +
  /* Set period size to 32 frames. */
 +
  frames = 2;
 +
  snd_pcm_hw_params_set_period_size_near(handle,
 +
                              params, &frames, &dir);
 +
 
 +
  /* Write the parameters to the driver */
 +
  rc = snd_pcm_hw_params(handle, params);
 +
  if (rc < 0) {
 +
    fprintf(stderr,
 +
            "unable to set hw parameters: %s\n",
 +
            snd_strerror(rc));
 +
    exit(1);
 +
  }
 +
 
 +
  /* Use a buffer large enough to hold one period */
 +
  snd_pcm_hw_params_get_period_size(params,
 +
                                      &frames, &dir);
 +
  size = frames * 4; /* 2 bytes/sample, 2 channels */
 +
  buffer = (char *) malloc(size);
 +
 
 +
  /* We want to loop for 5 seconds */
 +
  snd_pcm_hw_params_get_period_time(params,&val, &dir);
 +
  loops = 5000000 / val;
 +
 
 +
  while (loops > 0) {
 +
     loops--;
 +
     rc = snd_pcm_readi(handle, buffer, frames);
 +
    if (rc == -EPIPE) {
 +
      /* EPIPE means overrun */
 +
       fprintf(stderr, "overrun occurred\n");
 +
       snd_pcm_prepare(handle);
 +
     } else if (rc < 0) {
 +
       fprintf(stderr,
 +
              "error from read: %s\n",
 +
              snd_strerror(rc));
 +
     } else if (rc != (int)frames) {
 +
       fprintf(stderr, "short read, read %d frames\n", rc);
 
     }
 
     }
 +
    rc = write(1, buffer, size);
 +
    if (rc != size)
 +
      fprintf(stderr,
 +
              "short write: wrote %d bytes\n", rc);
 +
  }
 +
 +
  snd_pcm_drain(handle);
 +
  snd_pcm_close(handle);
 +
  free(buffer);
 +
 +
  return 0;
 +
}
 +
</pre></code>
 +
 +
'''Задача 5.''' Записания файл да се възпроизведе с програмата Play.
 +
 +
'''Задача 6.''' Изходът от програмата Capture да се подаде директно като вход на програмата Play.
 +
 +
'' '''Упътване:'''Използвайте следния синтаксис:'' ./Capture | ./Play
 +
 +
== Синусоида ==
 +
'''Задача 7. ''' Да се тества следвашата програмата и да се дават различни стойности за честотата на синусоидалния сигнал.
 +
<code><pre>
 +
/*********************************************************************\
 +
* Title: Sinusoid
 +
* Description: Генериране на синусоидален сигнал
 +
* Edited by: ilianko
 +
* Source:  pcm.c
 +
*
 +
\*********************************************************************/
  
    /* Set number of periods. Periods used to be called fragments.  
+
#include <stdio.h>
    if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0) {
+
#include <stdlib.h>
      fprintf(stderr, "Error setting periods.\n");
+
#include <string.h>
      return(-1);
+
#include <errno.h>
    }*/  
+
#include <alsa/asoundlib.h>
   
+
#include <math.h>
   
+
    /* Set buffer size (in frames). The resulting latency is given by */
+
static char *device = "default";                    /* playback device */  
    /* latency = periodsize * periods / (rate * bytes_per_frame)    */
+
static snd_pcm_format_t format = SND_PCM_FORMAT_S16;    /* sample format */  
    if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, (periodsize * periods)>>2) < 0) {
+
static unsigned int rate = 44100;                       /* stream rate */  
      fprintf(stderr, "Error setting buffersize.\n");
+
static unsigned int channels = 2;                      /* count of channels */  
      return(-1);
+
static unsigned int buffer_time = 500000;               /* ring buffer length in us */  
    }
+
static unsigned int period_time = 100000;              /* period time in us */  
   
+
static double freq = 200.100;                              /* sinusoidal wave frequency in Hz */  
    /* Apply HW parameter settings to */
+
   
    /* PCM device and prepare device  */
+
static snd_pcm_sframes_t buffer_size;  
    if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
+
static snd_pcm_sframes_t period_size;
      fprintf(stderr, "Error setting HW params.\n");
+
static snd_output_t *output = NULL;  
      return(-1);
 
    }
 
   
 
   
 
    /* Write num_frames frames from buffer data to    */  
 
    /* the PCM device pointed to by pcm_handle.      */
 
    /* Returns the number of frames actually written. */
 
    // snd_pcm_sframes_t snd_pcm_writei(pcm_handle, data, num_frames);
 
   
 
    unsigned char *data;
 
    int pcmreturn, l1, l2;
 
    short s1, s2, s3, s4;
 
    int frames;
 
  //  int num_frames= 100;
 
  
    data = (unsigned char *)malloc(periodsize);
+
static void generate_sine(const snd_pcm_channel_area_t *areas, 
    frames = periodsize >> 3;
+
                          snd_pcm_uframes_t offset,
    for(l1 = 0; l1 < 10000; l1++) {
+
                          int count, double *_phase)  
     
+
{
      for(l2 = 0; l2 < frames; l2++) {
+
        static double max_phase = 2. * M_PI;  
 +
        double phase = *_phase;
 +
       
 +
      /* generate secret number: */
 +
        double step = max_phase*freq/(double)rate;  
 
          
 
          
         s1 = (l2 % 64)  * 100 - 5000; 
+
         double res;  
        s2 = (l2 % 128) * 100 - 5000;
+
         unsigned char *samples[channels], *tmp;  
         s3 = (l2 % 192) * 100 - 5000; 
 
        s4 = (l2 % 256) * 100 - 5000;  
 
 
          
 
          
         data[8*l2] = (unsigned char)s1;
+
         int steps[channels];  
         data[8*l2+1] = s1 >> 8;
+
         unsigned int chn, byte;  
        data[8*l2+2] = (unsigned char)s2;
 
        data[8*l2+3] = s2 >> 8;
 
 
          
 
          
         data[8*l2+4] = (unsigned char)s4;
+
         int ires;
        data[8*l2+5] = s3 >> 8;
+
        unsigned int maxval = (1 << (snd_pcm_format_width(format) - 1)) - 1;
        data[8*l2+6] = (unsigned char)s3;
+
        int bps = snd_pcm_format_width(format) / 8;  /* bytes per sample */
        data[8*l2+7] = s4 >> 8;
+
       
     
+
        /* verify and prepare the contents of areas */
      }
+
        for (chn = 0; chn < channels; chn++) {
      while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
+
                if ((areas[chn].first % 8) != 0) {
         snd_pcm_prepare(pcm_handle);
+
                        printf("areas[%i].first == %i, aborting...\n", chn, areas[chn].first);
         fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n");
+
                        exit(EXIT_FAILURE);
      }
+
                }
    }
+
                samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8));
    /*
+
                if ((areas[chn].step % 16) != 0) {
 +
                        printf("areas[%i].step == %i, aborting...\n", chn, areas[chn].step);  
 +
                        exit(EXIT_FAILURE);
 +
                }
 +
                steps[chn] = areas[chn].step / 8;  
 +
                samples[chn] += offset * steps[chn];
 +
        }
 +
        /* fill the channel areas */
 +
        while (count-- > 0) {
 +
                res = sin(phase) *sin(phase) * maxval;
 +
                ires = res;
 +
                tmp = (unsigned char *)(&ires);  
 +
                for (chn = 0; chn < channels; chn++) {
 +
                        for (byte = 0; byte < (unsigned int)bps; byte++)
 +
                                *(samples[chn] + byte) = tmp[byte];
 +
                        samples[chn] += steps[chn];
 +
                }
 +
                phase += step;
 +
                if (phase >= max_phase)
 +
                        phase -= max_phase;  
 +
        }
 +
        *_phase = phase;
 +
}  
 +
 +
static int set_hwparams(snd_pcm_t *handle,
 +
                        snd_pcm_hw_params_t *params,
 +
                        snd_pcm_access_t access)
 +
{
 +
        unsigned int rrate;
 +
        snd_pcm_uframes_t size;
 +
        int err, dir;
 +
 +
        /* choose all parameters */
 +
        snd_pcm_hw_params_any(handle, params);
 +
        /* set hardware resampling */
 +
        snd_pcm_hw_params_set_rate_resample(handle, params, 1);
 +
        /* set the interleaved read/write format */
 +
        err = snd_pcm_hw_params_set_access(handle, params, access);
 +
        /* set the sample format */
 +
        err = snd_pcm_hw_params_set_format(handle, params, format);
 +
        /* set the count of channels */
 +
        err = snd_pcm_hw_params_set_channels(handle, params, channels);
 +
        /* set the stream rate */
 +
        rrate = rate;
 +
        err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0);
 +
        if (err < 0) {  
 +
                printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err));
 +
                return err;
 +
        }
 +
        if (rrate != rate) {
 +
                printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err);
 +
                return -EINVAL;
 +
         }
 +
        /* set the buffer time */
 +
        snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);
 +
        snd_pcm_hw_params_get_buffer_size(params, &size);  
 +
          
 +
        buffer_size = size;
 +
        /* set the period time */
 +
        err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);
 +
        if (err < 0) {
 +
                printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err));
 +
                return err;
 +
        }
 +
        err = snd_pcm_hw_params_get_period_size(params, &size, &dir);
 +
        if (err < 0) {
 +
                printf("Unable to get period size for playback: %s\n", snd_strerror(err));
 +
                return err;
 +
        }
 +
        period_size = size;
 +
        /* write the parameters to device */
 +
        err = snd_pcm_hw_params(handle, params);
 +
        if (err < 0) {
 +
                printf("Unable to set hw params for playback: %s\n", snd_strerror(err));
 +
                return err;
 +
        }
 +
        return 0;
 +
}
 +
 +
static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams)
 +
{
 +
        /* get the current swparams */
 +
        snd_pcm_sw_params_current(handle, swparams);
 +
        /* start the transfer when the buffer is almost full: */
 +
        /* (buffer_size / avail_min) * avail_min */
 +
        snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size);
 +
        /* allow the transfer when at least period_size samples can be processed */
 +
        snd_pcm_sw_params_set_avail_min(handle, swparams, period_size);
 +
        /* write the parameters to the playback device */
 +
        snd_pcm_sw_params(handle, swparams);
 +
        return 0;
 +
}
 +
 +
/*
 +
*  Underrun and suspend recovery
 +
*/
 +
 
 +
static int xrun_recovery(snd_pcm_t *handle, int err)
 +
{
 +
        if (err == -EPIPE) {    /* under-run */
 +
                err = snd_pcm_prepare(handle);
 +
                if (err < 0)
 +
                        printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
 +
                return 0;
 +
        } else if (err == -ESTRPIPE) {
 +
                while ((err = snd_pcm_resume(handle)) == -EAGAIN)
 +
                        sleep(1);      /* wait until the suspend flag is released */
 +
                if (err < 0) {
 +
                        err = snd_pcm_prepare(handle);
 +
                        if (err < 0)
 +
                                printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
 +
                }
 +
                return 0;
 +
        }
 +
        return err;
 +
}
 +
 +
/*
 +
*  Transfer method - write only
 +
*/
 +
 +
static int write_loop(snd_pcm_t *handle,
 +
                      signed short *samples,
 +
                      snd_pcm_channel_area_t *areas)
 +
{
 +
        double phase = 0;
 +
        signed short *ptr;
 +
        int err, cptr;
 +
 +
        while (1) {
 +
                generate_sine(areas, 0, period_size, &phase);
 +
                ptr = samples;
 +
                cptr = period_size;
 +
                while (cptr > 0) {
 +
                        err = snd_pcm_writei(handle, ptr, cptr);
 +
                        if (err == -EAGAIN)
 +
                                continue;
 +
                        if (err < 0) {
 +
                                if (xrun_recovery(handle, err) < 0) {
 +
                                        printf("Write error: %s\n", snd_strerror(err));
 +
                                        exit(EXIT_FAILURE);
 +
                                }
 +
                                break;  /* skip one period */
 +
                        }
 +
                        ptr += err * channels;  
 +
                        cptr -= err;
 +
                }
 +
        }  
 +
}  
 +
 
 +
int main(int argc, char *argv[])
 +
{
 +
srand(time(NULL));
 +
        snd_pcm_t *handle;
 +
        snd_pcm_hw_params_t *hwparams;
 +
        snd_pcm_sw_params_t *swparams;
 +
        signed short *samples;
 +
        unsigned int chn;
 +
        snd_pcm_channel_area_t *areas;
 +
 +
        snd_pcm_hw_params_alloca(&hwparams);
 +
        snd_pcm_sw_params_alloca(&swparams);
 +
 +
 +
        if ( snd_output_stdio_attach(&output, stdout, 0)){
 +
                printf("Output failed: %s\n", snd_strerror(0));
 +
                return 0;
 +
        }
 +
 +
        printf("Playback device is %s\n", device);
 +
        printf("Stream parameters are %iHz, %s, %i channels\n", rate, snd_pcm_format_name(format), channels);
 +
        printf("Sine wave rate is %.4fHz\n", freq);
 +
     
 +
        snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0);
 +
        set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
 +
        set_swparams(handle, swparams);
 +
     
 +
        samples = malloc((period_size * channels * snd_pcm_format_width(format)) / 8);
 +
        areas = calloc(channels, sizeof(snd_pcm_channel_area_t));
 +
       
 +
        for (chn = 0; chn < channels; chn++) {
 +
                areas[chn].addr = samples;
 +
                areas[chn].first = chn * snd_pcm_format_width(format);
 +
                areas[chn].step = channels * snd_pcm_format_width(format);
 +
        }
 +
 +
        write_loop(handle, samples, areas);
 
      
 
      
    unsigned char *data;
+
        free(areas);  
    int pcmreturn, l1, l2;
+
        free(samples);  
    short s1, s2;
+
        snd_pcm_close(handle);  
    int frames;
+
        return 0;  
 +
}
 +
</pre></code>
 +
 
 +
== Литература ==
 +
 
 +
http://www.linuxjournal.com/article/6735
  
    data = (unsigned char *)malloc(periodsize);
+
http://www.suse.de/~mana/alsa090_howto.html
    frames = periodsize >> 2;
+
 
    for(l1 = 0; l1 < 10000; l1++) {
+
http://www.alsa-project.org/
      for(l2 = 0; l2 < frames; l2++) {
 
        s1 = (l2 % 128) * 100 - 5000; 
 
        s2 = (l2 % 256) * 100 - 5000; 
 
        data[4*l2] = (unsigned char)s1;
 
        data[4*l2+1] = s1 >> 8;
 
        data[4*l2+2] = (unsigned char)s2;
 
        data[4*l2+3] = s2 >> 8;
 
      }
 
      while ((pcmreturn = snd_pcm_writei(pcm_handle, data, frames)) < 0) {
 
        snd_pcm_prepare(pcm_handle);
 
        fprintf(stderr, "<<<<<<<<<<<<<<< Buffer Underrun >>>>>>>>>>>>>>>\n");
 
      }
 
    }*/
 
  return 0;
 
}
 
  
</pre></code>
+
http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html
 
  
 +
[http://en.wikipedia.org/wiki/PulseAudio PulseAudio - надстройка на ALSA]
  
 
[[Category:Компютърна периферия]]
 
[[Category:Компютърна периферия]]
 +
 +
==Антидот==
 +
"Музиката, която не изобразява нищо е само шум."
 +
[http://en.wikipedia.org/wiki/Jean_le_Rond_d'Alembert Ж. Даламбер]

Latest revision as of 08:29, 29 April 2011

Цел на упражнението

Запознаване със средствата за управление на аудио хардуера.


Play

PCM - pulse code modulation (ИКМ Импулсно Кодова Модулация)


ALSA - Advanced Linux Sound Architecture

ALSA се състои от набор от драйвери за различен аудио хардуер, API функции и инструменти.


Основни стъпки на работа на аудио приложение:

  • отваряне на интерфейса за запис или възпроизвеждане;
  • задаване на параметрите на хардуера (тип на достъп, формат на данните, канали, честота на семплиране ...);
  • докато има данни: прихващане на PCM данни или възпроизвеждане на PCM данни;
  • затваряне на интерфейса.

Използвайте Synaptic Package Manager за да инсталирате:

  • libasound2-dev;
  • ffmpeg.

Преди да компилирате програмата, добавете към командата за изграждане на приложението -lasound .

Задача 1. Компилирайте и тествайте програмата Play. Упътване: Програмата чете от stdin и изпраща данните към буфера на звуковата карта. За да тествате програмата пренасочете stdin към /dev/urandom. ( $ ./ime_programa < /dev/urandom)

/*********************************************************************\
 * Title: Play
 * Description: Извеждане на аудио информация с ALSA API 
 * Edited by: ilianko
 * Source:  Jeff Tranter, http://www.linuxjournal.com/article/6735
 * 
\*********************************************************************/

/* Use the newer ALSA API */
//#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>

int main() {
  
  long loops;
  int size, rc;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
  char *buffer;

  /* Отваряне на устройство с ИКМ за четене. */
  if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) {
    fprintf(stderr, "unable to open pcm device. \n");
    exit(1);
  }

  /* Задаване на аудио параметрите */
  
  /* инициализиране на памет за обекта на хардуерните параметри  */
  snd_pcm_hw_params_alloca(&params);

  /* Зареждане на стойностете по подразбиране */
  snd_pcm_hw_params_any(handle, params);

  /* Interleaved mode */
  snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

  /* Signed 16-bit little-endian format */
  snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

  /* Two channels (stereo) */
  snd_pcm_hw_params_set_channels(handle, params, 2);

  /* 44100 bits/second sampling rate (CD quality) */
  val = 44100;
  snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

  /* Set period size to 32 frames. */
  frames = 2;
  snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

  /* Write the parameters to the driver */
  if (snd_pcm_hw_params(handle, params) < 0) {
    fprintf(stderr,"unable to set hw parameters:\n");
    exit(1);
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params, &frames, &dir);
  size = frames * 4; /* 2 bytes/sample, 2 channels */
  buffer = (char *) malloc(size);

  /* We want to loop for 5 seconds */
  snd_pcm_hw_params_get_period_time(params, &val, &dir);
  
  /* 5 seconds in microseconds divided by
   * period time */
  loops = 5000000 / val;

  while (loops > 0) {
    loops--;
    /* Четене от stdin */
    rc = read(0, buffer, size);
    /* Проверка на буфера */
    if (rc == 0) {
      fprintf(stderr, "end of file on input\n");
      break;
    } else if (rc != size) {
      fprintf(stderr,
              "short read: read %d bytes\n", rc);
    }
    /* Извеждане на информацията */
    rc = snd_pcm_writei(handle, buffer, frames);
    if (rc == -EPIPE) {
      /* EPIPE means underrun */
      fprintf(stderr, "underrun occurred\n");
      snd_pcm_prepare(handle);
    } else if (rc < 0) {
      fprintf(stderr,
              "error from writei: %s\n",
              snd_strerror(rc));
    }  else if (rc != (int)frames) {
      fprintf(stderr,
              "short write, write %d frames\n", rc);
    }
  }

  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}
Импулсно кодова модулация
Данни - Период - Фрейм - Канал - Отчет/Sample

За преобразуване на аудио сигнала в цифров вид, на определен период от време се взема отчет за нивото на сигнала (PCM -Pulse-Code Modulation - ИКМ Импулсно Кодова Модулация).

Sample rate (дискретизираща честота) е количеството отчети, които се вземат за 1 секунда. Примерно при дискретизираща честота 44100 kHz (CD-Качество), за една секунда се вземат 44100 отчета. От формАта на модулация се определя, с колко бита се описва всеки отчет. Примерно SND_PCM_FORMAT_S16_LE - 16 бита за всеки отчет.

Каналите (channels) при стерео сигнал са 2, при 5.1 са 6.

Един фрейм (frame - кадър) съдържа по един отчет за всеки от каналите.

Периодът съдържа няколко фрейма, това е обемът от данни, с които наведнъж се зарежда буфера на аудио контролера. След всеки един период (като се просвири), се изпраща прекъсване за ново зареждане на буфера.

Задача 2. Да се изчисли колко байта ще е количеството информация за 80 минути стерео аудио сигнал със CD-качество (използва формат 16 бита за всеки отчет с честота 44.1kHz).

Задача 3. Да се свали от Интернет mp3 файл. С програмата ffmpeg да се декомпресира и декомпресираният файл да се подаде като вход на програмата Play. Упътване: Използвайте следния синтаксис: $ffmpeg -i ime.mp3 ime.wav .

Capture

Задача 4. Да се компилира и тества следващата програма Capture за запис. Да се изпълни програмата като изходът се запише във файл.


#define ALSA_PCM_NEW_HW_PARAMS_API
/*********************************************************************\
 * Title: Capture
 * Description: Прихващане на аудио информация с ALSA API (5 секунди)
 * Edited by: ilianko
 * Source:  Jeff Tranter, http://www.linuxjournal.com/article/6735
 * 
\*********************************************************************/

/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>

int main() {
  long loops;
  int rc;
  int size;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
  char *buffer;

  /* Open PCM device for recording (capture). */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_CAPTURE, 0);
  if (rc < 0) {
    fprintf(stderr,
            "unable to open pcm device: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Allocate a hardware parameters object. */
  snd_pcm_hw_params_alloca(&params);

  /* Fill it in with default values. */
  snd_pcm_hw_params_any(handle, params);

  /* Set the desired hardware parameters. */

  /* Interleaved mode */
  snd_pcm_hw_params_set_access(handle, params,
                      SND_PCM_ACCESS_RW_INTERLEAVED);

  /* Signed 16-bit little-endian format */
  snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_S16_LE);

  /* Two channels (stereo) */
  snd_pcm_hw_params_set_channels(handle, params, 2);

  /* 44100 bits/second sampling rate (CD quality) */
  val = 44100;
  snd_pcm_hw_params_set_rate_near(handle, params,
                                  &val, &dir);

  /* Set period size to 32 frames. */
  frames = 2;
  snd_pcm_hw_params_set_period_size_near(handle,
                              params, &frames, &dir);

  /* Write the parameters to the driver */
  rc = snd_pcm_hw_params(handle, params);
  if (rc < 0) {
    fprintf(stderr,
            "unable to set hw parameters: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params,
                                      &frames, &dir);
  size = frames * 4; /* 2 bytes/sample, 2 channels */
  buffer = (char *) malloc(size);

  /* We want to loop for 5 seconds */
  snd_pcm_hw_params_get_period_time(params,&val, &dir);
  loops = 5000000 / val;

  while (loops > 0) {
    loops--;
    rc = snd_pcm_readi(handle, buffer, frames);
    if (rc == -EPIPE) {
      /* EPIPE means overrun */
      fprintf(stderr, "overrun occurred\n");
      snd_pcm_prepare(handle);
    } else if (rc < 0) {
      fprintf(stderr,
              "error from read: %s\n",
              snd_strerror(rc));
    } else if (rc != (int)frames) {
      fprintf(stderr, "short read, read %d frames\n", rc);
    }
    rc = write(1, buffer, size);
    if (rc != size)
      fprintf(stderr,
              "short write: wrote %d bytes\n", rc);
  }

  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}

Задача 5. Записания файл да се възпроизведе с програмата Play.

Задача 6. Изходът от програмата Capture да се подаде директно като вход на програмата Play.

Упътване:Използвайте следния синтаксис: ./Capture | ./Play

Синусоида

Задача 7. Да се тества следвашата програмата и да се дават различни стойности за честотата на синусоидалния сигнал.

/*********************************************************************\
 * Title: Sinusoid
 * Description: Генериране на синусоидален сигнал
 * Edited by: ilianko
 * Source:  pcm.c
 * 
\*********************************************************************/

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h> 
#include <alsa/asoundlib.h>
#include <math.h> 
 
static char *device = "default";                     /* playback device */ 
static snd_pcm_format_t format = SND_PCM_FORMAT_S16;    /* sample format */ 
static unsigned int rate = 44100;                       /* stream rate */ 
static unsigned int channels = 2;                       /* count of channels */ 
static unsigned int buffer_time = 500000;               /* ring buffer length in us */ 
static unsigned int period_time = 100000;               /* period time in us */ 
static double freq = 200.100;                               /* sinusoidal wave frequency in Hz */ 
 
static snd_pcm_sframes_t buffer_size; 
static snd_pcm_sframes_t period_size; 
static snd_output_t *output = NULL; 

static void generate_sine(const snd_pcm_channel_area_t *areas,  
                          snd_pcm_uframes_t offset, 
                          int count, double *_phase) 
{ 
        static double max_phase = 2. * M_PI; 
        double phase = *_phase;
        
       /* generate secret number: */
        double step = max_phase*freq/(double)rate; 
        
        double res; 
        unsigned char *samples[channels], *tmp; 
        
        int steps[channels]; 
        unsigned int chn, byte; 
        
        int ires; 
        unsigned int maxval = (1 << (snd_pcm_format_width(format) - 1)) - 1; 
        int bps = snd_pcm_format_width(format) / 8;  /* bytes per sample */ 
         
        /* verify and prepare the contents of areas */ 
        for (chn = 0; chn < channels; chn++) { 
                if ((areas[chn].first % 8) != 0) { 
                        printf("areas[%i].first == %i, aborting...\n", chn, areas[chn].first); 
                        exit(EXIT_FAILURE); 
                } 
                samples[chn] = /*(signed short *)*/(((unsigned char *)areas[chn].addr) + (areas[chn].first / 8)); 
                if ((areas[chn].step % 16) != 0) { 
                        printf("areas[%i].step == %i, aborting...\n", chn, areas[chn].step); 
                        exit(EXIT_FAILURE); 
                } 
                steps[chn] = areas[chn].step / 8; 
                samples[chn] += offset * steps[chn]; 
        } 
        /* fill the channel areas */ 
        while (count-- > 0) { 
                res = sin(phase) *sin(phase) * maxval; 
                ires = res; 
                tmp = (unsigned char *)(&ires); 
                for (chn = 0; chn < channels; chn++) { 
                        for (byte = 0; byte < (unsigned int)bps; byte++) 
                                *(samples[chn] + byte) = tmp[byte]; 
                        samples[chn] += steps[chn]; 
                } 
                phase += step; 
                if (phase >= max_phase) 
                        phase -= max_phase; 
        } 
        *_phase = phase; 
} 
 
static int set_hwparams(snd_pcm_t *handle, 
                        snd_pcm_hw_params_t *params, 
                        snd_pcm_access_t access) 
{ 
        unsigned int rrate; 
        snd_pcm_uframes_t size; 
        int err, dir; 
 
        /* choose all parameters */ 
        snd_pcm_hw_params_any(handle, params); 
        /* set hardware resampling */ 
        snd_pcm_hw_params_set_rate_resample(handle, params, 1); 
        /* set the interleaved read/write format */ 
        err = snd_pcm_hw_params_set_access(handle, params, access); 
        /* set the sample format */ 
        err = snd_pcm_hw_params_set_format(handle, params, format); 
        /* set the count of channels */ 
        err = snd_pcm_hw_params_set_channels(handle, params, channels); 
        /* set the stream rate */ 
        rrate = rate; 
        err = snd_pcm_hw_params_set_rate_near(handle, params, &rrate, 0); 
        if (err < 0) { 
                printf("Rate %iHz not available for playback: %s\n", rate, snd_strerror(err)); 
                return err; 
        } 
        if (rrate != rate) { 
                printf("Rate doesn't match (requested %iHz, get %iHz)\n", rate, err); 
                return -EINVAL; 
        } 
        /* set the buffer time */ 
        snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir); 
        snd_pcm_hw_params_get_buffer_size(params, &size); 
        
        buffer_size = size; 
        /* set the period time */ 
        err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir); 
        if (err < 0) { 
                printf("Unable to set period time %i for playback: %s\n", period_time, snd_strerror(err)); 
                return err; 
        } 
        err = snd_pcm_hw_params_get_period_size(params, &size, &dir); 
        if (err < 0) { 
                printf("Unable to get period size for playback: %s\n", snd_strerror(err)); 
                return err; 
        } 
        period_size = size; 
        /* write the parameters to device */ 
        err = snd_pcm_hw_params(handle, params); 
        if (err < 0) { 
                printf("Unable to set hw params for playback: %s\n", snd_strerror(err)); 
                return err; 
        } 
        return 0; 
} 
 
static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams) 
{ 
        /* get the current swparams */ 
        snd_pcm_sw_params_current(handle, swparams); 
        /* start the transfer when the buffer is almost full: */ 
        /* (buffer_size / avail_min) * avail_min */ 
        snd_pcm_sw_params_set_start_threshold(handle, swparams, (buffer_size / period_size) * period_size); 
        /* allow the transfer when at least period_size samples can be processed */ 
        snd_pcm_sw_params_set_avail_min(handle, swparams, period_size); 
        /* write the parameters to the playback device */ 
        snd_pcm_sw_params(handle, swparams); 
        return 0; 
} 
 
/* 
 *   Underrun and suspend recovery 
 */ 
  
static int xrun_recovery(snd_pcm_t *handle, int err) 
{ 
        if (err == -EPIPE) {    /* under-run */ 
                err = snd_pcm_prepare(handle); 
                if (err < 0) 
                        printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err)); 
                return 0; 
        } else if (err == -ESTRPIPE) { 
                while ((err = snd_pcm_resume(handle)) == -EAGAIN) 
                        sleep(1);       /* wait until the suspend flag is released */ 
                if (err < 0) { 
                        err = snd_pcm_prepare(handle); 
                        if (err < 0) 
                                printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err)); 
                } 
                return 0; 
        } 
        return err; 
} 
 
/* 
 *   Transfer method - write only 
 */ 
 
static int write_loop(snd_pcm_t *handle, 
                      signed short *samples, 
                      snd_pcm_channel_area_t *areas) 
{ 
        double phase = 0; 
        signed short *ptr; 
        int err, cptr; 
 
        while (1) { 
                generate_sine(areas, 0, period_size, &phase); 
                ptr = samples; 
                cptr = period_size; 
                while (cptr > 0) { 
                        err = snd_pcm_writei(handle, ptr, cptr); 
                        if (err == -EAGAIN) 
                                continue; 
                        if (err < 0) { 
                                if (xrun_recovery(handle, err) < 0) { 
                                        printf("Write error: %s\n", snd_strerror(err)); 
                                        exit(EXIT_FAILURE); 
                                } 
                                break;  /* skip one period */ 
                        } 
                        ptr += err * channels; 
                        cptr -= err; 
                } 
        } 
} 
  
int main(int argc, char *argv[]) 
{ 
	srand(time(NULL));
        snd_pcm_t *handle; 
        snd_pcm_hw_params_t *hwparams; 
        snd_pcm_sw_params_t *swparams; 
        signed short *samples; 
        unsigned int chn; 
        snd_pcm_channel_area_t *areas; 
 
        snd_pcm_hw_params_alloca(&hwparams); 
        snd_pcm_sw_params_alloca(&swparams); 
 
 
        if ( snd_output_stdio_attach(&output, stdout, 0)){ 
                printf("Output failed: %s\n", snd_strerror(0)); 
                return 0; 
        } 
 
        printf("Playback device is %s\n", device); 
        printf("Stream parameters are %iHz, %s, %i channels\n", rate, snd_pcm_format_name(format), channels); 
        printf("Sine wave rate is %.4fHz\n", freq); 
       
        snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0);
        set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
        set_swparams(handle, swparams);
       
        samples = malloc((period_size * channels * snd_pcm_format_width(format)) / 8); 
        areas = calloc(channels, sizeof(snd_pcm_channel_area_t)); 
         
        for (chn = 0; chn < channels; chn++) { 
                areas[chn].addr = samples; 
                areas[chn].first = chn * snd_pcm_format_width(format); 
                areas[chn].step = channels * snd_pcm_format_width(format); 
        } 
 
        write_loop(handle, samples, areas); 
    
        free(areas); 
        free(samples); 
        snd_pcm_close(handle); 
        return 0; 
} 

Литература

http://www.linuxjournal.com/article/6735

http://www.suse.de/~mana/alsa090_howto.html

http://www.alsa-project.org/

http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html

PulseAudio - надстройка на ALSA

Антидот

"Музиката, която не изобразява нищо е само шум."

Ж. Даламбер