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 theperiod 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 least2*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 toperiod size
multiplied by the size of each frame. Similarly, the total number of bytes in the bufferbuffer bytes
is equal tobuffer 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. Ifbuffer size
is 48000, thenbuffer time
is exactly one second; ifbuffer time
is 500ms, thenbuffer size
is 24000 frames.
Andperiod 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 asSND_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 commandcat /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 asSND_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 commandcat /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 andbuffer 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 adjustbuffer 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