[Audio and Video | ALSA] Developing ALSA application layer program based on alsa-lib – with source code

Blog homepage:https://blog.csdn.net/wkd_007
Blog content:Embedded development, Linux, C language, C++, data structure, audio Video
Content of this article:Developing ALSA application layer program based on alsa-lib
Sharing golden verses:When Gaishi people study, they must first be ambitious, and secondly they must be knowledgeable. , The third thing is to have perseverance. If you have ambition, you will never be willing to be a low-born person; if you have knowledge, you will know that knowledge is endless, and you will not dare to be self-sufficient in one thing. You are like a riverboy looking at the sea, like a frog in a well looking into the sky. They are all ignorant. If there is perseverance, nothing can be accomplished. One of these three is indispensable. –“Zeng Guofan’s Family Letter”

[Audio and Video|ALSA] What is ALSA? ALSA framework detailed introduction
[Audio and Video | ALSA] SS528 development board compiles Linux kernel ALSA driver, transplants alsa-lib, collects and plays USB headset sound

Directory

  • 1. Basic knowledge of ALSA application layer development
  • 2. Common functions of alsa-lib
  • 3. Write ALSA application layer program
    • 3.1 alsa playback program development–alsa-playback.c
    • 3.2 Development of alsa recording audio program–alsa-capture.c
  • 4. XRUN (underrun and overrun)
  • 5. Summary

1. Basic knowledge of ALSA application layer development

  • sample: sample, sampling point. The smallest unit of digital audio, its size is related to the bit width, generally 8bit (1 byte), 16bit (2 bytes);
  • channel: audio channel, generally mono and stereo, and some multi-channel such as 5.1 channel.
  • frame: Frame, a complete sound unit, that is, the data of all channels sampled in a single time. frame=sample*channel;
    For example: 1 frame of 48Khz, 16-bit stereo PCM stream is 4 bytes.
  • sample rate: Sampling rate, that is, the number of samples per second. If the sampling rate is 48kHz, it means that 48,000 frames are sampled per second.
  • period size: Period size, which is the number of frames between each hardware interrupt.
  • buffer size: Buffer size, must be larger than one cycle. Typically 2 times the cycle size. The unit is also the number of frames.

example:
Combining the above knowledge points, here is an example of a 48kHz, 16bit stereo audio stream:

  • For 16bit, each sample is 2 bytes.
  • Stereo means there are 2 channels,
  • 48kHz means 48,000 audio frames per second.

From this, the data size transmitted per second can be calculated: 2 * 2 * 48000=192000 bytes;

Now if ALSA generates a hardware interrupt every second, at the end of each second we need to have 192000 bytes ready;
If it interrupts every half second, for the same stream, we need to have 192000/2 = 96000 bytes ready for each interrupt;
If an interrupt occurs every 100 milliseconds, we need to have 192000*(0.1/1) = 19200 bytes ready for each interrupt.

We can control the generation time of PCM interrupts by setting the cycle size (in frames).
If we set the period size of the 48kHz, 16bit stereo audio stream to 4800 frames (that is, 480022=19200 bytes), then every 19200 bytes will Generate an interrupt, which is 100ms.
Accordingly, buffer size should be at least 2*period_size = 2*4800= 9600 frames (960022 = 38400 bytes).

In actual programming, you may need to calculate the total number of bytes in a cycle period bytes, which is equal to period size multiplied by the size of each frame. Similarly, the total number of bytes in the buffer buffer bytes is equal to buffer size multiplied by one frame size.

If the sampling rate, number of channels, bit width, and number of cycles of the audio are known, then buffer size, buffer time, period size, period time These four values can be inferred from each other:
Taking 48000Hz sampling rate, 2 channels, 16bit, and 4 cycles as an example, the number of frames per second of such an audio stream is 48000. If buffer size is 48000, then buffer time is exactly one second; if buffer time is 500ms, then buffer size is 24000 frames.
And period size=buffer size/number of cycles; period time=buffer time/number of cycles

2. alsa-lib common functions

The function declaration of alsa-lib is in pcm.h, which can be divided into 16 modules in total:

  • PCM Interface
  • Stream Information
  • Hardware Parameters
  • Software Parameters
  • Access Mask Functions
  • Format Mask Functions
  • Subformat Mask Functions
  • Status Functions
  • Description Functions
  • Debug Functions
  • Direct Access (MMAP) Functions
  • Helper Functions
  • Hook Extension
  • Scope Plugin Extension
  • Simple setup functions
  • Deprecated Functions

