Recording sound using ALSA and C/C++ for embedded systems

1. System preparation

  • Embedded system with Debian based OS such Raspberry Pi in this tutorial
  • Microphone, to check whether your system having any microphone or not, run command below
sudo arecord -L
hw:CARD=Device,DEV=0
     USB PnP Sound Device, USB Audio
     Direct hardware device without any conversions
plughw:CARD=Device,DEV=0
     USB PnP Sound Device, USB Audio
     Hardware device with all software conversions
usbstream:CARD=Device
     USB PnP Sound Device
     USB Stream Output

2. Install libraries

sudo apt install -y libsndfile1-dev libasound2-dev

3. Code

3.1 Include

#include <stdio.h>
#include <stdint.h>
#include <sys/time.h>
#include <unistd.h>
#include <alsa/asoundlib.h>

3.2 Define for default values

#define RECORD_SAMPLE_RATE  22000
#define RECORD_RATE_OFFSET  0
#define RECORD_FRAME        32

3.3 Global variables

snd_pcm_t *_soundDevice;
snd_pcm_uframes_t frames;
int cycleSize;

3.4 Initialize

int recorder_init(const char *name, unsigned int rate, snd_pcm_uframes_t fr){
  int err;
  int dir;
  snd_pcm_hw_params_t *hw_params;  
  unsigned int actualRate = RECORD_SAMPLE_RATE + RECORD_RATE_OFFSET;
  unsigned int usCycle = 0;  

  /* Open PCM device for recording (capture). */
  printf("[%lu] [Recorder] Open device \"%s\" for recording...\r\n",time(NULL),name);
  if( name == NULL ){
    // Try to open the default device
    err = snd_pcm_open( &_soundDevice, "default", SND_PCM_STREAM_CAPTURE, 0 );
  }
  else{
    // Open the device we were told to open.
    err = snd_pcm_open (&_soundDevice, name, SND_PCM_STREAM_CAPTURE, 0);
  }

  // Check for error on open.
  if( err < 0 ){
    printf("[%lu] [Recorder] Cannot open audio device %s ( %s )...Try default device...\r\n",time(NULL),name,snd_strerror (err));
    // Try to open the default device
    err = snd_pcm_open( &_soundDevice, "default", SND_PCM_STREAM_CAPTURE, 0 );
  }

  // Check for error on open.
  if( err < 0 ){
    printf("[%lu] [Recorder] Cannot open default audio device ( %s )\r\n",time(NULL),snd_strerror (err));
    return -1;
  }

  // Allocate the hardware parameter structure.
  if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0){
    printf("[%lu] [Recorder] Cannot allocate hardware parameter structure ( %s )\r\n",time(NULL),snd_strerror (err));
    return -2;
  }

  /* Fill it in with default values. */
  if ((err = snd_pcm_hw_params_any (_soundDevice, hw_params)) < 0){
    printf("[%lu] [Recorder] Cannot initialize hardware parameter structure ( %s )",time(NULL),snd_strerror (err));
    return -3;
  }

  /* Set the desired hardware parameters. */
  // Set access to RW interleaved.
  if ((err = snd_pcm_hw_params_set_access (_soundDevice, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0){
    printf("[%lu] [Recorder] Cannot set access type ( %s )\r\n",time(NULL),snd_strerror (err));
    return -4;
  }

  // Signed 16-bit little-endian format
  if ((err = snd_pcm_hw_params_set_format (_soundDevice, hw_params, SND_PCM_FORMAT_S16_LE)) < 0){
    printf("[%lu] [Recorder] Cannot set sample format ( %s )\r\n",time(NULL),snd_strerror (err));
    return -5;
  }
  // Set channels to stereo (2).
  if ((err = snd_pcm_hw_params_set_channels (_soundDevice, hw_params, 2)) < 0){
    printf("[%lu] [Recorder] Cannot set channel count ( %s )\r\n",time(NULL),snd_strerror (err));
    return -6;
  }
  // Set sample rate.
  actualRate = rate + RECORD_RATE_OFFSET;
  if ((err = snd_pcm_hw_params_set_rate_near (_soundDevice, hw_params, &actualRate, &dir)) < 0){
    printf("[%lu] [Recorder] Cannot set sample rate to %u. ( %s )\r\n",time(NULL),rate,snd_strerror (err));
    return -7;
  }
  if ((err = snd_pcm_hw_params_get_rate(hw_params, &actualRate, &dir)) < 0){
    printf("[%lu] [Recorder] Cannot get sample rate: ( %s )\r\n",time(NULL),snd_strerror (err));
    return -8;
  }
  if( actualRate < rate ){
    printf("[%lu] [Recorder] Sample rate does not match requested rate ( %u requested, %u acquired ).\r\n",time(NULL),rate,actualRate);
  }
  // Set period size to 32 frames.
  frames = fr;
  snd_pcm_hw_params_set_period_size_near(_soundDevice, hw_params,&frames, &dir);
  // Write the parameters to the driver
  if ((err = snd_pcm_hw_params (_soundDevice, hw_params)) < 0){
    printf("[%lu] [Recorder] Cannot write the parameters to the driver ( %s )\r\n",time(NULL),snd_strerror (err));
    return -9;
  }
  // Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(hw_params, &frames,&dir);
  cycleSize = frames * 1; /* 2 bytes/sample, 2 channels */
  /* Extract period time from a configuration space */
  snd_pcm_hw_params_get_period_time(hw_params,&usCycle, &dir);
  printf("[%lu] [Recorder] Rate       = %d,\tframe   = %lu\r\n",time(NULL),actualRate,frames);
  printf("[%lu] [Recorder] cycleSize  = %d,\tusCycle = %d\r\n",time(NULL),cycleSize,usCycle);
  printf("[%lu] [Recorder] Audio device has been prepared for use.\r\n",time(NULL));

  return cycleSize;
}

3.5 Deinitialize

int recorder_uninit(){
  snd_pcm_drain (_soundDevice);
  snd_pcm_close (_soundDevice);
  printf("[%lu] [Recorder] Audio device has been uninitialized.\r\n",time(NULL));
  return 0;
}

3.6 Read frame

int recorder_readFrame(int16_t *buff, int len){
  int16_t *cycleBuff;
  int rc;
  cycleBuff = (int16_t *) malloc(cycleSize*sizeof(cycleBuff));
  if(cycleBuff == NULL){
    return 0;
  }
  memset(cycleBuff,0,cycleSize*sizeof(cycleBuff));
  memset(buff,0,len);
  rc = snd_pcm_readi(_soundDevice, cycleBuff, frames);
  if(rc == -EPIPE){
    /* EPIPE means overrun */
    //printf("[%lu] [Recorder] Overrun!\r\n",time(NULL));
    snd_pcm_prepare(_soundDevice);
    free(cycleBuff);
    return 0;
  }
  else{
    if(rc != (int)frames){
      printf("[%lu] [Recorder] Short read, read %d frames\r\n",time(NULL),rc);
    }        
  }
  if(len>cycleSize){
    memcpy(buff,cycleBuff,cycleSize * sizeof(cycleBuff));
  }
  else{
    memcpy(buff,cycleBuff,len * sizeof(cycleBuff));
  }        
  free(cycleBuff);
  return cycleSize;
}

3.7 Main program

In this example, “plughw:CARD=Device,DEV=0” is my audio device, replace it to fit into your system.

int main(){
  // Recorder settings
  snd_pcm_uframes_t record_frame = RECORD_FRAME;
  int record_rate                = RECORD_SAMPLE_RATE;  
  int record_len                 = 0;
  // Data frame
  int16_t *dat;
  int dat_len;

  // Init recorder
  record_len = recorder_init("plughw:CARD=Device,DEV=0",record_rate,record_frame);
  if(record_len<0){
    printf("[%lu] Cannot init recorder...\r\n",time(NULL));
    return -1;
  }
  // Allocate buffer
  dat = (int16_t *) malloc(record_len*sizeof(dat));
  if(dat){
    dat_len = recorder_readFrame(dat,record_len);
    if(dat_len>0){
      printf("[%lu] Recoreded %d bytes: ",time(NULL),dat_len);
      for(int i = 0; i < dat_len; i++){
        printf("%d ",dat[i]);
      }
      printf("\r\n");
    }
    free(dat);
  }
  // Release resource
  recorder_uninit();
  return 0;
}

3.8 Compile

gcc record.c -o rec -lsndfile -lasound

3.9 Test

sudo ./rec
[1641549685] [Recorder] Open device "plughw:CARD=Device,DEV=0" for recording…
[1641549685] [Recorder] Rate       = 22001,    frame   = 32
[1641549685] [Recorder] cycleSize  = 32,    usCycle = 1496
[1641549685] [Recorder] Audio device has been prepared for use.
[1641549685] Recoreded 32 bytes: 0 0 0 0 0 0 -1 -1 2 2 -2 -2 3 3 -3 -3 2 2 1 1 -6 -6 13 13 -22 -22 32 32 -41 -41 47 47 
[1641549685] [Recorder] Audio device has been uninitialized.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *