Упражнение 7. Аудио контролер

From Ilianko

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

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


Теория

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

Импулсно кодова модулация

ALSA - Advanced Linux Sound Architecture

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


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

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

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

  • libasound2-dev;
  • ffmpeg.

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

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

/* 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 playback. */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_PLAYBACK, 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 = 32;
  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);
  /* 5 seconds in microseconds divided by
   * period time */
  loops = 50000000 / val;

  while (loops > 0) {
    loops--;
    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 - кадър) съдържа по един отчет за всеки от кадрите.

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


#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 = 32;
  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;
}

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <errno.h> 
#include <alsa/asoundlib.h>
#include <math.h> 
 
static char *device = "hw:0";                     /* 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; 
} 


Антидот

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

Даламбер