You can view the corresponding module function description in the official documentation:
https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html

The following table introduces some commonly used functions:

Function Description
snd_pcm_open Open a pcm device according to the pcm device name
snd_pcm_hw_params_malloc Use standard malloc to allocate invalid snd_pcm_hw_params_t
snd_pcm_hw_params_any Fills the parameters with the complete configuration space of the PCM.
snd_pcm_hw_params_set_access Limits the configuration space to contain only one access type.
snd_pcm_hw_params_set_format Limits the configuration space to contain only one format.
snd_pcm_hw_params_set_channels Limits the configuration space to contain only a channel count.
snd_pcm_hw_params_set_rate_near Limits the configuration space to the rate closest to the target.
snd_pcm_hw_params_get_buffer_time_max Extract the maximum buffer time from the configuration space.
snd_pcm_stream Get the stream of PCM handle
snd_pcm_hw_params_set_buffer_time_near Limit the configuration space to keep the buffer time closest to the target.
snd_pcm_hw_params_set_period_time_near Restrict the configuration space to keep the cycle time closest to the target.
snd_pcm_hw_params Install a PCM hardware configuration selected from the configuration space and call snd_pcm_prepare.
snd_pcm_nonblock Set non-blocking mode
snd_pcm_hw_params_get_period_size Extracts the period size from the configuration space.
snd_pcm_hw_params_get_buffer_size Extract the cycle size from the configuration space.
snd_pcm_format_physical_width Returns the bits required to store the PCM sample.

For more function descriptions, refer to:
https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___h_w___params.html
https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html

3. Write ALSA application layer program

This section introduces a simple ALSA application development process and gives example source code.

Before writing code, you can use cat /proc/asound/card0/stream0 to view the parameters supported by the ALSA device:

# cat /proc/asound/card0/stream0
Rapoo Gaming Headset at usb-10300000.xhci_0-1.1, full speed: USB Audio

Playback:
  Status: Stop
  Interface 1
    Altset 1
    Format: S16_LE
    Channels: 2
    Endpoint: 1 OUT (ADAPTIVE)
    Rates: 48000, 44100

Capture:
  Status: Stop
  Interface 2
    Altset 1
    Format: S16_LE
    Channels: 1
    Endpoint: 2 IN (ASYNC)
    Rates: 48000, 44100

Playback: playback device
Capture: recording device
Interface: interface serial number
Format: format
Channels: number of channels
Rates: supported sampling rates

3.1 alsa playback program development-alsa-playback.c

Development Process:

  • 1. Open the device: call snd_pcm_open, specify the type as SND_PCM_STREAM_PLAYBACK, and the device name to open the device;
  • 2. Set hardware parameters
    Set the access method, format, number of channels, sampling rate, buffer time, cycle time, and finally write the parameters to the device;
    If you don’t know how to set some parameters, you can use the command cat /proc/asound/card0/stream0 to view the supported parameters:
  • 3. Play audio
    Each time a cycle-sized byte is written to the alsa driver, fill in 0 if it is less than one cycle;
  • 4. Release resources and shut down the device

The following is a very simple code for ALSA to play audio. Copy it and save it as alsa-playback.c. Use the command aarch64-mix210-linux-gcc alsa-playback.c -I /usr /lib/alsa-lib-1.2.10/include/ -L /usr/lib/alsa-lib-1.2.10/lib/ -l asound -lpthread -ldl -lm -o alsa-playback Compiled pass.

48000Hz-16bit-2ch-ChengDu.pcm file download: https://download.csdn.net/download/wkd_007/88421282

// alsa-playback.c
// aarch64-mix210-linux-gcc alsa-playback.c -I /usr/lib/alsa-lib-1.2.10/include/ -L /usr/lib/alsa-lib-1.2.10/lib/ -l asound -lpthread -ldl -lm -o alsa-playback

/*
* The memory requested by snd_pcm_hw_params_alloca will be automatically released after the function returns, and does not need to be released manually. This function will allocate a piece of memory on the stack. After the function returns, the memory on the stack will be automatically reclaimed.
*/
#include <stdio.h>
#include <alsa/asoundlib.h>

#define PCM_NAME "hw:0,0"
#define PLAYBACK_FILE "48000Hz-16bit-2ch-ChengDu.pcm"

snd_pcm_hw_params_t *hw_params;
static unsigned int rate = 48000; /* stream rate */

int set_hw_params(snd_pcm_t *handle, int format, int channels, snd_pcm_uframes_t *period_frames)
{<!-- -->
int err = -1;
    // Allocate hardware parameter space, call alloca to allocate memory on the stack, and automatically release it after the function ends, no need to call snd_pcm_hw_params_free
    snd_pcm_hw_params_alloca( & amp;hw_params);

    //1. Fill in hardware parameters with default values
    if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {<!-- -->
        return err;
    }

    //2. Restrict a configuration space to contain only real hardware rates.
    if ((err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0)) < 0) {<!-- -->
        return err;
    }

    //3. Set the access method to cross storage
    if ((err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {<!-- -->
        return err;
    }

    //4. Set format, S16_LE, etc.
    if ((err = snd_pcm_hw_params_set_format(handle, hw_params, format)) < 0) {<!-- -->
        return err;
    }

    //5. Set channel
    if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, channels)) < 0) {<!-- -->
        return err;
    }

    //6. Roughly set the sampling rate
    unsigned int rrate;
    rrate =rate;
    if ((err = snd_pcm_hw_params_set_rate_near(handle, hw_params, & amp;rrate, NULL)) < 0) {<!-- -->
        return err;
    }
\t
    //7. Set buffer time
unsigned int buffer_time, period_time;
//Get the cache time first
if((err = snd_pcm_hw_params_get_buffer_time_max(hw_params, & amp;buffer_time, 0))<0){<!-- -->
return err;
}
\t
if (buffer_time > 500000){<!-- -->
buffer_time = 500000; // 500ms to read the entire buffer, combined with the following code, one cycle is buffer_time/4=125ms, and an interrupt will be generated in each cycle
printf("[%s %d] buffer_time=%d, irq=%d\\
",__FILE__,__LINE__,buffer_time, buffer_time/4);
}

//Set buffer time
if ((err = snd_pcm_hw_params_set_buffer_time_near(handle, hw_params, & amp;buffer_time, 0)) < 0) {<!-- -->
return err;
}
    
// 8. Set the cycle time, which is the interruption time
period_time = buffer_time / 4;
if ((err = snd_pcm_hw_params_set_period_time_near(handle, hw_params, & amp;period_time, 0)) < 0) {<!-- -->
return err;
}

    // 9. Write parameters to the device
    if ((err = snd_pcm_hw_params(handle, hw_params)) < 0){<!-- -->
        return err;
    }

    snd_pcm_uframes_t buffer_frames;
    snd_pcm_hw_params_get_buffer_size(hw_params, & amp;buffer_frames);

    if(period_frames != NULL) {<!-- -->
//Get how many frames of data there are in a cycle
if((err =snd_pcm_hw_params_get_period_size(hw_params, period_frames, 0)) < 0){<!-- -->
printf("cannot get period size (%s)\\
", snd_strerror(err));
return err;
}
}
\t
if(err = (snd_pcm_nonblock(handle, 1) < 0)){<!-- -->
return err;
}
\t
// 10. Release the memory allocated by snd_pcm_hw_params_malloc
//snd_pcm_hw_params_free(hw_params);

    return 0;
}

int main()
{<!-- -->
int err = -1;
snd_pcm_t *playback_handle;
snd_pcm_uframes_t period_frames; // Number of frames in one period
\t
// 1. Open the device
if((err = snd_pcm_open( & amp;playback_handle, PCM_NAME, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {<!-- -->
printf("cannot snd_pcm_open (%s)\\
",snd_strerror(err));
return -1;
}
\t
// 2. Set hardware parameters
set_hw_params(playback_handle, SND_PCM_FORMAT_S16_LE, 2, & amp;period_frames);
\t
// 3. Play audio
// 3.1 Open pcm file
int fd = open(PLAYBACK_FILE,O_RDONLY,0644);
if(fd < 0){<!-- -->
printf("open %s error!!!\\
",PLAYBACK_FILE);
return -1;
}
\t
// 3.2 Get the number of bytes in one cycle
const int period_bytes = snd_pcm_frames_to_bytes(playback_handle,period_frames);
char *playback_buf = malloc(period_bytes);
\t
// 3.3 Loop audio
int readframes = 0;
while(readframes = read(fd, playback_buf, period_bytes)) {<!-- -->
//Solve the last cycle data problem
if(readframes < period_bytes) {<!-- -->
memset(playback_buf + readframes, 0, period_bytes-readframes);
}
\t\t
//Write data to PCM and play
err = snd_pcm_writei(playback_handle, playback_buf, period_frames);
if(err == -EPIPE) {<!-- -->
snd_pcm_prepare(playback_handle);
fprintf(stderr, "<<< snd_pcm_writei --> Buffer Underrun >>> \\
");
err = snd_pcm_writei(playback_handle, playback_buf, period_frames);
if(err != period_frames) {<!-- -->
printf("write to audio interface failede err:%d (period_frames:%d)\\
",err,period_frames);
break;
}
}
else if(err != period_frames) {<!-- -->
printf("write to audio interface failede err:%d (period_frames:%d)\\
",err,period_frames);
break;
}
//printf("process:playback wrote %d frames\\
",period_frames);
//usleep(100*1000);
usleep(130*1000); //For testing, if it exceeds 125ms, an error will be reported Underrun
}
\t
// 4. Release resources and close the device
free(playback_buf);
close(fd);
snd_pcm_close(playback_handle);
\t
return 0;
}

3.2 Development of alsa audio recording program – alsa-capture.c

Development Process:

  • 1. Open the device: Call snd_pcm_open, specify the type as SND_PCM_STREAM_CAPTURE, and the device name to open the device;
  • 2. Set hardware parameters
    Set the access method, format, number of channels, sampling rate, buffer time, cycle time, and finally write the parameters to the device;
    If you don’t know how to set some parameters, you can use the command cat /proc/asound/card0/stream0 to view the supported parameters:
  • 3. Read audio
    Each time one cycle-sized byte is read from the alsa driver;
  • 4. Release resources and shut down the device
// alsa-capture.c
// aarch64-mix210-linux-gcc alsa-capture.c -I /usr/lib/alsa-lib-1.2.10/include/ -L /usr/lib/alsa-lib-1.2.10/lib/ -l asound -lpthread -ldl -lm -o alsa-capture

/*
* The memory requested by snd_pcm_hw_params_alloca will be automatically released after the function returns, and does not need to be released manually. This function will allocate a piece of memory on the stack. After the function returns, the memory on the stack will be automatically reclaimed.
*/
#include <stdio.h>
#include <alsa/asoundlib.h>

#define PCM_NAME "hw:0,0"
#define CAPTURE_FILE "alsa-capture.pcm"

snd_pcm_hw_params_t *hw_params;
static unsigned int rate = 48000; /* stream rate */

int set_hw_params(snd_pcm_t *handle, int format, int channels, snd_pcm_uframes_t *period_frames)
{<!-- -->
int err = -1;
    // Allocate hardware parameter space, call alloca to allocate memory on the stack, and automatically release it after the function ends, no need to call snd_pcm_hw_params_free
    snd_pcm_hw_params_alloca( & amp;hw_params);

    //1. Fill in hardware parameters with default values
    if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {<!-- -->
        return err;
    }

    //2. Restrict a configuration space to contain only real hardware rates.
    if ((err = snd_pcm_hw_params_set_rate_resample(handle, hw_params, 0)) < 0) {<!-- -->
        return err;
    }

    //3. Set the access method to cross storage
    if ((err = snd_pcm_hw_params_set_access(handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {<!-- -->
        return err;
    }

    //4. Set format, S16_LE, etc.
    if ((err = snd_pcm_hw_params_set_format(handle, hw_params, format)) < 0) {<!-- -->
        return err;
    }

    //5. Set channel
    if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, channels)) < 0) {<!-- -->
        return err;
    }

    //6. Roughly set the sampling rate
    unsigned int rrate;
    rrate =rate;
    if ((err = snd_pcm_hw_params_set_rate_near(handle, hw_params, & amp;rrate, NULL)) < 0) {<!-- -->
        return err;
    }
\t
    //7. Set buffer time
unsigned int buffer_time, period_time;
//Get the cache time first
if((err = snd_pcm_hw_params_get_buffer_time_max(hw_params, & amp;buffer_time, 0))<0){<!-- -->
return err;
}
\t
if (buffer_time > 500000){<!-- -->
buffer_time = 500000; // It takes 500ms to write the entire buffer. Combined with the following code, one cycle is buffer_time/4=125ms. Each cycle will generate an interrupt.
printf("[%s %d] buffer_time=%d, irq=%d\\
",__FILE__,__LINE__,buffer_time, buffer_time/4);
}

//Set buffer time
if ((err = snd_pcm_hw_params_set_buffer_time_near(handle, hw_params, & amp;buffer_time, 0)) < 0) {<!-- -->
return err;
}
    
// 8. Set the cycle time, which is the interruption time
period_time = buffer_time / 4;
if ((err = snd_pcm_hw_params_set_period_time_near(handle, hw_params, & amp;period_time, 0)) < 0) {<!-- -->
return err;
}

    // 9. Write parameters to the device
    if ((err = snd_pcm_hw_params(handle, hw_params)) < 0){<!-- -->
        return err;
    }

    snd_pcm_uframes_t buffer_frames;
    snd_pcm_hw_params_get_buffer_size(hw_params, & amp;buffer_frames);

    if(period_frames != NULL) {<!-- -->
//Get how many frames of data there are in a cycle
if((err =snd_pcm_hw_params_get_period_size(hw_params, period_frames, 0)) < 0){<!-- -->
printf("cannot get period size (%s)\\
", snd_strerror(err));
return err;
}
}
\t
if(err = (snd_pcm_nonblock(handle, 1) < 0)){<!-- -->
return err;
}
\t
// 10. Release the memory allocated by snd_pcm_hw_params_malloc
//snd_pcm_hw_params_free(hw_params);

    return 0;
}

int main()
{<!-- -->
int err = -1;
snd_pcm_t *capture_handle;
snd_pcm_uframes_t period_frames; // Number of frames in one period
\t
// 1. Open the device
if((err = snd_pcm_open( & amp;capture_handle, PCM_NAME, SND_PCM_STREAM_CAPTURE, 0)) < 0) {<!-- -->
printf("cannot snd_pcm_open (%s)\\
",snd_strerror(err));
return -1;
}
\t
// 2. Set hardware parameters
set_hw_params(capture_handle, SND_PCM_FORMAT_S16_LE, 1, & amp;period_frames);
\t
// 3. Get audio
// 3.1 Open the recording file
int fd = open(CAPTURE_FILE,O_RDWR | O_TRUNC | O_CREAT,0644);
if(fd < 0){<!-- -->
printf("open %s error!!!\\
",CAPTURE_FILE);
return -1;
}
\t
// 3.2 Get the number of bytes in one cycle
const int period_bytes = snd_pcm_frames_to_bytes(capture_handle,period_frames);
char *capture_buf = malloc(period_bytes);
\t
int count = 100; // Capture 100 cycles
int readframes = 0;
while(count--) {<!-- -->
//Read one cycle of data to PCM
memset(capture_buf,0,period_bytes);
if((readframes = snd_pcm_readi(capture_handle, capture_buf, period_frames)) < 0) {<!-- -->
if(readframes == -EPIPE)
printf("read from audio interface failed (%d), overrun, Need to read faster\\
",readframes);
else
printf("read from audio interface failed (%d)\\
",readframes);
break;
}
printf("--process:capture read %d frames\\
",readframes);
write(fd,capture_buf,snd_pcm_frames_to_bytes(capture_handle,readframes));
usleep(100*1000);
//usleep(130*1000); //For testing, if it exceeds 125ms, an error will be reported overrun
}
\t
// 4. Release resources and close the device
free(capture_buf);
close(fd);
snd_pcm_close(capture_handle);
\t
return 0;
}

4. XRUN (underrun and overrun)

In ALSA data transmission, the most common errors are underrun and overrun.

  • underrun: When playing pcm, the interface snd_pcm_writei returns -EPIPE, which is underrun (insufficient)
    This problem occurs because the audio data prepared by the application is not enough. For example, the driver needs 1026 frames of data to play, but the application has only prepared 1024 frames. It can be adjusted according to the sampling rate and buffer size, period size;
  • overrun: When recording audio, the interface snd_pcm_readi returns -EPIPE, which is overrun (overload)
    The alsa driver keeps writing to the buffer, but the application reads very slowly. For example, the driver writes 1026 frames, but the application layer only reads 1024 frames. Need to speed up reading. Or adjust buffer size, period size.

5. Summary

The article introduces the basic knowledge of alsa, as well as the development process of developing ALSA application layer programs based on alsa-lib and common errors during the alsa development process, and provides simple alsa application layer code.

References:
ALSA official website information – FramesPeriods: https://alsa-project.org/main/index.php/FramesPeriods
[Linux & Audio] Alsa Audio Programming [Essence]: https://blog.csdn.net/u012183924/article/details/53407668
ALSA audio data transmission underrun and overrun: https://blog.csdn.net/qq_38350702/article/details/111995039
Linux application development [Chapter 8] ALSA application development: https://blog.csdn.net/thisway_diy/article/details/121809633


If the article is helpful, please like it, collect it, support it, thank you