Audio application programming

The ALPHA I.MX6U development board supports audio. The board is equipped with the audio codec chip WM8960, which supports playback and recording functions!
In this chapter, we will learn audio application programming under Linux. Audio application programming is more difficult than what was introduced in the previous chapters. However, the author only introduces you to the basic knowledge of Linux audio application programming, and more Details and more in-depth content need to be learned by everyone.

ALSA overview

ALSA is the abbreviation of Advanced Linux Sound Architecture (Advanced Linux Sound Architecture). It has now become the
mainstream audio architecture under Linux, providing audio and MIDI support, replacing the OSS (Development Sound System) in the original old version; Readers who have studied Linux audio driver development must know this; in fact, ALSA is a standard and advanced audio driver framework under Linux system, so the design of this framework itself is relatively complicated, and it is designed with separation and layered thinking So I won’t introduce the specific details to you! As audio application programming, we don't need to study this.
At the application layer, ALSA provides us with a set of standard APIs. The application only needs to call these APIs to complete the control of the underlying audio hardware devices, such as playback, recording, etc. This set of APIs is called alsa-lib. As shown below:
Figure 28.1.1 alsa audio diagram

Introduction to alsa-lib

如上所述,alsa-lib 是一套Linux 应用层的C 语言函数库,为音频应用程序开发提供了一套统一、标准的接口,应用程序只需调用这一套API 即可完成对底层声卡设备的操控,譬如播放与录音。
用户空间的alsa-lib 对应用程序提供了统一的API 接口,这样可以隐藏驱动层的实现细节,简化了应用程序的实现难度、无需应用程序开发人员直接去读写音频设备节点。所以本章,对于我们来说,学习音频应用编程其实就是学习alsa-lib 库函数的使用、如何基于alsa-lib 库函数开发音频应用程序。
ALSA 提供了关于alsa-lib 的使用说明文档,其链接地址为:https://www.alsa-project.org/alsa-doc/alsa-lib/,进入到该链接地址后,如下所示:
Insert image description here
alsa-lib 库支持功能比较多,提供了丰富的API 接口供应用程序开发人员调用,根据函数的功能、作用将这些API 进行了分类,可以点击上图中Modules 按钮查看其模块划分,如下所示:
Insert image description here
一个分类就是一个模块(module),有些模块下可能该包含了子模块,譬如上图中,模块名称前面有三角箭头的表示该模块包含有子模块。
⚫ Global defines and functions:包括一些全局的定义,譬如函数、宏等;
⚫ Constants for Digital Audio Interfaces:数字音频接口相关的常量;
⚫ Input Interface:输入接口;
⚫ Output Interface:输出接口;
⚫ Error handling:错误处理相关接口;
⚫ Configuration Interface:配置接口;
⚫ Control Interface: control interface;
⚫ PCM Interface: PCM device interface;
⚫ RawMidi Interface: RawMidi interface;
⚫ Timer Interface: timer interface;
⚫ Hardware Dependant Interface: hardware-related interface;
⚫ MIDI Sequencer: MIDI sequencer;
⚫ External PCM plugin SDK: external PCM plug-in SDK;
⚫ External Control Plugin SDK: external control plug-in SDK;
⚫ Mixer Interface: mixer interface;
⚫ Use Case Interface: use case interface;
⚫ Topology Interface: topology interface.
It can be seen that alsa-lib does provide a lot of interfaces and modules. The author of many of the modules listed above is not very clear about their specific functions and functions, but in this chapter we only cover the three modules. API functions, including: PCM Interface,
Error Interface and Mixer Interface.
PCM Interface
PCM Interface provides operating interfaces related to PCM devices, such as opening/closing PCM devices, configuring PCM device hardware or software parameters, and controlling PCM devices (start, pause, resume, write/read data), under this module Some submodules are also included, as shown below:
Figure 28.2.3 Submodules under PCM Interface

Click on the module name to see the API interfaces provided by the module and the corresponding function descriptions. I will not demonstrate them here!
Error Interface
This module provides interfaces related to error handling. For example, when an error occurs in a function call, you can call the function provided under this module to print error description information.
Mixer Interface
provides a series of operating interfaces related to the mixer, such as volume, channel control, gain, etc.

sound device node

Sound devices registered in the Linux kernel device driver layer and based on the ALSA audio driver framework will generate corresponding device node files in the /dev/snd directory. For example, the ALPHA I.MX6U development board factory system has the following files in the /dev/snd directory:
Figure 28.3.1 Files in the /dev/snd directory

Tips: Note that the Mini I.MX6U development board does not have these files in the /dev/snd directory of the factory system. Because the Mini board does not support audio and does not have an onboard audio codec chip, the experimental routines in this chapter cannot be performed on the Mini board. Testing, please be informed!
As you can see from the picture above, there are the following device files:
⚫ controlC0: device node used for sound card control, such as channel selection, mixer, microphone control, etc. C0 represents sound card 0
(card0);
⚫ pcmC0D0c: used for recording PCM device node. Among them, C0 represents card0, which is sound card 0; and D0 represents device
0, which is device 0; the last letter c is the abbreviation of capture, which means recording; so pcmC0D0c is the
recording device 0 in the system's sound card 0;
⚫ pcmC0D0p: PCM device node used for playback (or playback, playback). Among them, C0 represents card0, which is sound card 0; and D0 represents device 0, which is device 0; the last letter p is the abbreviation of playback, which means playback; so pcmC0D0p is the playback
device 0 in the system's sound card 0;
⚫ pcmC0D1c: PCM device node for recording. Corresponds to recording device 1 in sound card 0 of the system;
⚫ pcmC0D1p: PCM device node used for playback. Corresponds to playback device 1 in sound card 0 of the system.
⚫ timer: timer.
Although the application program we wrote in this chapter calls the alsa-lib library function to control the underlying audio hardware, it is ultimately implemented to perform
I/O operations on the sound device node, but alsa-lib has already packaged it for us. There are many files in the /proc/asound directory of the Linux system. These files record information related to the sound card in the system, as shown below:
Insert image description here
cards:
Use the "cat /proc/asound/cards" command to view the contents of the cards file to list the available and registered sound cards in the system, as shown below:

cat /proc/asound/cards

Figure 28.3.3 View all sound cards registered in the system

We have only one sound card (WM8960 audio codec) on our Alpha board, so it is numbered 0, which is card0. All sound cards registered in the system will have a corresponding directory in the /proc/asound/ directory. The directory is named cardX (X represents the number of the sound card), such as card0 in Figure 28.3.2; the card0 directory is recorded Information related to sound card 0, such as the name of the sound card and the PCM devices registered by the sound card, are as follows:
Insert image description here
devices:
Lists all devices registered by the sound card in the system, including control, pcm, timer, seq, etc. As follows:

cat /proc/asound/devices

Figure 28.3.5 List all devices

pcm:
List all PCM devices in the system, including playback and capture:

cat /proc/asound/pcm

Figure 28.3.6 List all PCM devices in the system

alsa-lib porting

因为alsa-lib 是ALSA 提供的一套Linux 下的C 语言函数库,需要将alsa-lib 移植到开发板上,这样基于alsa-lib 编写的应用程序才能成功运行,除了移植alsa-lib 库之外,通常还需要移植alsa-utils,alsa-utils 包含了一些用于测试、配置声卡的工具。
事实上,ALPHA I.MX6U 开发板出厂系统中已经移植了alsa-lib 和alsa-utils,本章我们直接使用出厂系统移植好的alsa-lib 和alsa-utils 进行测试,笔者也就不再介绍移植过程了。其实它们的移植方法也非常简单,如果你想自己尝试移植,网上有很多参考,大家可以自己去看看。
alsa-utils 提供了一些用于测试、配置声卡的工具,譬如aplay、arecord、alsactl、alsaloop、alsamixer、
amixer 等,在开发板出厂系统上可以直接使用这些工具,这些应用程序也都是基于alsa-lib 编写的。
aplay
aplay 是一个用于测试音频播放功能程序,可以使用aplay 播放wav 格式的音频文件,如下所示:
Insert image description here
程序运行之后就会开始播放音乐,因为ALPHA 开发板支持喇叭和耳机自动切换,如果不插耳机默认从喇叭播放音乐,插上耳机以后喇叭就会停止播放,切换为耳机播放音乐,这个大家可以自己进行测试。
需要注意的是,aplay 工具只能解析wav 格式音频文件,不支持mp3 格式解码,所以无法使用aplay 工具播放mp3 音频文件。稍后笔者会向大家介绍如何基于alsa-lib 编写一个简单地音乐播放器,实现与aplay
相同的效果。
alsamixer
alsamixer is a very important tool for configuring the mixer of the sound card. It is a character-based graphical configuration tool. Run the alsamixer command directly on the serial port terminal of the development board to open the graphical configuration interface, as shown below:
Figure 28.4.2 alsamixer interface

alsamixer can configure the mixer of the sound card. "Card: wm8960-audio" in the upper left corner indicates that the currently configured sound card is wm8960-
audio. If there are multiple sound cards registered in your system, you can press F6 to select.
Press the H key to view the operating instructions of the interface, as shown below:
Insert image description here
Different sound cards support different mixer configuration options. This is related to the specific hardware and requires hardware support! The picture above shows the configuration items supported by the development board WM8960 sound card, including Playback playback and Capture recording. The View in the upper left corner prompts:
View: F3: [Playback] F4: Capture F5: All
means that the currently displayed is [Playback] Configuration items, press F4 to switch to Capture, or press F5 to display all configuration items.
Tips: When you press the F4 or F5 button on the terminal, you may exit the configuration interface directly. This reason may be that the F4 or F5 shortcut key is occupied by other programs. You can try using ssh to remotely log in to the development board under the Ubuntu system. Then execute the alsamixer
program in the Ubuntu ssh terminal. The author tested F4 and F5 and they were normal.
The prompt at Item in the upper left corner:
Item: Headphone [dB gain: -8.00, -8.00]
indicates that the Headphone configuration item is currently selected. You can switch to other configuration items through the LEFT (left) and RIGHT (right) keys on the keyboard. . When the user modifies the configuration item, only the selected configuration item can be modified, and the content in the square brackets [dB gain: -7.00, -7.00] shows the current configuration value of the configuration item.
The above picture only lists some of them, and some configuration items are not displayed. You can view the remaining configuration items by moving the left and right keys. The WM8960 sound card supports a lot of configuration items, including playback volume, headphone volume, speaker volume, capture recording volume, channel enable, ZC, AC, DC, ALC, 3D, etc. There are so many configuration items that the author does not understand many of them. Understand. Some of these configuration items and their descriptions are listed below:

Headphone:耳机音量,使用上(音量增加)、下(音量降低)按键可以调节播放时耳机输出的音量大小,当然可以通过Q(左声道音量增加)、Z(左声道音量降低)按键单独调节左声道音量或通过E(右声道音量增加)、C(右声道音量降低)按键单独调节右声道音量。
Headphone Playback ZC:耳机播放ZC(交流),通过M 键打开或关闭ZC。
Speaker:喇叭播放音量,音量调节方法与Headphon 相同。
Speaker AC:喇叭ZC,通过上下按键可调节大小。
Speaker DC:喇叭DC,通过上下按键可调节大小。
Speaker Playback ZC:喇叭播放ZC,通过M 键打开或关闭ZC。
Playback:播放音量,播放音量作用于喇叭、也能作用于耳机,能同时控制喇叭和耳机的输出音量。调节方法与Headphon 相同。
Capture:采集音量,也就是录音时的音量大小,调节方法与Headphon 相同。
其它的配置项就不再介绍了,笔者也看不懂,后面会用到时再给大家解释!
开发板出厂系统中有一个配置文件/var/lib/alsa/asound.state,这其实就是WM8960 声卡的配置文件,每当开发板启动进入系统时会自动读取该文件加载声卡配置;而每次系统关机时,又会将声卡当前的配置写入到该文件中进行保存,以便下一次启动时加载。加载与保存操作其实是通过alsactl 工具完成的,稍后向大家介绍。
alsactl
配置好声卡之后,如果直接关机,下一次重启之后之前的设置都会消失,必须要重新设置,所以我们需要对配置进行保存,如何保存呢?可通过alsactl 工具完成。
使用alsactl 工具可以将当前声卡的配置保存在一个文件中,这个文件默认是/var/lib/alsa/asound.state,譬如使用alsactl 工具将声卡配置保存在该文件中:

alsactl -f /var/lib/alsa/asound.state store

-f 选项指定保存在哪一个文件中,当然也可以不用指定,如果不指定则使用alsactl 默认的配置文件
/var/lib/alsa/asound.state,store 表示保存配置。保存成功以后就会生成/var/lib/alsa/asound.state 这个文件,
asound.state 文件中保存了声卡的各种设置信息,大家可以打开此文件查看里面的内容,如下所示:
Insert image description here
除了保存配置之外,还可以加载配置,譬如使用/var/lib/alsa/asound.state 文件中的配置信息来配置声卡,可执行如下命令:

alsactl -f /var/lib/alsa/asound.state restore

restore 表示加载配置,读取/var/lib/alsa/asound.state 文件中的配置信息并对声卡进行设置。关于alsactl
的详细使用方法,可以执行"alsactl -h"进行查看。
开发板出厂系统每次开机启动时便会自动从/var/lib/alsa/asound.state 文件中读取配置信息并配置声卡,而每次关机时(譬如执行reset 或poweroff 命令)又会将声卡当前的配置写入到该文件中进行保存,以便下一次启动时加载。其实也就是在系统启动(或关机)时通过alsactl 工具加载(或保存)配置。
amixer
amixer 工具也是一个声卡配置工具,与alsamixer 功能相同,区别在于,alsamixer 是一个基于字符图形化的配置工具、而amixer 不是图形化配置工具,直接使用命令行配置即可,详细地用法大家可以执行"amixer --help"命令查看,下面笔者简单地提一下该工具怎么用:
执行命令"amixer scontrols"可以查看到有哪些配置项,如下所示:
Insert image description here
从打印信息可知,这里打印出来的配置项与alsamixer 配置界面中所看到的配置项是相同的,那如何进去配置呢?不同的配置项对应的配置方法(配置值或值类型)是不一样的,可以先使用命令"amixer scontents"
查看配置项的说明,如下所示:

amixer scontents

Insert image description here
The "Headphone" configuration item is used to set the headphone volume. The adjustable volume range is 0-127, and the current volume is 115 (the left and right channels are both
115); some setting items are of bool type and only have two states: on and off.
For example, to set the headphone volume for both the left and right channels to 100, you can execute the following command to set it:

amixer sset Headphone 100,100

For example, to turn Headphone Playback ZC on or off:

amixer sset "Headphone Playback ZC" off #关闭ZC
amixer sset "Headphone Playback ZC" on #打开ZC

I gave you two examples above, and the configuration method is still very simple!
arecord
The arecord tool is an application for recording testing. Here I will briefly introduce how to use the tool. For detailed usage, you can execute the "arecord --help" command to view the help information. For example, to use arecord to record a 10-second piece of audio, you can execute the following command:

arecord -f cd -d 10 test.wav

Figure 28.4.7 Recording using arecord tool

The -f option specifies the audio format, and cd represents cd-level audio, which is "16 bit little endian, 44100, stereo"; the -d option specifies the audio recording time length, in seconds; test.wav specifies the file where the audio data is saved. When the recording is completed, the test.wav file will be generated, and then we can use the aplay tool to play this audio.
The above introduces you to several tools for testing audio and configuring sound cards provided by alsa-utils. Of course, this article only briefly introduces them. For more detailed usage, you need to check the help information yourself.

Write a simple alsa-lib application

At the beginning of this section, we will learn how to write audio applications based on alsa-lib. There are not many library functions provided by alsa-lib. I will definitely not introduce them all to you. I will only introduce the basic usage methods. For more in-depth and detailed information Everyone needs to research and learn how to use it.
For the use of alsa-lib library, ALSA provides some reference materials to help application developers quickly get started with alsa-lib and
perform application programming based on alsa-lib. The following author provides the link:
https://users.suse.com /~mana/alsa090_howto.html
https://www.alsa-project.org/alsa-doc/alsa-lib/examples.html
The first document introduces users to how to use alsa-lib to write simple audio applications, Including PCM audio playback, PCM recording, etc., the author also referred to this document to write this tutorial. It is suitable for beginners and I recommend everyone to take a look.
The second link address is some sample code provided by ALSA, as shown below:
Figure 28.5.1 Reference code provided by ALSA

Click on the corresponding source file to view the source code.
The above is the help document and reference code provided by ALSA. The link address has been given. If you are interested, you can take a look.
In this section, the author will introduce to you how to write a simple audio application based on alsa-lib, such as playing music, recording, etc.; but before that, we first need to understand some basic concepts to lay a solid foundation for subsequent learning. Foundation!

some basic concepts

Mainly the basic concepts related to audio, because these concepts will be involved in alsa-lib application programming, so I will give you a brief introduction first.
Sample length (Sample)
Sample is the most basic unit for recording audio data. The sample length is the number of sampling bits, also known as bit depth (Bit Depth, Sample Size,
Sample Width). It refers to the number of binary digits of the digital sound signal used by the computer when collecting and playing sound files, or the number of digits contained in each sampling sample (the number of digital bits when the computer samples and quantizes each channel), usually 8 bits , 16bit, 24bit, etc.
The number of channels (channel)
is divided into mono (Mono) and two-channel/stereo (Stereo). 1 means mono, 2 means stereo.
A frame
records a sound unit, and its length is the product of the sample length and the number of channels. A piece of audio data is composed of hard frames.
The data in all channels are added together and called a frame. For mono channel: one frame = sample length * 1; for dual channel: one frame = sample length * 2. For example, for two-channel audio with a sample length of 16 bits, the size of one frame is equal to: 16 * 2 / 8 = 4 bytes.

Sampling rate (Sample rate),
also called sampling frequency, refers to the number of samples per second, which is for frames. For example, common sampling rates are:
8KHz - the sampling rate used by telephones
is 22.05KHz - the sampling rate used by FM FM radio is
44.1KHz - the sampling rate used by audio CD, also commonly used in MPEG-1 audio (VCD, SVCD, MP3) is
48KHz - miniDV, digital The sampling rate used for digital sound used in television, DVD, DAT, movies, and professional audio.
Interleaved mode (interleaved)
Interleaved mode is a recording method of audio data, which is divided into interleaved mode and non-interleaved mode. In interleaved mode, the data is stored in the form of continuous frames, that is, the left channel sample and right channel sample of frame 1 are first recorded (assuming it is a stereo format), and then the left channel sample and right channel sample of frame 2 are recorded. . In non-interleaved mode, the left channel samples of all frames in a cycle are first recorded, and then the right channel samples are recorded. The data is stored in continuous channels. But in most cases, we generally use interleaved mode.
Period (period)
Period is the unit for audio equipment to process (read and write) data. In other words, the unit for audio equipment to read and write data is cycle. Each time a cycle of data is read or written, a cycle contains several frames. ; For example, if the cycle size is 1024 frames, it means that the amount of data for a read or write operation by the audio device is 1024 frames. Assuming that one frame is 4 bytes, then it is 1024*4=4096 bytes of data.
A cycle is actually the number of frames between two hardware interrupts. The audio device will generate an interrupt every time it processes (reads or writes) one cycle of data, so there is one cycle difference between the two interrupts. Regarding the issue of interrupts, let’s talk a little bit. I’ll introduce it to you later!
buffer
Data buffer, a buffer contains several cycles, so the buffer is a space composed of several cycles. The following figure visually represents the relationship between buffer, period, frame, and sample (sample length). Assume that a buffer contains 4 periods, a week contains 1024 frames, and a frame contains two samples (left and right). channel):
Figure 28.5.2 Example diagram of the relationship between buffer/period/frame/sample

音频设备底层驱动程序使用DMA 来搬运数据,这个buffer 中有4 个period,每当DMA 搬运完一个
period 的数据就会触发一次中断,因此搬运整个buffer 中的数据将产生4 次中断。ALSA 为什么这样做?直接把整个buffer 中的数据一次性搬运过去岂不是更快?情况并非如此,我们没有考虑到一个很重要的问题,那就是延迟;如果数据缓存区buffer 很大,一次传输整个buffer 中的数据可能会导致不可接受的延迟,因为一次搬运的数据量越大,所花费的时间就越长,那么必然会导致数据从传输开始到发出声音(以播放为例)这个过程所经历的时间就会越长,这就是延迟。为了解决这个问题,ALSA 把缓存区拆分成多个周期,以周期为传输单元进行传输数据。
所以,周期不宜设置过大,周期过大会导致延迟过高;但周期也不能太小,周期太小会导致频繁触发中断,这样会使得CPU 被频繁中断而无法执行其它的任务,使得效率降低!所以,周期大小要合适,在延迟可接受的情况下,尽量设置大一些,不过这个需要根据实际应用场合而定,有些应用场合,可能要求低延迟、实时性高,但有些应用场合没有这种需求。
数据之间的传输
这里再介绍一下数据之间传输的问题,这个问题很重要,大家一定要理解,这样会更好的帮助我们理解代码、理解代码的逻辑。
⚫ PCM 播放情况下
在播放情况下,buffer 中存放了需要播放的PCM 音频数据,由应用程序向buffer 中写入音频数据,buffer
中的音频数据由DMA 传输给音频设备进行播放,所以应用程序向buffer 写入数据、音频设备从buffer 读取数据,这就是buffer 中数据的传输情况。
There are read pointer and write pointer pointers marked in Figure 28.5.2. The write pointer points to the location where the current application writes the buffer, and the
read pointer points to the location where the current audio device reads the buffer. Before data transmission (before playback), there is no data in the buffer. At this time, the write/read pointer points to the starting position of the buffer, which is the starting position of the first cycle, as shown below:
Figure 28.5.3 pointer points to the starting position of the buffer

The write pointer moves forward by how many frames the application writes to the buffer. When the application writes a cycle of data to the buffer, the write pointer moves forward by one cycle; then another cycle is written. , the pointer moves forward one cycle, and so on! When the write pointer moves to the end of the buffer, it will return to the starting position of the buffer, and the cycle continues! So it can be seen that this is a ring buffer.
The above is a process of the application writing the buffer, and then let's look at the process of reading the buffer (playing) of the audio device. Before playback starts, the read pointer points to the starting position of the buffer, which is the starting position of the first cycle. The audio device only plays one cycle of data at a time (reads one cycle), and each time it reads from the position pointed by the read pointer; every time a cycle is read, the read pointer moves forward by one cycle. Similarly, when read When the pointer moves to the end of the buffer, it will return to the starting position of the buffer, thus forming a loop!
The application needs to write audio data into the buffer so that the audio device can read the data for playback. If the period pointed to by the read pointer is not filled with audio data, it cannot be played! When the buffer data is full, the application will no longer be able to write data, otherwise the previous data will be overwritten. It must wait for the audio device to finish playing a cycle. Every time the audio device plays a cycle, the cycle becomes idle. At this point the application can write a cycle of data to fill the idle cycle.
⚫ In case of PCM recording

在录音情况下,buffer 中存放了音频设备采集到的音频数据(外界模拟声音通过ADC 转为数字声音),由音频设备向buffer 中写入音频数据(DMA 搬运),而应用程序从buffer 中读取数据,所以音频设备向
buffer 写入数据、应用程序从buffer 读取数据,这就是录音情况下buffer 中数据的传输情况。
回到图28.5.2 中,此时write pointer 指向音频设备写buffer 的位置、read pointer 指向应用程序读buffer
的位置。在录音开始之前,buffer 缓冲区是没有数据的,此时write/read pointer 均指向了buffer 的起始位置,也就是第一个周期的起始位置,如图28.5.3 中所示。
音频设备向buffer 写入多少帧数据,则write pointer 指针向前移动多少帧,音频设备每次只采集一个周期,将采集到的数据写入buffer 中,从write pointer 所指位置开始写入;当音频设备向buffer 中写入一个周期的数据时,write pointer 指针将向前移动一个周期;接着再写入一个周期,指针再向前移动一个周期,以此类推!当write pointer 移动到buffer 末尾时,又会回到buffer 的起始位置,以此构成循环!
以上是音频设备写buffer 的一个过程,接着再来看看应用程序读buffer 的过程。在录音开始之前,read pointer 指向了buffer 的起始位置,也就是第一个周期的起始位置。同样,应用程序从buffer 读取了多少帧数据,则read pointer 指针向前移动多少帧;从read pointer 所指位置开始读取,当read pointer 指针移动到
At the end of the buffer, it will return to the starting position of the buffer, thus forming a loop!
The audio device needs to write audio data into the buffer before the application can read data (recording) from the buffer. If the
period pointed to by the read pointer is not filled with audio data, it cannot be read! When there is no data in the buffer, you need to wait for the audio device to write data to the buffer. The audio device writes one cycle at a time. After the application reads the data for this cycle, this cycle becomes an idle cycle again and you need to wait. The audio device writes data.
Over and Under Run
When a sound card is in working state, the data in the ring buffer buffer is always continuously transmitted between the audio device and the application cache, as shown in the following figure:
Figure 28.5.4 Transmission of data in buffer

The above figure shows the transmission of data in the buffer when the sound card is working. It is always continuously transmitted between the audio device and the application buffer, but things are not always perfect, and there are exceptions; for example, in the recording example In , if the application program does not read the data fast enough, the data in the ring buffer buffer has been filled by the audio device, and the application program has not had time to read it, then the data will be overwritten; this kind of data loss is called overrun. In the playback example, if the application cannot write data into the ring buffer fast enough,

The buffer will be "starved" (no data in the buffer to play); such an error is called an underrun. In the ALSA documentation, these two situations are collectively referred to as "XRUN", and appropriately designed applications can minimize and recover from XRUN.

Open the PCM device

Starting from this section, we will formally introduce how to write an audio application. First, we need to include the header file <alsa/asoundlib.h> of the alsa-lib library in the application, so that the alsa-lib library function can be called in the application. As well as using related macros.
The first step is to open the PCM device and call the function snd_pcm_open(). The function prototype is as follows:

int snd_pcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode)

This function has a total of 4 parameters, as shown below:
⚫ pcmp: snd_pcm_t is used to describe a PCM device, so a snd_pcm_t object represents a PCM device; the
snd_pcm_open function will open the device specified by the parameter name, instantiate the snd_pcm_t object, and The pointer to the object (that is, the handle of the PCM device) is returned through pcmp.
⚫ name: The parameter name specifies the name of the PCM device. The alsa-lib library function uses logical device names instead of device file names. The naming method is "hw:i,j", where i represents the card number of the sound card, and j represents the device number on the sound card; for example, "hw:0, 0" represents PCM device 0 on sound card 0.
In the case of playback, this actually corresponds to /dev/snd/pcmC0D0p (if it is recording, it corresponds to
/dev/snd/pcmC0D0c). In addition to naming using "hw:i,j", there are two other commonly used naming methods, such as
"plughw:i,j", "default", etc. Regarding the differences between these names, we will discuss the differences at the end of this chapter. Let’s give a brief introduction and ignore this issue for now.
⚫ stream: The parameter stream specifies the stream type. There are two different types: SND_PCM_STREAM_PLAYBACK and
SND_PCM_STREAM_CAPTURE; SND_PCM_STREAM_PLAYBACK means playback, and
SND_PCM_STREAM_CAPTURE means collection.
⚫ mode: The last parameter mode specifies the open mode. Normally, we will set it to 0, which means the default open mode. By default, the device is opened in blocking mode; of course, it can also be set to SND_PCM_NONBLOCK, which means Open the device in non-blocking mode.
If the device is opened successfully, the snd_pcm_open function returns 0; if the opening fails, an error number less than 0 is returned. You can use the library function snd_strerror() provided by alsa-lib to get the
corresponding error description information. This function is the same as the C library function strerror(). same.
Corresponding to snd_pcm_open is snd_pcm_close(), the function snd_pcm_close() is used to close the PCM device, the function prototype is as follows:

int snd_pcm_close(snd_pcm_t *pcm);

Usage example:
Call the snd_pcm_open() function to open the PCM playback device 0 of sound card 0:

snd_pcm_t *pcm_handle = NULL;
int ret;
ret = snd_pcm_open(&pcm_handle, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
if (0 > ret)
{
    
    
    fprintf(stderr, "snd_pcm_open error: %s\n", snd_strerror(ret));
    return -1;
}

Set hardware parameters

After opening the PCM device, we then need to set up the device, including hardware configuration and software configuration. The software configuration will not be introduced anymore, just use the default configuration! We mainly configure hardware parameters, such as sampling rate, number of channels, format, access type, period
size, buffer size, etc.
Instantiate the snd_pcm_hw_params_t object
alsa-lib uses the snd_pcm_hw_params_t data type to describe the hardware configuration parameters of the PCM device. Before configuring the parameters, we need to instantiate a snd_pcm_hw_params_t object using snd_pcm_hw_params_malloc or

snd_pcm_hw_params_alloca()来实例化一个snd_pcm_hw_params_t 对象,如下所示:
snd_pcm_hw_params_t *hwparams = NULL;
snd_pcm_hw_params_malloc(&hwparams);
snd_pcm_hw_params_alloca(&hwparams);

The difference between them is the difference between the C library functions malloc and alloca. Of course, you can also directly use malloc() or
alloca() to allocate a snd_pcm_hw_params_t object, or directly define global variables or stack automatic variables.
Corresponding to snd_pcm_hw_params_malloc/snd_pcm_hw_params_alloca is snd_pcm_hw_params_free. The
snd_pcm_hw_params_free() function is used to release the memory space occupied by the snd_pcm_hw_params_t object. The function prototype looks like this:

void snd_pcm_hw_params_free(snd_pcm_hw_params_t *obj)

Initialize the snd_pcm_hw_params_t object.
After the snd_pcm_hw_params_t object is instantiated, we need to initialize it. Call
snd_pcm_hw_params_any() to initialize the snd_pcm_hw_params_t object. Calling this function will use the current configuration parameters of the PCM device to initialize the snd_pcm_hw_params_t object, as shown below:

snd_pcm_hw_params_any(pcm_handle, hwparams);

The first parameter is the handle of the PCM device, and the second parameter is passed in the pointer of the snd_pcm_hw_params_t object.
Setting hardware parameters
alsa-lib provides a series of snd_pcm_hw_params_set_xxx functions for setting hardware parameters of PCM devices, and also provides a series of snd_pcm_hw_params_get_xxx functions for obtaining hardware parameters.
(1) Set the access access type: snd_pcm_hw_params_set_access()
calls snd_pcm_hw_params_set_access to set the access type. Its function prototype is as follows:

int snd_pcm_hw_params_set_access(snd_pcm_t *pcm,
	snd_pcm_hw_params_t * params,
	snd_pcm_access_t access
)

The parameter access specifies the access type of the device, which is an snd_pcm_access_t type constant, which is an enumeration type, as shown below: The parameter access specifies the access type of the device, which is an snd_pcm_access_t type constant, which
is an enumeration type, as shown below:

enum snd_pcm_access_t
{
    
    
    SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, // mmap access with simple interleaved channels
    SND_PCM_ACCESS_MMAP_NONINTERLEAVED,  // mmap access with simple non interleaved channels
    SND_PCM_ACCESS_MMAP_COMPLEX,         // mmap access with complex placement
    SND_PCM_ACCESS_RW_INTERLEAVED,       // snd_pcm_readi/snd_pcm_writei access
    SND_PCM_ACCESS_RW_NONINTERLEAVED,    // snd_pcm_readn/snd_pcm_writen access
    SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
};

Usually, set the access type to SND_PCM_ACCESS_RW_INTERLEAVED, interleaved access mode, and
perform read/write operations on the PCM device through snd_pcm_readi/snd_pcm_writei.
The function call returns 0 successfully; failure will return an error code less than 0, and the error description information can be obtained through the snd_strerror() function.
Example usage:

ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
if (0 > ret)
fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));

(2) Set data format: snd_pcm_hw_params_set_format()
calls the snd_pcm_hw_params_set_format() function to set the data format of the PCM device. The function prototype is as follows:

int snd_pcm_hw_params_set_format(snd_pcm_t *pcm,
	snd_pcm_hw_params_t *params,
	snd_pcm_format_t format
)

The parameter format specifies the data format. This parameter is a snd_pcm_format_t type constant, which is an enumeration type, as shown below:

enum snd_pcm_format_t
{
    
    
    SND_PCM_FORMAT_UNKNOWN = -1,
    SND_PCM_FORMAT_S8 = 0,
    SND_PCM_FORMAT_U8,
    SND_PCM_FORMAT_S16_LE,
    SND_PCM_FORMAT_S16_BE,
    SND_PCM_FORMAT_U16_LE,
    SND_PCM_FORMAT_U16_BE,
    SND_PCM_FORMAT_S24_LE,
    SND_PCM_FORMAT_S24_BE,
    SND_PCM_FORMAT_U24_LE,
    SND_PCM_FORMAT_U24_BE,
    SND_PCM_FORMAT_S32_LE,
    SND_PCM_FORMAT_S32_BE,
    SND_PCM_FORMAT_U32_LE,
    SND_PCM_FORMAT_U32_BE,
    SND_PCM_FORMAT_FLOAT_LE,
    SND_PCM_FORMAT_FLOAT_BE,
    SND_PCM_FORMAT_FLOAT64_LE,
    SND_PCM_FORMAT_FLOAT64_BE,
    SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
    SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
    SND_PCM_FORMAT_MU_LAW,
    SND_PCM_FORMAT_A_LAW,
    SND_PCM_FORMAT_IMA_ADPCM,
    SND_PCM_FORMAT_MPEG,
    SND_PCM_FORMAT_GSM,
    SND_PCM_FORMAT_S20_LE,
    SND_PCM_FORMAT_S20_BE,
    SND_PCM_FORMAT_U20_LE,
    SND_PCM_FORMAT_U20_BE,
    SND_PCM_FORMAT_SPECIAL = 31,
    SND_PCM_FORMAT_S24_3LE = 32,
    SND_PCM_FORMAT_S24_3BE,
    SND_PCM_FORMAT_U24_3LE,
    SND_PCM_FORMAT_U24_3BE,
    SND_PCM_FORMAT_S20_3LE,
    SND_PCM_FORMAT_S20_3BE,
    SND_PCM_FORMAT_U20_3LE,
    SND_PCM_FORMAT_U20_3BE,
    SND_PCM_FORMAT_S18_3LE,
    SND_PCM_FORMAT_S18_3BE,
    SND_PCM_FORMAT_U18_3LE,
    SND_PCM_FORMAT_U18_3BE,
    SND_PCM_FORMAT_G723_24,
    SND_PCM_FORMAT_G723_24_1B,
    SND_PCM_FORMAT_G723_40,
    SND_PCM_FORMAT_G723_40_1B,
    SND_PCM_FORMAT_DSD_U8,
    SND_PCM_FORMAT_DSD_U16_LE,
    SND_PCM_FORMAT_DSD_U32_LE,
    SND_PCM_FORMAT_DSD_U16_BE,
    SND_PCM_FORMAT_DSD_U32_BE,
    SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U32_BE,
    SND_PCM_FORMAT_S16 = SND_PCM_FORMAT_S16_LE,
    SND_PCM_FORMAT_U16 = SND_PCM_FORMAT_U16_LE,
    SND_PCM_FORMAT_S24 = SND_PCM_FORMAT_S24_LE,
    SND_PCM_FORMAT_U24 = SND_PCM_FORMAT_U24_LE,
    SND_PCM_FORMAT_S32 = SND_PCM_FORMAT_S32_LE,
    SND_PCM_FORMAT_U32 = SND_PCM_FORMAT_U32_LE,
    SND_PCM_FORMAT_FLOAT = SND_PCM_FORMAT_FLOAT_LE,
    SND_PCM_FORMAT_FLOAT64 = SND_PCM_FORMAT_FLOAT64_LE,
    SND_PCM_FORMAT_IEC958_SUBFRAME = SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
    SND_PCM_FORMAT_S20 = SND_PCM_FORMAT_S20_LE,
    SND_PCM_FORMAT_U20 = SND_PCM_FORMAT_U20_LE
};

The most commonly used format is SND_PCM_FORMAT_S16_LE, signed 16-bit, little-endian mode. Of course, the audio device may not necessarily support the format specified by the user. Before that, the user can call the snd_pcm_hw_params_test_format() function to test whether the PCM device supports a certain format, as shown below:

if (snd_pcm_hw_params_test_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE)) {
    
    
// 返回一个非零值表示不支持该格式
}
else {
    
    
// 返回0 表示支持
}

(3) Set the number of channels: snd_pcm_hw_params_set_channels()
calls the snd_pcm_hw_params_set_channels() function to set the number of channels of the PCM device. The function prototype is as follows:

int snd_pcm_hw_params_set_channels(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
unsigned int val
)

The parameter val specifies the number of channels, val=2 means two channels, that is, stereo. The function call returns 0 successfully, and an error code less than 0 is returned if it fails.
Example usage:

ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2);
if (0 > ret)
fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));

(4) Set the sampling rate: snd_pcm_hw_params_set_rate()
calls snd_pcm_hw_params_set_rate to set the sampling rate. The function prototype is as follows:

int snd_pcm_hw_params_set_rate(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
unsigned int val,
int dir
)

The parameter val specifies the sampling rate, such as 44100; the parameter dir is used to control the direction. If dir=-1, the actual sampling rate is less than the
value specified by the parameter val; dir=0 means that the actual sampling rate is equal to the parameter val; dir=1 means the actual The sampling rate is greater than the parameter val.
The function call returns 0 successfully; failure returns an error code less than 0.
Example usage:

ret = snd_pcm_hw_params_set_rate(pcm_handle, hwparams, 44100, 0);
if (0 > ret)
fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));

(5) Set the period size: snd_pcm_hw_params_set_period_size()
The period mentioned here is the period introduced to you in Section 28.5.1. The size of a period is measured by frames, for example, a period of
1024 frames; call the snd_pcm_hw_params_set_period_size() function to set the period size, its function prototype is as follows:

int snd_pcm_hw_params_set_period_size(snd_pcm_t *pcm,
	snd_pcm_hw_params_t *params,
	snd_pcm_uframes_t val,
	int dir
)


alsa-lib uses the snd_pcm_uframes_t type to indicate the number of frames; the parameter dir has the same meaning as the dir parameter of the snd_pcm_hw_params_set_rate() function .
Example usage (set period size to 1024 frames):

ret = snd_pcm_hw_params_set_period_size(pcm_handle, hwparams, 1024, 0);
if (0 > ret)
fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));

Note that the unit of parameter val is frames, not bytes.
(6) Set the buffer size: snd_pcm_hw_params_set_buffer_size()
calls the snd_pcm_hw_params_set_buffer_size() function to set the buffer size. Its function prototype is as follows:

int snd_pcm_hw_params_set_buffer_size(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
snd_pcm_uframes_t val
)

The parameter val specifies the buffer size in frames. Usually the buffer size is an integer multiple of the cycle size, such as 16 cycles; but the function snd_pcm_hw_params_set_buffer_size() expresses the buffer size in frames, so it needs to be converted, for example Set the buffer size to 16 cycles, then the parameter val is equal to 16 * 1024 (assuming one cycle is 1024 frames) = 16384 frames.
Function call returns 0 successfully; failure returns an error code less than 0.
Example usage:

ret = snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, 16*1024);
if (0 > ret)
fprintf(stderr, "snd_pcm_hw_params_set_buffer_size error: %s\n", snd_strerror(ret));

In addition to the snd_pcm_hw_params_set_buffer_size() function, we can also call the snd_pcm_hw_params_set_periods()
function to set the buffer size. The function prototype is as follows:

int snd_pcm_hw_params_set_periods(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params,
unsigned int val,
int dir
)

The parameter val specifies the size of the buffer. The size is in cycles, not frames. Pay attention to the difference!
The parameter dir has the same meaning as the dir parameter of the snd_pcm_hw_params_set_rate() function.
A successful function call returns 0; a failure will return an error code less than 0.
Example usage:

ret = snd_pcm_hw_params_set_periods(pcm_handle, hwparams, 16, 0); //buffer 大小为16 个周期
if (0 > ret)
fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));

(7) Install/load hardware configuration parameters:
After the snd_pcm_hw_params() parameter setting is completed, finally call snd_pcm_hw_params() to load/install the configuration and write the configuration parameters to the hardware to make them effective. The function prototype is as follows:

int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)

函数调用成功返回0,失败将返回一个小于0 的错误码。函数snd_pcm_hw_params()调用之后,其内部会自动调用snd_pcm_prepare()函数,PCM 设备的状态被更改为SND_PCM_STATE_PREPARED。
设备有多种不同的状态,SND_PCM_STATE_PREPARED 为其中一种,关于状态的问题,后面在向大家介绍。调用snd_pcm_prepare()函数会使得PCM 设备处于SND_PCM_STATE_PREPARED 状态(也就是处于一种准备好的状态)。
使用示例:

ret = snd_pcm_hw_params(pcm_handle, hwparams);
if (0 > ret)
fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));

读/写数据

接下来就可以进行读/写数据了,如果是PCM 播放,则调用snd_pcm_writei()函数向播放缓冲区buffer
中写入音频数据;如果是PCM 录音,则调用snd_pcm_readi()函数从录音缓冲区buffer 中读取数据,它们的函数原型如下所示:

snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t *pcm,
const void *buffer,
snd_pcm_uframes_t size
)
snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm,
void *buffer,
snd_pcm_uframes_t size
)

The parameter pcm is the handle of the PCM device; call the snd_pcm_writei() function to write the data in the parameter buffer (application buffer) buffer to the playback ring buffer buffer of the driver layer. The parameter size specifies the size of the written data. , in frames; usually, one cycle of data is written each time snd_pcm_writei() is called.
Call the snd_pcm_readi() function to read data from the recording ring buffer buffer of the driver layer into the buffer specified by the parameter buffer (application buffer). The parameter size specifies the size of the read data, in frames; Normally,
one cycle of data is read each time snd_pcm_readi() is called.
Tips: In the snd_pcm_writei/snd_pcm_readi function prototype, the parameter buffer refers to the application buffer. Don’t confuse it with the driver layer’s ring buffer!
If the call to snd_pcm_readi/snd_pcm_writei is successful, the actual number of frames read/written will be returned; if the call fails, a negative error code will be returned. Even if the call is successful, the actual number of frames read/written is not necessarily equal to the number of frames specified by parameter size. Only when a signal or XRUN occurs, the number of frames returned may be less than parameter size.
Blocking and non-blocking
When calling snd_pcm_open() to open the device, if blocking mode is specified, call snd_pcm_readi/snd_pcm_writei to read/write in blocking mode. For PCM recording, when there is no data to read in the buffer, calling the snd_pcm_readi() function will block until the audio device writes the collected audio data into the buffer; similarly, for PCM playback, when When the data in the buffer buffer is full, calling the snd_pcm_writei() function will block until the audio device reads the data from the buffer for playback.
If you specify non-blocking mode when calling snd_pcm_open() to open the device, call snd_pcm_readi/snd_pcm_writei to read/write in non-blocking mode. For PCM recording, when there is no data to read in the buffer, calling snd_pcm_readi() will not block, but will return immediately with an error; similarly, for PCM playback, when the data in the buffer is full, When calling the snd_pcm_writei()
function, it will not block, but will return immediately with an error.
snd_pcm_readn and snd_pcm_writen
snd_pcm_readi/snd_pcm_writei are suitable for reading/writing data in interleaved mode. If the access type set by the user is not interleaved mode, but non-interleaved mode (non interleaved), snd_pcm_readi/snd_pcm_writei can no longer be used for reading. The write operation is performed, and snd_pcm_readn and snd_pcm_writen need to be used for reading and writing.

Sample code for PCM playback

Through the introduction in the previous section, I believe everyone has a basic knowledge and understanding of alsa-lib audio application programming. In this section we will write a simple music player that can play WAV audio files. The author has already written the code. As shown below:
The path corresponding to the source code of this routine is: development board CD->11, Linux C application programming routine source code->28_alsa-lib->pcm_playback.c.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_PLAYBACK_DEV "hw:0,0"
/************************************
WAV 音频文件解析相关数据结构申明
************************************/
typedef struct WAV_RIFF
{
    
    
    char ChunkID[4];     /* "RIFF" */
    u_int32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数*/
    char Format[4];      /* "WAVE" */
} __attribute__((packed)) RIFF_t;
typedef struct WAV_FMT
{
    
    
    char Subchunk1ID[4];     /* "fmt " */
    u_int32_t Subchunk1Size; /* 16 for PCM */
    u_int16_t AudioFormat;   /* PCM = 1*/
    u_int16_t NumChannels;   /* Mono = 1, Stereo = 2, etc. */
    u_int32_t SampleRate;    /* 8000, 44100, etc. */
    u_int32_t ByteRate;      /* = SampleRate * NumChannels * BitsPerSample/8 */
    u_int16_t BlockAlign;    /* = NumChannels * BitsPerSample/8 */
    u_int16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__((packed)) FMT_t;
static FMT_t wav_fmt;
typedef struct WAV_DATA
{
    
    
    char Subchunk2ID[4];     /* "data" */
    u_int32_t Subchunk2Size; /* data size */
} __attribute__((packed)) DATA_t;
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                // pcm 句柄
static unsigned int buf_bytes;               // 应用程序缓冲区的大小(字节为单位)
static void *buf = NULL;                     // 指向应用程序缓冲区的指针
static int fd = -1;                          // 指向WAV 音频文件的文件描述符
static snd_pcm_uframes_t period_size = 1024; // 周期大小(单位: 帧)
static unsigned int periods = 16;            // 周期数(设备驱动层buffer 的大小)
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_PLAYBACK_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(驱动层buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    buf_bytes = period_size * wav_fmt.BlockAlign; // 变量赋值,一个周期的字节大小
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
static int open_wav_file(const char *file)
{
    
    
    RIFF_t wav_riff;
    DATA_t wav_data;
    int ret;
    fd = open(file, O_RDONLY);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));
        return -1;
    }
    /* 读取RIFF chunk */
    ret = read(fd, &wav_riff, sizeof(RIFF_t));
    if (sizeof(RIFF_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("RIFF", wav_riff.ChunkID, 4) || // 校验
        strncmp("WAVE", wav_riff.Format, 4))
    {
    
    
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 读取sub-chunk-fmt */
    ret = read(fd, &wav_fmt, sizeof(FMT_t));
    if (sizeof(FMT_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4))
    {
    
     // 校验
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 打印音频文件的信息*/
    printf("<<<<音频文件格式信息>>>>\n\n");
    printf(" file name: %s\n", file);
    printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);
    printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);
    printf(" NumChannels: %u\n", wav_fmt.NumChannels);
    printf(" SampleRate: %u\n", wav_fmt.SampleRate);
    printf(" ByteRate: %u\n", wav_fmt.ByteRate);
    printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);
    printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);
    /* sub-chunk-data */
    if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,
                  SEEK_SET))
    {
    
    
        perror("lseek error");
        close(fd);
        return -1;
    }
    while (sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t)))
    {
    
    
        /* 找到sub-chunk-data */
        if (!strncmp("data", wav_data.Subchunk2ID, 4)) // 校验
            return 0;
        if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR))
        {
    
    
            perror("lseek error");
            close(fd);
            return -1;
        }
    }
    fprintf(stderr, "check error: %s\n", file);
    return -1;
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 打开WAV 音频文件*/
    if (open_wav_file(argv[1]))
        exit(EXIT_FAILURE);
    /* 初始化PCM Playback 设备*/
    if (snd_pcm_init())
        goto err1;
    /* 申请读缓冲区*/
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err2;
    }
    /* 播放*/
    for (;;)
    {
    
    
        memset(buf, 0x00, buf_bytes);   // buf 清零
        ret = read(fd, buf, buf_bytes); // 从音频文件中读取数据
        if (0 >= ret)                   // 如果读取出错或文件读取完毕
            goto err3;
        ret = snd_pcm_writei(pcm, buf, period_size);
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
            goto err3;
        }
        else if (ret < period_size)
        {
    
     // 实际写入的帧数小于指定的帧数
            // 此时我们需要调整下音频文件的读位置
            // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes 个字节
            // frame_bytes 表示一帧的字节大小
            if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
            {
    
    
                perror("lseek error");
                goto err3;
            }
        }
    }
err3:
    free(buf); // 释放内存
err2:
    snd_pcm_close(pcm); // 关闭pcm 设备
err1:
    close(fd); // 关闭打开的音频文件
    exit(EXIT_FAILURE);
}

This application can play WAV audio files. Regarding the analysis of WAV file format, this document will not explain it. The WAV file format is actually very simple. You can understand it by yourself on Baidu.
In the main() function, the parameters are first verified. To execute the test program, the user needs to pass in a parameter. This parameter is used to specify a WAV audio file that needs to be played. Then call the custom function open_wav_file() to parse the WAV file, which actually means verifying and parsing its header data to obtain the audio format information and the position offset of the audio data.
Then call the custom function snd_pcm_init() to initialize the PCM device. In the snd_pcm_init() function, first call the alsa-lib library function snd_pcm_open() to open the PCM playback device, and then set the PCM device hardware parameters, including: access type, Data format, sampling rate, number of channels, cycle size and buffer size have been introduced to you in detail before and will not be repeated here!
Return to the main() function and call the C library function malloc() to apply for allocation of a buffer to store audio data read from the audio file.
After everything is ready, you can play the audio. In the for loop, first call the read() function to read the audio data from the audio file. Read one cycle at a time and store the read data in the buf pointed to. In the buffer, the alsa-lib library function snd_pcm_writei() is then called
to write data for playback. The blocking method is used when calling snd_pcm_open() in the sample program. When the driver layer ring buffer buffer is not full, calling snd_pcm_writei() will not block, but will write the data into the ring buffer and then return. ;Call snd_pcm_writei() once
to write one cycle of data, call it once and write another cycle; when the ring buffer data is full, call
snd_pcm_writei() will block until the audio device finishes playing a cycle, at which time an idle cycle will appear, and then snd_pcm_writei()
will fill the data into this idle cycle and return.
The above is a brief introduction to the sample code. The code itself is very simple and has no difficulties. The annotation information in the code has also been described clearly. I believe everyone can understand it. It should be noted that the alsa-lib header file <alsa/asoundlib.h> must be included in the source code!
Compile the sample code
Next, compile the above sample code. The compilation method is very simple. According to the previous practice, compiling is nothing more than specifying two paths (the path where the alsa-lib header file is located, the path where the alsa-lib library file is located) and the link library. (The name of the library file that needs to be linked), for example:

${
    
    CC} -o testApp testApp.c -Ixxx -Lyyy -lzzz

xxx represents the path of the header file, yyy represents the path of the library file, and zzz represents the link library.
However, we did not transplant alsa-lib ourselves, which means that we did not transplant or install alsa-lib under Ubuntu, so these paths cannot be specified. In fact, alsa-lib has been installed in the installation directory corresponding to the cross-compilation tool we use. Go to the sysroots/cortexa7hf-neon-poky-linux-gnueabi directory under the cross-compilation tool installation directory. For example, the Ubuntu system I use, cross The installation path of the compilation tool is /opt/fsl-imx-x11/4.1.15-2.1.0.
Figure 28.5.5 Folders under the cortexa7hf-neon-poky-linux-gnueabi directory

There are two directories under this directory, lib and usr. These two directories are actually lib and usr under the root directory of the Linux system; so the lib directory stores some link library files, and the usr directory contains the include and lib directories, respectively. Stores header files and link library files.
The header files of alsa-lib are stored in the usr/include/alsa directory, as shown below:
Figure 28.5.6 Header file of alsa-lib

The header file we need to include, asoundlib.h, is in this directory.
The usr/lib directory contains the alsa-lib library file, as shown below:
Figure 28.5.7 alsa-lib library file

The alsa-lib link library libasound.so is in this directory. Now that you have found the header file path and library file path of alsa-lib, you can directly specify these paths when compiling the application. But we don’t need to manually specify these paths ourselves. The cross-compiler has already added these paths to its search path. Use echo ${CC} to view the contents of the environment variable CC, as shown below: The cross-compiler arm-
Insert image description here
poky -linux-gnueabi-gcc has an option –sysroot, which specifies a path. This path is the sysroots/cortexa7hf-neon-poky-linux-gnueabi directory under the cross-compilation tool installation directory. The –sysroot option is used to set the target platform. Root directory. After setting the platform root directory, when compiling the application, the compiler will add usr/include in the root directory to the header file search path, and add lib and usr/lib in the root directory to the library file search path. middle.
So it can be seen that when compiling the application, we only need to specify the link library, as shown below:

${
    
    CC} -o testApp testApp.c -lasound

Insert image description here
Test the application.
Copy the compiled executable file to the /home/root directory of the development board Linux system, and copy a WAV audio file to the
/home/root directory, as shown below:
Insert image description here
Then test, before testing, we You also need to configure the sound card mixer. Of course, you don’t have to configure it, because the sound card in the development board’s factory system is already configured. Here we directly use the amixer tool to configure the configuration as follows:

#打开耳机播放ZC
amixer sset 'Headphone Playback ZC' on
#打开喇叭播放ZC
    amixer sset 'Speaker Playback ZC' on
        amixer sset 'Speaker AC' 3 amixer sset 'Speaker DC' 3
#音量设置
    amixer sset Headphone 105,
    105 // 耳机音量设置
    amixer sset Playback 230,
    230 // 播放音量设置
    amixer sset Speaker 118,
    118 // 喇叭音量设置
#打开左右声道
    amixer sset 'Right Output Mixer PCM' on    // 打开右声道
        amixer sset 'Left Output Mixer PCM' on // 打开左声道

Insert image description here
Due to limited space, all the printed information cannot be intercepted for everyone. The volume of the sound can be adjusted according to the situation.
After the sound card settings are completed, then run the test program, as shown below:
Insert image description here
After the program is run, the incoming WAV file is parsed and its audio format information is printed out.
At this time, the development board speaker will start to play music. If a headset is connected, the music will be played through the headset.

Sample Code Value PCM Recording

In this section, we will write a test program for PCM audio recording (recording). The sample code has been given by the author, as shown below: The
path corresponding to the source code of this routine is: Development board CD->11. Linux C application programming routine source code ->28_alsa-lib->pcm_capture.c.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_CAPTURE_DEV "hw:0,0"
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                // pcm 句柄
static snd_pcm_uframes_t period_size = 1024; // 周期大小(单位: 帧)
static unsigned int periods = 16;            // 周期数(buffer 的大小)
static unsigned int rate = 44100;            // 采样率
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_CAPTURE_DEV, SND_PCM_STREAM_CAPTURE, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_CAPTURE_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, rate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    unsigned char *buf = NULL;
    unsigned int buf_bytes;
    int fd = -1;
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <output_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 初始化PCM Capture 设备*/
    if (snd_pcm_init())
        exit(EXIT_FAILURE);
    /* 申请读缓冲区*/
    buf_bytes = period_size * 4; // 字节大小= 周期大小*帧的字节大小16 位双声道
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err1;
    }
    /* 打开一个新建文件*/
    fd = open(argv[1], O_WRONLY | O_CREAT | O_EXCL);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", argv[1], strerror(errno));
        goto err2;
    }
    /* 录音*/
    for (;;)
    {
    
    
        // memset(buf, 0x00, buf_bytes); //buf 清零
        ret = snd_pcm_readi(pcm, buf, period_size); // 读取PCM 数据一个周期
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_readi error: %s\n", snd_strerror(ret));
            goto err3;
        }
        // snd_pcm_readi 的返回值ret 等于实际读取的帧数* 4 转为字节数
        ret = write(fd, buf, ret * 4); // 将读取到的数据写入文件中
        if (0 >= ret)
            goto err3;
    }
err3:
    close(fd); // 关闭文件
err2:
    free(buf); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    exit(EXIT_FAILURE);
}

In the main() function, the parameters are first verified. To execute the test program, the user needs to pass in a parameter and specify the output file, because the sample program will save the recorded audio data to this file.
Then call the custom function snd_pcm_init() to initialize the PCM device. In the snd_pcm_init() function, first call the alsa-lib library function snd_pcm_open() to open the PCM recording device, then set the PCM device hardware parameters, and set the access type to interleave mode. SND_PCM_ACCESS_RW_INTERLEAVED, data format is set to SND_PCM_FORMAT_S16_LE, sampling rate is set to 44100, dual channel, cycle size is set to 1024 frames, and buffer size is set to 16 cycles.
Return to the main() function and call the C library function malloc() to apply for allocation of a buffer to store the audio data read from the driver layer ring buffer buffer. And open a new file (because the O_CREAT | O_EXCL flag is used).
After everything is ready, you can start audio recording. In the for loop, first call the alsa-lib library function snd_pcm_readi() to read the audio data collected by the audio device from the ring buffer, and then call write() after reading it. Function writes data to a file. The blocking method is used when calling snd_pcm_open() in the sample program. When there is data to be read in the ring buffer buffer, calling snd_pcm_readi() will not
block, but will read the data and then return; call snd_pcm_readi() once Read one cycle, call it once and read another cycle; when the ring buffer is empty, calling snd_pcm_readi() will block until the audio device collects a cycle of data, at which time the blocked snd_pcm_readi() call is awakened and
read This cycle then returns.
Compiling the sample code
Next we compile the sample code as follows:

${
    
    CC} -o testApp testApp.c -lasound

Insert image description here
Test the application.
Copy the compiled executable file to the /home/root directory of the development board Linux system. Before executing the test program, we need to configure the sound card. We also use the amixer tool to configure, as follows:

amixer sset Capture 58,58 //录制音量大小
amixer sset 'ADC PCM' 200,200 //PCM ADC
# 左声道Mixer Boost 管理
amixer sset 'Left Input Mixer Boost' off
amixer sset 'Left Boost Mixer LINPUT1' off
amixer sset 'Left Input Boost Mixer LINPUT1' 0
amixer sset 'Left Boost Mixer LINPUT2' off
amixer sset 'Left Input Boost Mixer LINPUT2' 0
amixer sset 'Left Boost Mixer LINPUT3' off
amixer sset 'Left Input Boost Mixer LINPUT3' 0
# 右声道Mixer Boost 管理
amixer sset 'Right Input Mixer Boost' on
amixer sset 'Right Boost Mixer RINPUT1' on
amixer sset 'Right Input Boost Mixer RINPUT1' 5
amixer sset 'Right Boost Mixer RINPUT2' on
amixer sset 'Right Input Boost Mixer RINPUT2' 5
amixer sset 'Right Boost Mixer RINPUT3' off
amixer sset 'Right Input Boost Mixer RINPUT3' 0



Insert image description here
Why should the Mixer Boost of the left and right channels be configured in this way? This has something to do with hardware design, so we won’t explain it here. For specific details, please refer to the audio driver chapter in the "I.MX6U Embedded Linux Driver Development Guide" document.
Next, execute the test program for recording, as shown below:
Figure 28.5.15 Recording

After executing the test program, the recording starts. Then we can speak to the microphone (MIC) on the baseboard. The onboard MIC is as follows: the
Insert image description here
program will record what we say; if we want to stop recording, we can only Terminate the process and press Ctrl+C to terminate the application; at this time, the cap.wav audio file will be generated in the current directory, as shown below: The
Insert image description here
generated file is a pure audio data file, not a WAV format file, because this file There is no header information. If the program detects that the file is not a WAV format file, it will exit directly, so the previous section cannot be used directly.28.5.5 test program playback

cap.wav file, pay attention here! Of course, you can modify the sample code in the previous section, or you can directly use the aplay tool to play the recorded audio, as follows:

aplay -f cd cap.wav

Figure 28.5.18 Use aplay to play the recorded audio.
If the recording is normal, the sound played by aplay is the sound we recorded!
LINE_IN test
In addition to the microphone, there is also a LINE_IN interface on the bottom of the development board, which is the line input, as shown in the figure below: The
Insert image description here
left one in the above figure is the headphone interface, and the right one is the LINE_IN interface, which supports audio input. We passed this test The program tests the LINE_IN
interface and collects the audio input from the LINE_IN interface. During the test, we use a 3.5mm male-to-male audio cable. One end is connected to a mobile phone or computer, and the other end is connected to the LINE_IN interface. Then the mobile phone or computer plays music, and the audio data will be input to the development board through the LINE_IN interface
. Our application captures (records).
Before testing, we need to configure the sound card as follows:

amixer sset Capture 58,58 //录制音量大小
amixer sset 'ADC PCM' 200,200 //PCM ADC
# 左声道Mixer Boost 管理
amixer sset 'Left Input Mixer Boost' off
amixer sset 'Left Boost Mixer LINPUT1' off
amixer sset 'Left Input Boost Mixer LINPUT1' 0
amixer sset 'Left Boost Mixer LINPUT2' on
amixer sset 'Left Input Boost Mixer LINPUT2' 5
amixer sset 'Left Boost Mixer LINPUT3' off
amixer sset 'Left Input Boost Mixer LINPUT3' 0
# 右声道Mixer Boost 管理
amixer sset 'Right Input Mixer Boost' on
amixer sset 'Right Boost Mixer RINPUT1' off
amixer sset 'Right Input Boost Mixer RINPUT1' 0
amixer sset 'Right Boost Mixer RINPUT2' off
amixer sset 'Right Input Boost Mixer RINPUT2' 0
amixer sset 'Right Boost Mixer RINPUT3' on
amixer sset 'Right Input Boost Mixer RINPUT3' 5

After configuration, you can test it. After executing the program, the mobile phone or computer plays music, and the development board collects the audio data input from the LINE_IN interface. The test method is the same as the MIC microphone. You can test it yourself!

Use asynchronous mode

Sample code 28.5.1 and sample code 28.5.2 in the previous section both use synchronous reading and writing, which will prevent the application from doing other things. In this section we will learn how to use asynchronous reading and writing.
In fact, reading and writing in asynchronous mode is very simple. You only need to register an asynchronous processing function.
snd_async_add_pcm_handler() function
alsa-lib provides the snd_async_add_pcm_handler() function for registering asynchronous processing functions. In fact, we only need to register an asynchronous processing function through this function. Its function prototype is as follows:

int snd_async_add_pcm_handler(snd_async_handler_t **handler,
snd_pcm_t *pcm,
snd_async_callback_t callback,
void *private_data
)

Calling this function needs to pass in 4 parameters:
⚫ handler: The parameter snd_async_handler_t is used to describe an asynchronous processing, so a snd_async_handler_t object represents an asynchronous processing object; calling the snd_async_add_pcm_handler() function will instantiate a snd_async_handler_t object and set the object pointer ( The pointer serves as the handle of the asynchronous processing object) and is returned through *handler.
⚫ pcm: The handle of the pcm device.
⚫ callback: asynchronous processing function (or callback function), the snd_async_callback_t function pointer is as follows:

typedef void(*snd_async_callback_t)(snd_async_handler_t *handler)

The parameter handler is the handle of the asynchronous processing object.
⚫ private_data: The private data passed to the asynchronous processing function. The data type of the private data can be defined by the user.
When calling the snd_async_add_pcm_handler() function, the parameter private_date points to your private data object. Private data can be obtained in the asynchronous processing function by calling the snd_async_handler_get_callback_private() function, as shown below:

struct my_private_data *data = snd_async_handler_get_callback_private(handler);

That’s all I’ll tell you about the parameters of the snd_async_add_pcm_handler() function. After calling this function, the PCM device passed in by the user will be associated with the asynchronous processing object. In the asynchronous processing function callback, the handle of the PCM device can be obtained through the handle of the asynchronous processing object, obtained through snd_async_handler_get_pcm(), as shown below :

snd_pcm_t *pcm_handle = snd_async_handler_get_pcm(handler);

To implement asynchronous I/O, applications usually need to complete these three things:
⚫ Enable asynchronous I/O;
⚫ Set the owner of asynchronous I/O;
⚫ Register a signal processing function (such as SIGIO signal or other real-time signal).
This content was introduced to you in detail in Section 13.3, so I won’t go into details here! So it can be seen that the snd_async_add_pcm_handler
function has helped us complete these things.
Example usage:

static void snd_playback_async_callback(snd_async_handler_t *handler)
{
    
    
	snd_pcm_t *handle = snd_async_handler_get_pcm(handler);//获取PCM 句柄
	......
}

int main(void)
{
    
    
	......
	snd_async_handler_t *async_handler = NULL;
	/* 注册异步处理函数*/
	ret = snd_async_add_pcm_handler(&async_handler, pcm, snd_playback_async_callback, NULL);
	if (0 > ret)
	fprintf(stderr, "snd_async_add_pcm_handler error: %s\n", snd_strerror(ret));
	......
}

Call snd_async_add_pcm_handler() to register the asynchronous callback function snd_playback_async_callback(). When the ring buffer has free cycles to fill with data (take playback as an example), the audio device driver will send a signal (SIGIO) to the application, and then the application will Will jump to snd_playback_async_callback() function execution.
For recording, when there is data to read in the ring buffer (for example, the audio device has recorded a cycle and written the data to the ring buffer), the driver will send a signal to the application, and then the application Jump to callback function execution.
In the case of playback, we usually fill the ring buffer first. When the audio device completes one cycle of playback, an idle cycle will be generated. At this time, the application will receive the signal and jump to the asynchronous callback function for execution. .
snd_pcm_avail_update() function
We usually use this function in asynchronous processing functions. In the case of recording, the application calls the snd_pcm_avail_update()
function to obtain the current number of frames that can be read; in the case of playback, the application calls the snd_pcm_avail_update() function. Function is used to get the current number of writable frames. In other words, it is how many frames of data can currently be read (recorded) or written (played) in the driver layer ring buffer. The application can only write data when the ring buffer is not full.
The function prototype is as follows:

snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);

本小节主要给大家介绍这两个函数,因为后面的示例代码中会使用到。

PCM 播放示例-异步方式

通过上面的介绍,本小节我们来编写一个使用异步方式的PCM 播放示例程序,直接基于示例代码28.5.1
进行修改,代码笔者已经写好了,如下所示:
本例程源码对应的路径为:开发板光盘->11 、Linux C 应用编程例程源码->28_alsa-lib->pcm_playback_async.c。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_PLAYBACK_DEV "hw:0,0"
/************************************
WAV 音频文件解析相关数据结构申明
************************************/
typedef struct WAV_RIFF
{
    
    
    char ChunkID[4];     /* "RIFF" */
    u_int32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数*/
    char Format[4];      /* "WAVE" */
} __attribute__((packed)) RIFF_t;
typedef struct WAV_FMT
{
    
    
    char Subchunk1ID[4];     /* "fmt " */
    u_int32_t Subchunk1Size; /* 16 for PCM */
    u_int16_t AudioFormat;   /* PCM = 1*/
    u_int16_t NumChannels;   /* Mono = 1, Stereo = 2, etc. */
    u_int32_t SampleRate;    /* 8000, 44100, etc. */
    u_int32_t ByteRate;      /* = SampleRate * NumChannels * BitsPerSample/8 */
    u_int16_t BlockAlign;    /* = NumChannels * BitsPerSample/8 */
    u_int16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__((packed)) FMT_t;
static FMT_t wav_fmt;
typedef struct WAV_DATA
{
    
    
    char Subchunk2ID[4];     /* "data" */
    u_int32_t Subchunk2Size; /* data size */
} __attribute__((packed)) DATA_t;
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                // pcm 句柄
static unsigned int buf_bytes;               // 应用程序缓冲区的大小(字节为单位)
static void *buf = NULL;                     // 指向应用程序缓冲区的指针
static int fd = -1;                          // 指向WAV 音频文件的文件描述符
static snd_pcm_uframes_t period_size = 1024; // 周期大小(单位: 帧)
static unsigned int periods = 16;            // 周期数(设备驱动层buffer 的大小)
/************************************
static 静态函数
************************************/
static void snd_playback_async_callback(snd_async_handler_t *handler)
{
    
    
    snd_pcm_t *handle = snd_async_handler_get_pcm(handler); // 获取PCM 句柄
    snd_pcm_sframes_t avail;
    int ret;
    avail = snd_pcm_avail_update(handle); // 获取环形缓冲区中有多少帧数据需要填充
    while (avail >= period_size)
    {
    
                                     // 我们一次写入一个周期
        memset(buf, 0x00, buf_bytes); // buf 清零
        ret = read(fd, buf, buf_bytes);
        if (0 >= ret)
            goto out;
        ret = snd_pcm_writei(handle, buf, period_size);
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
            goto out;
        }
        else if (ret < period_size)
        {
    
     // 实际写入的帧数小于指定的帧数
            // 此时我们需要调整下音频文件的读位置重新读取没有播放出去的数据
            // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes 个字节
            // frame_bytes 表示一帧的字节大小
            if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
            {
    
    
                perror("lseek error");
                goto out;
            }
        }
        avail = snd_pcm_avail_update(handle); // 再次获取、更新avail
    }
    return;
out:
    snd_pcm_close(handle); // 关闭pcm 设备
    free(buf);
    close(fd);          // 关闭打开的音频文件
    exit(EXIT_FAILURE); // 退出程序
}
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    snd_async_handler_t *async_handler = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_PLAYBACK_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(驱动层环形缓冲区buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    buf_bytes = period_size * wav_fmt.BlockAlign; // 变量赋值,一个周期的字节大小
    /* 注册异步处理函数*/
    ret = snd_async_add_pcm_handler(&async_handler, pcm, snd_playback_async_callback, NULL);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_async_add_pcm_handler error: %s\n", snd_strerror(ret));
        goto err1;
    }
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
static int open_wav_file(const char *file)
{
    
    
    RIFF_t wav_riff;
    DATA_t wav_data;
    int ret;
    fd = open(file, O_RDONLY);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));
        return -1;
    }
    /* 读取RIFF chunk */
    ret = read(fd, &wav_riff, sizeof(RIFF_t));
    if (sizeof(RIFF_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("RIFF", wav_riff.ChunkID, 4) || // 校验
        strncmp("WAVE", wav_riff.Format, 4))
    {
    
    
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 读取sub-chunk-fmt */
    ret = read(fd, &wav_fmt, sizeof(FMT_t));
    if (sizeof(FMT_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4))
    {
    
     // 校验
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 打印音频文件的信息*/
    printf("<<<<音频文件格式信息>>>>\n\n");
    printf(" file name: %s\n", file);
    printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);
    printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);
    printf(" NumChannels: %u\n", wav_fmt.NumChannels);
    printf(" SampleRate: %u\n", wav_fmt.SampleRate);
    printf(" ByteRate: %u\n", wav_fmt.ByteRate);
    printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);
    printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);
    /* sub-chunk-data */
    if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,
                  SEEK_SET))
    {
    
    
        perror("lseek error");
        close(fd);
        return -1;
    }
    while (sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t)))
    {
    
    
        /* 找到sub-chunk-data */
        if (!strncmp("data", wav_data.Subchunk2ID, 4)) // 校验
            return 0;
        if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR))
        {
    
    
            perror("lseek error");
            close(fd);
            return -1;
        }
    }
    fprintf(stderr, "check error: %s\n", file);
    return -1;
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    snd_pcm_sframes_t avail;
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 打开WAV 音频文件*/
    if (open_wav_file(argv[1]))
        exit(EXIT_FAILURE);
    /* 初始化PCM Playback 设备*/
    if (snd_pcm_init())
        goto err1;
    /* 申请读缓冲区*/
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err2;
    }
    /* 播放:先将环形缓冲区填满数据*/
    avail = snd_pcm_avail_update(pcm); // 获取环形缓冲区中有多少帧数据需要填充
    while (avail >= period_size)
    {
    
                                     // 我们一次写入一个周期
        memset(buf, 0x00, buf_bytes); // buf 清零
        ret = read(fd, buf, buf_bytes);
        if (0 >= ret)
            goto err3;
        ret = snd_pcm_writei(pcm, buf, period_size); // 向环形缓冲区中写入数据
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
            goto err3;
        }
        else if (ret < period_size)
        {
    
     // 实际写入的帧数小于指定的帧数
            // 此时我们需要调整下音频文件的读位置
            // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes 个字节
            // frame_bytes 表示一帧的字节大小
            if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
            {
    
    
                perror("lseek error");
                goto err3;
            }
        }
        avail = snd_pcm_avail_update(pcm); // 再次获取、更新avail
    }
    for (;;)
    {
    
    
        /* 主程序可以做一些其它的事,当环形缓冲区有空闲周期需要写入数据时
         * 音频设备驱动程序会向应用程序发送SIGIO 信号
         * 接着应用程序跳转到snd_playback_async_callback()函数执行*/
        // do_something();
        sleep(1);
    }
err3:
    free(buf); // 释放内存
err2:
    snd_pcm_close(pcm); // 关闭pcm 设备
err1:
    close(fd); // 关闭打开的音频文件
    exit(EXIT_FAILURE);
}

在snd_pcm_init() 函数中,我们调用了snd_async_add_pcm_handler() 函数注册了异步回调函数
snd_playback_async_callback(),当可写入数据时,跳转到snd_playback_async_callback()函数去执行。
在异步回调函数中,我们首先调用snd_pcm_avail_update()获取当前可写入多少帧数据,然后在while()
循环中调用read()读取音频文件的数据、接着调用snd_pcm_writei()向环形缓冲区写入数据,每次循环写入一个周期,直到把缓冲区写满,然后退出回调函数。
回到main()函数中,在进入for()死循环之前,我们先将环形缓冲区填满,执行的代码与回调函数中的代码相同,这里就不再说明了!
编译示例代码
在Ubuntu 系统下执行命令,编译示例代码:

${
    
    CC} -o testApp testApp.c -lasound

Insert image description here
测试应用程序
将上面编译得到的可执行文件拷贝开发板Linux 系统/home/root 目录下,然后在开发板上测试,大家自己去测!

PCM 录音示例-异步方式

This section writes a sample program for PCM recording using asynchronous mode. The code has been written by the author, as shown below: The
path corresponding to the source code of this routine is: development board CD->11, Linux C application programming routine source code->28_alsa -lib->pcm_capture_async.c.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_CAPTURE_DEV "hw:0,0"
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                // pcm 句柄
static unsigned int buf_bytes;               // 应用层缓冲区的大小(字节为单位)
static void *buf = NULL;                     // 指向应用层缓冲区的指针
static int fd = -1;                          // 输出文件的文件描述符
static snd_pcm_uframes_t period_size = 1024; // 周期大小(单位: 帧)
static unsigned int periods = 16;            // 周期数(驱动层环形缓冲区的大小)
static unsigned int rate = 44100;            // 采样率
/************************************
static 静态函数
************************************/
static void snd_capture_async_callback(snd_async_handler_t *handler)
{
    
    
    snd_pcm_t *handle = snd_async_handler_get_pcm(handler);
    snd_pcm_sframes_t avail;
    int ret;
    avail = snd_pcm_avail_update(handle); // 检查有多少帧数据可读
    while (avail >= period_size)
    {
    
     // 每次读取一个周期
        // memset(buf, 0x00, buf_bytes); //buf 清零
        ret = snd_pcm_readi(handle, buf, period_size); // 读取PCM 数据一个周期
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_readi error: %s\n", snd_strerror(ret));
            goto out;
        }
        // snd_pcm_readi 的返回值ret 等于实际读取的帧数* 4 转为字节数
        ret = write(fd, buf, ret * 4); // 将读取到的数据写入文件中
        if (0 >= ret)
            goto out;
        avail = snd_pcm_avail_update(handle); // 再次读取、更新avail
    }
    return;
out:
    snd_pcm_close(handle); // 关闭pcm 设备
    free(buf);
    close(fd);          // 关闭打开的音频文件
    exit(EXIT_FAILURE); // 退出程序
}
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    snd_async_handler_t *async_handler = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_CAPTURE_DEV, SND_PCM_STREAM_CAPTURE, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_CAPTURE_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, rate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    /* 注册异步处理函数*/
    ret = snd_async_add_pcm_handler(&async_handler, pcm, snd_capture_async_callback, NULL);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_async_add_pcm_handler error: %s\n", snd_strerror(ret));
        goto err1;
    }
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <output_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 初始化PCM Capture 设备*/
    if (snd_pcm_init())
        exit(EXIT_FAILURE);
    /* 申请读缓冲区*/
    buf_bytes = period_size * 4; // 字节大小= 周期大小*帧的字节大小16 位双声道
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err1;
    }
    /* 打开一个新建文件*/
    fd = open(argv[1], O_WRONLY | O_CREAT | O_EXCL);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", argv[1], strerror(errno));
        goto err2;
    }
    /* 录音*/
    ret = snd_pcm_start(pcm); // 开始录音
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_start error: %s\n", snd_strerror(ret));
        goto err3;
    }
    for (;;)
    {
    
    
        /* 主程序可以做一些其它的事,当环形缓冲区有数据可读时
         * 音频设备驱动程序会向应用程序发送SIGIO 信号
         * 接着应用程序跳转到snd_capture_async_callback()函数执行、读取数据*/
        // do_something();
        sleep(1);
    }
err3:
    close(fd); // 关闭文件
err2:
    free(buf); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    exit(EXIT_FAILURE);
}

This code is adapted based on sample code 28.5.2 and uses an asynchronous method to read the recorded audio data.
The code will not be explained anymore. It is worth noting that in the main() function we called the snd_pcm_start() function. This function has not been introduced to you before. The function of this function is actually as it is named, used to start the PCM device. , for example, in the case of recording, call this function to start recording; in the case of playback, call this function to start playing.
Why is this function not called in the previous sample codes? We will save this question for now and introduce it to you later!
Compile the sample code
Execute the command to compile the sample code:

${
    
    CC} -o testApp testApp.c -lasound

The test application
copies the compiled executable file to the /home/root directory of the development board Linux system, and then performs the test. The test method is the same as the test program corresponding to sample code 28.5.2. It will not be repeated here. You can go by yourself. Measurement!

Use poll() function

In the previous section, we used asynchronous I/O to read and write PCM devices. In this section, we will learn how to use poll I/O multiplexing to read and write data.

Use poll I/O multiplexing to implement reading and writing

I/O multiplexing is a kind of advanced I/O. It is introduced in detail in Section 13.2 of the first article. I/O multiplexing can be realized through the select() or poll() function. This article In this section, we use the poll() function to implement I/O multiplexing, which we will introduce to you next!
Get the count: snd_pcm_poll_descriptors_count
This function is used to get the polling descriptor count of the PCM handle. Its function prototype is as follows:
int snd_pcm_poll_descriptors_count(snd_pcm_t *pcm);
Call this function to return the polling descriptor count of the PCM handle.
Allocate struct pollfd object
Allocate a struct pollfd object for each polling descriptor, for example:

struct pollfd *pfds = NULL;
int count;
/* 获取PCM 句柄的轮询描述符计数*/
count = snd_pcm_poll_descriptors_count(pcm);
if (0 >= count)
{
    
    
    fprintf(stderr, "Invalid poll descriptors count\n");
    return -1;
}
/* 分配内存*/
pfds = calloc(count, sizeof(struct pollfd));
if (NULL == pfds)
{
    
    
    perror("calloc error");
    return -1;
}

Fill struct pollfd: snd_pcm_poll_descriptors
Next, call the snd_pcm_poll_descriptors() function to fill (initialize) the struct pollfd object. Its function prototype is as follows:

int snd_pcm_poll_descriptors(
	snd_pcm_t *pcm,
	struct pollfd *pfds,
	unsigned int space
);

The parameter space represents the number of elements in the pfds array.

/* 填充pfds */
ret = snd_pcm_poll_descriptors(pcm, pfds, count);
if (0 > ret)
return -1;

poll+snd_pcm_poll_descriptors_revents
After all preparations are completed, you can call the poll() function to monitor whether the PCM device has data that can be read or written. When there is data that can be read or written, the poll() function returns. At this time we can call snd_pcm_poll_descriptors_revents () function gets the event type returned in the file descriptor and compares it with the events flag of poll to determine whether it is readable or writable. The snd_pcm_poll_descriptors_revents() function
prototype is as follows:

int snd_pcm_poll_descriptors_revents(
	snd_pcm_t *pcm,
	struct pollfd *pfds,
	unsigned int nfds,
	unsigned short *revents
)

The parameter nfds represents the number of elements in the pfds array. Call this function to obtain the events returned in the file descriptor and return them through the parameter revents; note that do not directly read the revents member variable in the struct pollfd object because the snd_pcm_poll_descriptors_revents() function
will "Explode" the revents mask returned by the poll() system call to correct semantics (POLLIN = read, POLLOUT = write).
Example usage:

for (;;)
{
    
    
    ret = poll(pfds, count, -1); // 调用poll
    if (0 > ret)
    {
    
    
        perror("poll error");
        return -1;
    }
    ret = snd_pcm_poll_descriptors_revents(pcm, pfds, count, &revents);
    if (0 > ret)
        return -1;
    if (revents & POLLERR) // 发生I/O 错误
        return -1;
    if (revents & POLLIN)
    {
    
     // 表示可读取数据
        // 从PCM 设备读取数据
    }
    if (revents & POLLOUT)
    {
    
     // 表示可写入数据
        // 将数据写入PCM 设备
    }
}

PCM playback sample code

Modify the sample code 28.5.1 and use poll I/O multiplexing. The sample code is as follows: The
path corresponding to the source code of this routine is: Development board CD->11, Linux C application programming routine source code-> 28_alsa-lib->pcm_playback_poll.c.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_PLAYBACK_DEV "hw:0,0"
/************************************
WAV 音频文件解析相关数据结构申明
************************************/
typedef struct WAV_RIFF
{
    
    
    char ChunkID[4];     /* "RIFF" */
    u_int32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数*/
    char Format[4];      /* "WAVE" */
} __attribute__((packed)) RIFF_t;
typedef struct WAV_FMT
{
    
    
    char Subchunk1ID[4];     /* "fmt " */
    u_int32_t Subchunk1Size; /* 16 for PCM */
    u_int16_t AudioFormat;   /* PCM = 1*/
    u_int16_t NumChannels;   /* Mono = 1, Stereo = 2, etc. */
    u_int32_t SampleRate;    /* 8000, 44100, etc. */
    u_int32_t ByteRate;      /* = SampleRate * NumChannels * BitsPerSample/8 */
    u_int16_t BlockAlign;    /* = NumChannels * BitsPerSample/8 */
    u_int16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__((packed)) FMT_t;
static FMT_t wav_fmt;
typedef struct WAV_DATA
{
    
    
    char Subchunk2ID[4];     /* "data" */
    u_int32_t Subchunk2Size; /* data size */
} __attribute__((packed)) DATA_t;
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                // pcm 句柄
static unsigned int buf_bytes;               // 应用程序缓冲区的大小(字节为单位)
static void *buf = NULL;                     // 指向应用程序缓冲区的指针
static int fd = -1;                          // 指向WAV 音频文件的文件描述符
static snd_pcm_uframes_t period_size = 1024; // 周期大小(单位: 帧)
static unsigned int periods = 16;            // 周期数(设备驱动层buffer 的大小)
static struct pollfd *pfds = NULL;
static int count;
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_PLAYBACK_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(驱动层buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    buf_bytes = period_size * wav_fmt.BlockAlign; // 变量赋值,一个周期的字节大小
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
static int open_wav_file(const char *file)
{
    
    
    RIFF_t wav_riff;
    DATA_t wav_data;
    int ret;
    fd = open(file, O_RDONLY);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));
        return -1;
    }
    /* 读取RIFF chunk */
    ret = read(fd, &wav_riff, sizeof(RIFF_t));
    if (sizeof(RIFF_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("RIFF", wav_riff.ChunkID, 4) || // 校验
        strncmp("WAVE", wav_riff.Format, 4))
    {
    
    
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 读取sub-chunk-fmt */
    ret = read(fd, &wav_fmt, sizeof(FMT_t));
    if (sizeof(FMT_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4))
    {
    
     // 校验
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 打印音频文件的信息*/
    printf("<<<<音频文件格式信息>>>>\n\n");
    printf(" file name: %s\n", file);
    printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);
    printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);
    printf(" NumChannels: %u\n", wav_fmt.NumChannels);
    printf(" SampleRate: %u\n", wav_fmt.SampleRate);
    printf(" ByteRate: %u\n", wav_fmt.ByteRate);
    printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);
    printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);
    /* sub-chunk-data */
    if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,
                  SEEK_SET))
    {
    
    
        perror("lseek error");
        close(fd);
        return -1;
    }
    while (sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t)))
    {
    
    
        /* 找到sub-chunk-data */
        if (!strncmp("data", wav_data.Subchunk2ID, 4)) // 校验
            return 0;
        if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR))
        {
    
    
            perror("lseek error");
            close(fd);
            return -1;
        }
    }
    fprintf(stderr, "check error: %s\n", file);
    return -1;
}
static int snd_pcm_poll_init(void)
{
    
    
    int ret;
    /* 获取PCM 句柄的轮询描述符计数*/
    count = snd_pcm_poll_descriptors_count(pcm);
    if (0 >= count)
    {
    
    
        fprintf(stderr, "Invalid poll descriptors count\n");
        return -1;
    }
    /* 分配内存*/
    pfds = calloc(count, sizeof(struct pollfd));
    if (NULL == pfds)
    {
    
    
        perror("calloc error");
        return -1;
    }
    /* 填充pfds */
    ret = snd_pcm_poll_descriptors(pcm, pfds, count);
    if (0 > ret)
        return -1;
    return 0;
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    unsigned short revents;
    snd_pcm_sframes_t avail;
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 打开WAV 音频文件*/
    if (open_wav_file(argv[1]))
        exit(EXIT_FAILURE);
    /* 初始化PCM Playback 设备*/
    if (snd_pcm_init())
        goto err1;
    /* 申请读缓冲区*/
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err2;
    }
    /* I/O 多路复用poll 初始化*/
    if (snd_pcm_poll_init())
        goto err3;
    for (;;)
    {
    
    
        ret = poll(pfds, count, -1); // 调用poll
        if (0 > ret)
        {
    
    
            perror("poll error");
            goto err3;
        }
        ret = snd_pcm_poll_descriptors_revents(pcm, pfds, count, &revents);
        if (0 > ret)
            goto err3;
        if (revents & POLLERR)
            goto err3;
        if (revents & POLLOUT)
        {
    
                                          // 可写数据
            avail = snd_pcm_avail_update(pcm); // 获取环形缓冲区中有多少帧数据需要填充
            while (avail >= period_size)
            {
    
                                     // 我们一次写入一个周期
                memset(buf, 0x00, buf_bytes); // buf 清零
                ret = read(fd, buf, buf_bytes);
                if (0 >= ret)
                    goto err3;
                ret = snd_pcm_writei(pcm, buf, period_size);
                if (0 > ret)
                {
    
    
                    fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
                    goto err3;
                }
                else if (ret < period_size)
                {
    
    
                    if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
                    {
    
    
                        perror("lseek error");
                        goto err3;
                    }
                }
                avail = snd_pcm_avail_update(pcm); // 再次获取、更新avail
            }
        }
    }
err3:
    free(buf); // 释放内存
err2:
    snd_pcm_close(pcm); // 关闭pcm 设备
err1:
    close(fd); // 关闭打开的音频文件
    exit(EXIT_FAILURE);
}

PCM recording sample code

Modify the sample code 28.5.2 and use poll I/O multiplexing. The sample code is as follows: The
path corresponding to the source code of this routine is: Development board CD->11, Linux C application programming routine source code-> 28_alsa-lib->pcm_capture_poll.c.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_CAPTURE_DEV "hw:0,0"
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                // pcm 句柄
static snd_pcm_uframes_t period_size = 1024; // 周期大小(单位: 帧)
static unsigned int periods = 16;            // 周期数(buffer 的大小)
static unsigned int rate = 44100;            // 采样率
static struct pollfd *pfds = NULL;
static int count;
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_CAPTURE_DEV, SND_PCM_STREAM_CAPTURE, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_CAPTURE_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, rate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, 2);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
static int snd_pcm_poll_init(void)
{
    
    
    int ret;
    /* 获取PCM 句柄的轮询描述符计数*/
    count = snd_pcm_poll_descriptors_count(pcm);
    if (0 >= count)
    {
    
    
        fprintf(stderr, "Invalid poll descriptors count\n");
        return -1;
    }
    /* 分配内存*/
    pfds = calloc(count, sizeof(struct pollfd));
    if (NULL == pfds)
    {
    
    
        perror("calloc error");
        return -1;
    }
    /* 填充pfds */
    ret = snd_pcm_poll_descriptors(pcm, pfds, count);
    if (0 > ret)
        return -1;
    return 0;
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    unsigned char *buf = NULL;
    unsigned int buf_bytes;
    unsigned short revents;
    snd_pcm_sframes_t avail;
    int fd = -1;
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <output_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 初始化PCM Capture 设备*/
    if (snd_pcm_init())
        exit(EXIT_FAILURE);
    /* 申请读缓冲区*/
    buf_bytes = period_size * 4; // 字节大小= 周期大小*帧的字节大小16 位双声道
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err1;
    }
    /* 打开一个新建文件*/
    fd = open(argv[1], O_WRONLY | O_CREAT | O_EXCL);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", argv[1], strerror(errno));
        goto err2;
    }
    /* I/O 多路复用poll 初始化*/
    if (snd_pcm_poll_init())
        goto err3;
    /* 开始录音*/
    ret = snd_pcm_start(pcm);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_start error: %s\n", snd_strerror(ret));
        goto err3;
    }
    for (;;)
    {
    
    
        ret = poll(pfds, count, -1); // 调用poll
        if (0 > ret)
        {
    
    
            perror("poll error");
            goto err3;
        }
        ret = snd_pcm_poll_descriptors_revents(pcm, pfds, count, &revents);
        if (0 > ret)
            goto err3;
        if (revents & POLLERR)
            goto err3;
        if (revents & POLLIN)
        {
    
                                          // 可读数据
            avail = snd_pcm_avail_update(pcm); // 检查有多少帧数据可读
            while (avail >= period_size)
            {
    
                                                   // 每次读取一个周期
                ret = snd_pcm_readi(pcm, buf, period_size); // 读取PCM 数据一个周期
                if (0 > ret)
                {
    
    
                    fprintf(stderr, "snd_pcm_readi error: %s\n", snd_strerror(ret));
                    goto err3;
                }
                ret = write(fd, buf, ret * 4); // 将读取到的数据写入文件中
                if (0 >= ret)
                    goto err3;
                avail = snd_pcm_avail_update(pcm); // 再次读取、更新avail
            }
        }
    }
err3:
    close(fd); // 关闭文件
err2:
    free(buf); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    exit(EXIT_FAILURE);
}

Status of the PCM device

This section introduces you to the status of the PCM device. alsa-lib provides the function snd_pcm_state() to obtain the current status of the PCM device. Its function prototype is as follows:

snd_pcm_state_t snd_pcm_state(snd_pcm_t *pcm);

You can see that its return value is a variable of type snd_pcm_state_t. snd_pcm_state_t is actually an enumeration type that describes all states contained in the PCM device, as shown below:

enum snd_pcm_state_t {
    
    
	SND_PCM_STATE_OPEN = 0,
	SND_PCM_STATE_SETUP,
	SND_PCM_STATE_PREPARED,
	SND_PCM_STATE_RUNNING,
	SND_PCM_STATE_XRUN,
	SND_PCM_STATE_DRAINING,
	SND_PCM_STATE_PAUSED,
	SND_PCM_STATE_SUSPENDED,
	SND_PCM_STATE_DISCONNECTED,
	SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED,
	SND_PCM_STATE_PRIVATE1 = 1024
}

SND_PCM_STATE_OPEN
This state indicates that the PCM device is in the open state. For example, when snd_pcm_open() is called, the PCM device is in this state.
SND_PCM_STATE_SETUP
The explanation in the alsa-lib documentation is "Setup installed"! This status indicates that the device has been initialized and the parameters have been configured.
SND_PCM_STATE_PREPARED
This state indicates that the device is ready and can start "Ready to start"! For example, you can start playing, you can start recording.

This state was mentioned earlier, when the application calls the snd_pcm_hw_params() function, the device is in the
SND_PCM_STATE_PREPARED state. In the application, you can call the snd_pcm_prepare() function to put the device in the
SND_PCM_STATE_PREPARED state. The function prototype is as follows:

int snd_pcm_prepare(snd_pcm_t *pcm);

The line number call returns 0 successfully, and a negative error code will be returned if it fails.
If the function call is successful, the PCM device will be in the SND_PCM_STATE_PREPARED state. In fact, when the application calls
snd_pcm_hw_params(), snd_pcm_prepare() is automatically called inside the function, so why is
the device already in the SND_PCM_STATE_PREPARED state after calling snd_pcm_hw_params()? When the snd_pcm_hw_params() function is called, two states should actually occur. Transition: first from SND_PCM_STATE_OPEN to SND_PCM_STATE_SETUP state, and then from
SND_PCM_STATE_SETUP to SND_PCM_STATE_PREPARED state.
SND_PCM_STATE_RUNNING
This status indicates that the device is running, such as playing or recording.
We mentioned in the previous section that the application can call the snd_pcm_start() function to start the PCM device. After the startup is successful, the device starts playing or collecting, and the device is in the SND_PCM_STATE_RUNNING state.
In addition, when the device is in the SND_PCM_STATE_PREPARED state and the application calls snd_pcm_readi/snd_pcm_writei
to read and write data, these functions will automatically call the snd_pcm_start() function internally; for example, in playback mode, after calling snd_pcm_writei to write data, the PCM device will automatically be turned on. Play, please note here that PCM must be turned on after the data is written to the ring buffer.
The device plays audio, because once it is turned on, there must be at least one cycle of data in the ring buffer for the audio device to play, otherwise an underrun will occur and the function call will return in the form of an error; in recording mode, call After calling the snd_pcm_readi() function, PCM is automatically turned on for audio collection. So this is why the snd_pcm_start() function
is not called in these examples of sample code 28.5.1, sample code 28.5.2, and sample code 28.6.1 . When the device is running, the application can call the snd_pcm_drop() or snd_pcm_drain() function to stop the device, such as stopping playback or audio collection; their function prototypes are as follows:

int snd_pcm_drain(snd_pcm_t *pcm);
int snd_pcm_drop(snd_pcm_t *pcm);

Function call returns 0 successfully; failure returns a negative error code.
Both functions can stop the device, and their differences are as follows:
⚫ The snd_pcm_drop() function will stop PCM immediately and discard the hung frames;
⚫ The snd_pcm_drain() function will not stop PCM immediately, but will handle the hang PCM will be stopped after the frames; for playback, it will wait until all frames to be played are completed (which should be the data to be played in the ring buffer), and then stop PCM; for recording, residual frames will be retrieved before stopping PCM.
After calling snd_pcm_drop() or snd_pcm_drain() to stop the PCM device, the device will return to the SND_PCM_STATE_SETUP state.
SND_PCM_STATE_XRUN
When XRUN occurs, the device will be in the SND_PCM_STATE_XRUN state. XRUN has been explained to you before and will not be repeated here! When in the SND_PCM_STATE_XRUN state, the application can call snd_pcm_prepare() to restore the device to the SND_PCM_STATE_PREPARED state.
The author of SND_PCM_STATE_DRAINING
is not clear about this state. The explanation in the alsa-lib document is "Draining: running (playback) or stopped (capture)".
SND_PCM_STATE_PAUSED

pause means pause, so this status indicates that the device is in a paused state. For example, when the device is running (that is, in
the SND_PCM_STATE_RUNNING state), the application calls the snd_pcm_pause() function to pause the device. The function prototype is as follows:

int snd_pcm_pause(snd_pcm_t *pcm, int enable);

The function snd_pcm_pause() can both pause and resume the device (resume operation from pause, that is,
SND_PCM_STATE_RUNNING—>SND_PCM_STATE_RUNNING), and is controlled by the parameter enable; when enable is equal to
1, it means to pause the device; when enable is equal to 0, it means to enable the device. The device resumes operation.
The snd_pcm_pause() function call returns 0 successfully; fails and returns a negative error code.
There is a problem to note here. Not all audio device hardware supports the pause function. You can use the
snd_pcm_hw_params_can_pause() function to determine whether the device supports pause. The function prototype is as follows:

int snd_pcm_hw_params_can_pause(const snd_pcm_hw_params_t *params);

The function returns 1 to indicate that the hardware supports pause; returns 0 to indicate that the hardware does not support pause.
SND_PCM_STATE_SUSPENDED
This status indicates that the hardware has been suspended. If the hardware is suspended, the application can call the snd_pcm_resume() function to recover from the suspension and ensure that sample data will not be lost (fine recovery). The snd_pcm_resume() function prototype is as follows:

int snd_pcm_resume(snd_pcm_t *pcm);

A successful function call returns 0; a failure returns a negative error code.
Of course, not all hardware supports this function. You can call the snd_pcm_hw_params_can_resume() function to determine whether the hardware supports recovery from suspend. The function prototype is as follows:

int snd_pcm_hw_params_can_resume(const snd_pcm_hw_params_t *params);

The function call returns 1 if supported and 0 if not supported.
SND_PCM_STATE_DISCONNECTED
This status indicates that the hardware has been disconnected.
The transition between states
Through the above introduction, we already know several different states of PCM devices and their transition relationship. In order to deepen your impression, the author sorts them out, mainly sorting out SND_PCM_STATE_OPEN, SND_PCM_STATE_SETUP ,
SND_PCM_STATE_PREPARED, SND_PCM_STATE_RUNNING, SND_PCM_STATE_XRUN and
SND_PCM_STATE_PAUSED. The conversion relationship between these six states is as shown in the figure below:

Insert image description here

The author tried his best! This picture is still a bit messy, but it doesn't matter, the state transition is still clearly described. In fact, the conversion relationship between these states is not difficult to understand. It is easy to understand which state can be converted to which state and which state cannot be converted to which state. I won’t say more here.

PCM playback sample code-add status control

Through the above introduction, we already know the transition between different states of PCM equipment, such as how to pause, how to stop, and how to resume when playing music. In this section, we will write a PCM playback program. Based on the sample code 28.6.1, we will add control over the playback process. For example, the user can pause playback by pressing the space bar, and resume playback by pressing the space bar again.
The sample code has been written by the author, as shown below.
The paths corresponding to the source code of this routine are: development board CD->11, Linux C application programming routine source code->28_alsa-lib->pcm_playback_ctl.c.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <signal.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_PLAYBACK_DEV "hw:0,0"
/************************************
WAV 音频文件解析相关数据结构申明
************************************/
typedef struct WAV_RIFF
{
    
    
    char ChunkID[4];     /* "RIFF" */
    u_int32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数*/
    char Format[4];      /* "WAVE" */
} __attribute__((packed)) RIFF_t;
typedef struct WAV_FMT
{
    
    
    char Subchunk1ID[4];     /* "fmt " */
    u_int32_t Subchunk1Size; /* 16 for PCM */
    u_int16_t AudioFormat;   /* PCM = 1*/
    u_int16_t NumChannels;   /* Mono = 1, Stereo = 2, etc. */
    u_int32_t SampleRate;    /* 8000, 44100, etc. */
    u_int32_t ByteRate;      /* = SampleRate * NumChannels * BitsPerSample/8 */
    u_int16_t BlockAlign;    /* = NumChannels * BitsPerSample/8 */
    u_int16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__((packed)) FMT_t;
static FMT_t wav_fmt;
typedef struct WAV_DATA
{
    
    
    char Subchunk2ID[4];     /* "data" */
    u_int32_t Subchunk2Size; /* data size */
} __attribute__((packed)) DATA_t;
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                // pcm 句柄
static unsigned int buf_bytes;               // 应用程序缓冲区的大小(字节为单位)
static void *buf = NULL;                     // 指向应用程序缓冲区的指针
static int fd = -1;                          // 指向WAV 音频文件的文件描述符
static snd_pcm_uframes_t period_size = 1024; // 周期大小(单位: 帧)
static unsigned int periods = 16;            // 周期数(设备驱动层buffer 的大小)
static struct termios old_cfg;               // 用于保存终端当前的配置参数
/************************************
static 静态函数
************************************/
static void snd_playback_async_callback(snd_async_handler_t *handler)
{
    
    
    snd_pcm_t *handle = snd_async_handler_get_pcm(handler); // 获取PCM 句柄
    snd_pcm_sframes_t avail;
    int ret;
    avail = snd_pcm_avail_update(handle); // 获取环形缓冲区中有多少帧数据需要填充
    while (avail >= period_size)
    {
    
                                     // 我们一次写入一个周期
        memset(buf, 0x00, buf_bytes); // buf 清零
        ret = read(fd, buf, buf_bytes);
        if (0 >= ret)
            goto out;
        ret = snd_pcm_writei(handle, buf, period_size);
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
            goto out;
        }
        else if (ret < period_size)
        {
    
     // 实际写入的帧数小于指定的帧数
            // 此时我们需要调整下音频文件的读位置
            // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes 个字节
            // frame_bytes 表示一帧的字节大小
            if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
            {
    
    
                perror("lseek error");
                goto out;
            }
        }
        avail = snd_pcm_avail_update(handle); // 再次获取、更新avail
    }
    return;
out:
    snd_pcm_drain(pcm);                         // 停止PCM
    snd_pcm_close(handle);                      // 关闭pcm 设备
    tcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); // 退出前恢复终端的状态
    free(buf);
    close(fd);          // 关闭打开的音频文件
    exit(EXIT_FAILURE); // 退出程序
}
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    snd_async_handler_t *async_handler = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_PLAYBACK_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(驱动层环形缓冲区buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    buf_bytes = period_size * wav_fmt.BlockAlign; // 变量赋值,一个周期的字节大小
    /* 注册异步处理函数*/
    ret = snd_async_add_pcm_handler(&async_handler, pcm, snd_playback_async_callback, NULL);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_async_add_pcm_handler error: %s\n", snd_strerror(ret));
        goto err1;
    }
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
static int open_wav_file(const char *file)
{
    
    
    RIFF_t wav_riff;
    DATA_t wav_data;
    int ret;
    fd = open(file, O_RDONLY);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));
        return -1;
    }
    /* 读取RIFF chunk */
    ret = read(fd, &wav_riff, sizeof(RIFF_t));
    if (sizeof(RIFF_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("RIFF", wav_riff.ChunkID, 4) || // 校验
        strncmp("WAVE", wav_riff.Format, 4))
    {
    
    
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 读取sub-chunk-fmt */
    ret = read(fd, &wav_fmt, sizeof(FMT_t));
    if (sizeof(FMT_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4))
    {
    
     // 校验
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 打印音频文件的信息*/
    printf("<<<<音频文件格式信息>>>>\n\n");
    printf(" file name: %s\n", file);
    printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);
    printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);
    printf(" NumChannels: %u\n", wav_fmt.NumChannels);
    printf(" SampleRate: %u\n", wav_fmt.SampleRate);
    printf(" ByteRate: %u\n", wav_fmt.ByteRate);
    printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);
    printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);
    /* sub-chunk-data */
    if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,
                  SEEK_SET))
    {
    
    
        perror("lseek error");
        close(fd);
        return -1;
    }
    while (sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t)))
    {
    
    
        /* 找到sub-chunk-data */
        if (!strncmp("data", wav_data.Subchunk2ID, 4)) // 校验
            return 0;
        if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR))
        {
    
    
            perror("lseek error");
            close(fd);
            return -1;
        }
    }
    fprintf(stderr, "check error: %s\n", file);
    return -1;
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    snd_pcm_sframes_t avail;
    struct termios new_cfg;
    sigset_t sset;
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 屏蔽SIGIO 信号*/
    sigemptyset(&sset);
    sigaddset(&sset, SIGIO);
    sigprocmask(SIG_BLOCK, &sset, NULL);
    /* 打开WAV 音频文件*/
    if (open_wav_file(argv[1]))
        exit(EXIT_FAILURE);
    /* 初始化PCM Playback 设备*/
    if (snd_pcm_init())
        goto err1;
    /* 申请读缓冲区*/
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err2;
    }
    /* 终端配置*/
    tcgetattr(STDIN_FILENO, &old_cfg);                  // 获取终端<标准输入-标准输出构成了一套终端>
    memcpy(&new_cfg, &old_cfg, sizeof(struct termios)); // 备份
    new_cfg.c_lflag &= ~ICANON;                         // 将终端设置为非规范模式
    new_cfg.c_lflag &= ~ECHO;                           // 禁用回显
    tcsetattr(STDIN_FILENO, TCSANOW, &new_cfg);         // 使配置生效
    /* 播放:先将环形缓冲区填满数据*/
    avail = snd_pcm_avail_update(pcm); // 获取环形缓冲区中有多少帧数据需要填充
    while (avail >= period_size)
    {
    
                                     // 我们一次写入一个周期
        memset(buf, 0x00, buf_bytes); // buf 清零
        ret = read(fd, buf, buf_bytes);
        if (0 >= ret)
            goto err3;
        ret = snd_pcm_writei(pcm, buf, period_size); // 向环形缓冲区中写入数据
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
            goto err3;
        }
        else if (ret < period_size)
        {
    
     // 实际写入的帧数小于指定的帧数
            // 此时我们需要调整下音频文件的读位置
            // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes 个字节
            // frame_bytes 表示一帧的字节大小
            if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
            {
    
    
                perror("lseek error");
                goto err3;
            }
        }
        avail = snd_pcm_avail_update(pcm); // 再次获取、更新avail
    }
    sigprocmask(SIG_UNBLOCK, &sset, NULL); // 取消SIGIO 信号屏蔽
    char ch;
    for (;;)
    {
    
    
        ch = getchar(); // 获取用户输入的控制字符
        switch (ch)
        {
    
    
        case 'q':                                // Q 键退出程序
            sigprocmask(SIG_BLOCK, &sset, NULL); // 屏蔽SIGIO 信号
            goto err3;
        case ' ': // 空格暂停/恢复
            switch (snd_pcm_state(pcm))
            {
    
    
            case SND_PCM_STATE_PAUSED: // 如果是暂停状态则恢复运行
                ret = snd_pcm_pause(pcm, 0);
                if (0 > ret)
                    fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));
                break;
            case SND_PCM_STATE_RUNNING: // 如果是运行状态则暂停
                ret = snd_pcm_pause(pcm, 1);
                if (0 > ret)
                    fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));
                break;
            }
            break;
        }
    }
err3:
    snd_pcm_drop(pcm);                          // 停止PCM
    tcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); // 退出前恢复终端的状态
    free(buf);                                  // 释放内存
err2:
    snd_pcm_close(pcm); // 关闭pcm 设备
err1:
    close(fd); // 关闭打开的音频文件
    exit(EXIT_FAILURE);
}

The above sample program is modified on the basis of sample code 28.6.1, and a user control unit is added. Program settings:
⚫ q: Press the q key on the terminal to exit the application;
⚫ Press the space bar on the terminal to pause playback, and press again to resume playback.
Let me briefly introduce the design of the above example code. In the main() function, we first shield the SIGIO signal, as follows:

/* 屏蔽SIGIO 信号*/
sigemptyset(&sset);
sigaddset(&sset, SIGIO);
sigprocmask(SIG_BLOCK, &sset, NULL);

This is mainly for safety considerations in program design. After the ring buffer is filled with data, the SIGIO signal shielding will be cancelled. Of course, you don't have to do this.
Then open the audio file passed in by the user, initialize the PCM playback device, and apply for the buffer required by the application:

/* 打开WAV 音频文件*/
if (open_wav_file(argv[1]))
    exit(EXIT_FAILURE);
/* 初始化PCM Playback 设备*/
if (snd_pcm_init())
    goto err1;
/* 申请读缓冲区*/
buf = malloc(buf_bytes);
if (NULL == buf)
{
    
    
    perror("malloc error");
    goto err2;
}

Then set the terminal, configure the terminal to non-standard mode, and cancel the echo. After configuring the terminal to non-standard mode, the characters entered by the user will be directly read by the application without pressing the Enter key; cancel the echo, This means that the characters entered by the user will not be displayed on the terminal. These contents are introduced in detail in the serial port application programming chapter, so I won’t go into details here!

/* 终端配置*/
tcgetattr(STDIN_FILENO, &old_cfg); //获取终端<标准输入-标准输出构成了一套终端>
memcpy(&new_cfg, &old_cfg, sizeof(struct termios));//备份
new_cfg.c_lflag &= ~ICANON; //将终端设置为非规范模式
new_cfg.c_lflag &= ~ECHO; //禁用回显
tcsetattr(STDIN_FILENO, TCSANOW, &new_cfg);//使配置生效

Next, the data is written to the ring buffer and playback begins.
Cancel the SIGIO signal signal shielding.
Finally, enter the for() loop, read the characters entered by the user through getchar(), and exit the program when the user enters q. It should be noted here that when exiting the program, you need to call tcsetattr() to restore the terminal configuration parameters to the previous state, otherwise you The following situation may occur in the terminal:
Insert image description here
At this time, you can only restart.
The user enters a space to pause or resume, and calls snd_pcm_pause() to realize pause/resume.
The code is relatively simple, so I won’t say more about it!
Compile the sample code
Execute the command to compile the application:

${
    
    CC} -o testApp testApp.c -lasound

测试应用程序
将编译得到的可执行文件拷贝到开发板Linux 系统/home/root 目录下,并准备一个WAV 音频文件,接着我们执行测试程序:
Insert image description here
运行之后,开始播放音乐,此时我们可以通过空格键来暂停播放、再按空格键恢复播放,按q 键退出程序,大家自己去测试。
Tips:本测试程序不能放在后台运行,一旦放入后台,程序将停止(不是终止、是暂停运行),因为这个程序在设计逻辑上就不符合放置在后台,因为程序中会读取用户从终端(标准输入)输入的字符,如果放入后台,那用户输入的字符就不可能被该程序所读取到,这是其一;其二,程序中修改了终端的配置。

snd_pcm_readi/snd_pcm_writei 错误处理

When an error occurs when calling snd_pcm_readi/snd_pcm_writei, an error code less than 0 (negative value) will be returned. The snd_strerror()
function can be called to obtain the corresponding error description information. In the previous sample code, we did not handle the error return of snd_pcm_readi/snd_pcm_writei in too many details, but simply exited after the error.
In fact, when an error occurs when calling snd_pcm_redi/snd_pcm_writei, further processing can be performed according to different situations. It is
introduced in the alsa-lib document that the different error return values ​​​​of the snd_pcm_redi/snd_pcm_writei function represent different meanings, as shown below:
Insert image description here
The snd_pcm_readi() function is the same.
When the return value is equal to -EBADFD, it means that the state of the PCM device is wrong, because executing snd_pcm_readi/snd_pcm_writei to read/write data requires the PCM device to be in the SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING state. The state transition problem of the PCM device has been introduced in detail before.
When the return value is equal to -EPIPE, it means that XRUN has occurred. What can be done at this time? This can be handled according to your actual needs, such as calling snd_pcm_drop() to stop the PCM device, or calling snd_pcm_prepare() to restore the device to the ready state.
When the return value is equal to -ESTRPIPE, it means that the hardware has suspended. At this time, the PCM device is in the SND_PCM_STATE_SUSPENDED
state. For example, you can call the snd_pcm_resume() function to accurately recover from the suspension. If the hardware does not support it, you can also call
The snd_pcm_prepare() function puts the device into the preparation state, or performs other processing according to the application requirements.
The above introduces you to some situations when calling the snd_pcm_readi/snd_pcm_writei function and some measures that can be taken when errors occur!

Mixer settings

Earlier I introduced to you two sound card configuration tools provided by alsa-utils: alsamixer and amixer. These two tools are also written based on the alsa-lib library function. In this section, we will learn how to configure the sound card mixer by calling the alsa-lib library function in our own application program, such as volume adjustment.
The interface related to the mixer is introduced in the Mixer Interface module of alsa-lib. Click "Mixer Interface" in Figure 28.2.2 to view the introduction of the interface related to the mixer, as shown below: You can simply browse the
Insert image description here
module Those functions are provided below. Click on the function name to view a brief introduction to the function.

Open the mixer: snd_mixer_open

After using the mixer, you need to open the mixer and call the snd_mixer_open() function to open an empty mixer. The function prototype is as follows:
int snd_mixer_open(snd_mixer_t **mixerp, int mode);
alsa-lib uses snd_mixer_t The data structure describes the mixer. Calling the snd_mixer_open() function will instantiate an snd_mixer_t object and return the object pointer (that is, the handle of the mixer) through mixerp. The parameter mode specifies the opening mode, usually set to 0 to use the default mode!
Function call returns 0 successfully; failure returns an error code less than 0.
Example usage:

snd_mixer_t *mixer = NULL;
int ret;
ret = snd_mixer_open(&mixer, 0);
if (0 > ret)
	fprintf(stderr, "snd_mixer_open error: %s\n", snd_strerror(ret));

Attach associated device: snd_mixer_attach

Call the snd_mixer_open() function to open and instantiate an empty mixer. Next, we need to associate the sound card control device and call the
snd_mixer_attach() function to associate. The function prototype is as follows:

int snd_mixer_attach(snd_mixer_t *mixer, const char *name);

The parameter mixer corresponds to the handle of the mixer. The parameter name specifies the name of the sound card control device. Similarly, the logical device name is used here instead of the name of the device node. The naming method is "hw:i", where i represents the sound card. Card number, usually one sound card corresponds to one control device; for example, "hw:0" means the control device of sound card 0, which actually corresponds to the /dev/snd/controlC0 device. Just like the naming of PCM devices in the snd_pcm_open() function, there are other ways to name the sound card control devices in the snd_mixer_attach() function. We will ignore this issue for now.
Calling the snd_mixer_open() function will associate the control device specified by the parameter name with the mixer.
The function call returns 0 successfully; fails and returns an error code less than 0.
Example usage:

ret = snd_mixer_attach(mixer, "hw:0");
if (0 > ret)
	fprintf(stderr, "snd_mixer_attach error: %s\n", snd_strerror(ret));

Registration: snd_mixer_selem_register

Call the snd_mixer_selem_register() function to register the mixer. Its function prototype is as follows:

int snd_mixer_selem_register(
	snd_mixer_t *mixer,
	struct snd_mixer_selem_regopt *options,
	snd_mixer_class_t **classp);

Parameter options and parameter classp can be directly set to NULL.
Function call returns 0 successfully; failure returns an error code less than 0.
Example usage:

ret = snd_mixer_selem_register(mixer, NULL, NULL);
if (0 > ret)
fprintf(stderr, "snd_mixer_selem_register error: %s\n", snd_strerror(ret));

Loading: snd_mixer_load

Finally, the mixer needs to be loaded, and the snd_mixer_load() function is called to complete the loading. The function prototype is as follows:
int snd_mixer_load(snd_mixer_t * mixer);
The function call returns 0 successfully; an error code less than 0 is returned upon failure.
Example usage:

ret = snd_mixer_load(mixer);
if (0 > ret)
	fprintf(stderr, "snd_mixer_load error: %s\n", snd_strerror(ret));

Find element

After going through the above series of steps, you can use the mixer. In alsa-lib, the configuration items of the mixer are called elements. For example, headphone volume adjustment Headphone is an element, and 'Headphone Playback ZC' is An element, 'Right Output Mixer PCM'
is also an element.
snd_mixer_first_elem and snd_mixer_last_elem
alsa-lib uses the data structure snd_mixer_elem_t to describe an element, so a snd_mixer_elem_t object is an element. The mixer has many elements (that is, there are many configuration items). The first element of the mixer can be found through the snd_mixer_first_elem() function. Its function prototype is as follows:

snd_mixer_elem_t *snd_mixer_first_elem(snd_mixer_t *mixer);

The last element of the mixer can be found through the snd_mixer_last_elem() function, as follows:

snd_mixer_elem_t *snd_mixer_last_elem(snd_mixer_t *mixer);

snd_mixer_elem_next and snd_mixer_elem_prev
call the snd_mixer_elem_next() and snd_mixer_elem_prev() functions to obtain the next element and previous element of the specified element:

snd_mixer_elem_t *snd_mixer_elem_next(snd_mixer_elem_t *elem);
snd_mixer_elem_t *snd_mixer_elem_prev(snd_mixer_elem_t *elem);


So you can traverse all elements in the entire mixer through snd_mixer_first_elem and snd_mixer_elem_next() or snd_mixer_last_elem() and snd_mixer_elem_prev(), as shown below:

snd_mixer_elem_t *elem = NULL;

elem = snd_mixer_first_elem(mixer);//找到第一个元素
while (elem) {
    
    
	......
	......
	snd_mixer_elem_next(elem); //找到下一个元素
}

snd_mixer_selem_get_name
calls the snd_mixer_selem_get_name() function to get the name of the specified element, as shown below:

const char *snd_mixer_selem_get_name(snd_mixer_elem_t *elem);

After getting the name of the element, compare it to determine whether it is the element we are looking for:

const char *name = snd_mixer_selem_get_name(elem);
if (!strcmp(name, "Headphone")) {
    
    
//该配置项是"Headphone"
}
else {
    
    
//该配置项不是"Headphone"
}

Get/change the configuration value of an element

As mentioned earlier, there are two types of mixer configuration values. The first configuration value is a value within a range, such as volume adjustment; the other is a bool type, used to control the opening or closing of the mixer. Close, for example, 0 means to turn off the configuration, 1 means to enable the configuration.
snd_mixer_selem_has_playback_volume/snd_mixer_selem_has_capture_volume
We can call the snd_mixer_selem_has_playback_volume (playback) or snd_mixer_selem_has_capture_volume (recording) function to determine whether the configuration value of a specified element is of volume type, which is the first case mentioned above. The function prototype looks like this:

int snd_mixer_selem_has_playback_volume(snd_mixer_elem_t *elem);
int snd_mixer_selem_has_capture_volume(snd_mixer_elem_t *elem);

If the function returns 0, it means it is not a volume type; if it returns 1, it means it is a volume type.
snd_mixer_selem_has_playback_switch/snd_mixer_selem_has_capture_switch
calls the snd_mixer_selem_has_playback_switch (playback) snd_mixer_selem_has_capture_switch (recording) function to determine whether the configuration value of a specified element is of switch type, which is the second case mentioned above. The function prototype looks like this:

int snd_mixer_selem_has_playback_switch(snd_mixer_elem_t *elem);
int snd_mixer_selem_has_capture_switch(snd_mixer_elem_t *elem);

If the function returns 0, it means it is not a switch type; if it returns 1, it means it is a switch type.
snd_mixer_selem_has_playback_channel/snd_mixer_selem_has_capture_channel
uses the snd_mixer_selem_has_playback_channel (playback) or snd_mixer_selem_has_capture_channel (recording) function to determine whether the specified element contains the specified channel. The function prototype is as follows:

int snd_mixer_selem_has_playback_channel(
	snd_mixer_elem_t *elem,
	snd_mixer_selem_channel_id_t channel
);
int snd_mixer_selem_has_capture_channel(
	snd_mixer_elem_t *elem,
	snd_mixer_selem_channel_id_t channel
);

The parameter channel is used to specify a channel, snd_mixer_selem_channel_id_t is an enumeration type, as shown below:

enum snd_mixer_selem_channel_id_t
{
    
    
    SND_MIXER_SCHN_UNKNOWN = -1,
    SND_MIXER_SCHN_FRONT_LEFT = 0, // 左前
    SND_MIXER_SCHN_FRONT_RIGHT,    // 右前
    SND_MIXER_SCHN_REAR_LEFT,      // 左后
    SND_MIXER_SCHN_REAR_RIGHT,     // 右后
    SND_MIXER_SCHN_FRONT_CENTER,   // 前中
    SND_MIXER_SCHN_WOOFER,         // 低音喇叭
    SND_MIXER_SCHN_SIDE_LEFT,      // 左侧
    SND_MIXER_SCHN_SIDE_RIGHT,     // 右侧
    SND_MIXER_SCHN_REAR_CENTER,    // 后中
    SND_MIXER_SCHN_LAST = 31,
    SND_MIXER_SCHN_MONO = SND_MIXER_SCHN_FRONT_LEFT // 单声道
};

If the element is a two-channel element, it usually only contains the left front (SND_MIXER_SCHN_FRONT_LEFT) and right front (SND_MIXER_SCHN_FRONT_RIGHT) channels. If it is a mono device, it usually only contains
SND_MIXER_SCHN_MONO, and its value is equal to SND_MIXER_SCHN_FRONT_LEFT.
You can call the snd_mixer_selem_is_playback_mono (playback) or snd_mixer_selem_is_capture_mono (recording) function to determine whether a specified element is a mono element. The function prototype is as follows:

int snd_mixer_selem_is_playback_mono(snd_mixer_elem_t *elem);
int snd_mixer_selem_is_capture_mono(snd_mixer_elem_t *elem);

snd_mixer_selem_get_playback_volume/snd_mixer_selem_get_capture_volume
calls snd_mixer_selem_get_playback_volume (playback) or snd_mixer_selem_get_capture_volume (recording) to get the volume of the specified element. The function prototype is as follows:

int snd_mixer_selem_get_playback_volume(
	snd_mixer_elem_t *elem,
	snd_mixer_selem_channel_id_t channel,
	long *value
);
int snd_mixer_selem_get_capture_volume(
	snd_mixer_elem_t *elem,
	snd_mixer_selem_channel_id_t channel,
	long *value
);

The parameter elem specifies the corresponding element, and the parameter channel specifies a channel of the element. Call
the snd_mixer_selem_get_playback_volume() function to obtain the volume corresponding to the channel of the elem element, and return the obtained volume value through value.
The function call returns 0 if successful, and an error code less than 0 if failed.
For example, get the volume of the front left channel (playback):

long value;
snd_mixer_selem_get_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT, &value);

snd_mixer_selem_set_playback_volume/snd_mixer_selem_set_capture_volume
sets the volume value of the specified element. Its function prototype is as follows:

int snd_mixer_selem_set_playback_volume(
	snd_mixer_elem_t *elem,
	snd_mixer_selem_channel_id_t channel,
	long value
);
int snd_mixer_selem_set_capture_volume(
	snd_mixer_elem_t *elem,
	snd_mixer_selem_channel_id_t channel,
	long value
);

Call snd_mixer_selem_set_playback_volume (playback) or snd_mixer_selem_set_capture_volume (recording) to set the volume of a channel of the element. The parameter elem specifies the element, the parameter channel specifies a channel of the element, and the parameter value specifies the volume value.
Call snd_mixer_selem_set_playback_volume_all/snd_mixer_selem_set_capture_volume_all to set the volume of all channels of the specified element at once. The function prototype is as follows:

int snd_mixer_selem_set_playback_volume_all(
    snd_mixer_elem_t *elem,
    long value);
int snd_mixer_selem_set_capture_volume_all(
    snd_mixer_elem_t *elem,
    long value);

snd_mixer_selem_get_playback_volume_range/snd_mixer_selem_get_capture_volume_range
gets the volume range of the specified element. Its function prototype is as follows:

int snd_mixer_selem_get_playback_volume_range(
    snd_mixer_elem_t *elem,
    long *min,
    long *max);
int snd_mixer_selem_get_capture_volume_range(
    snd_mixer_elem_t *elem,
    long *min,
    long *max);

sample program

In this section, we will modify the sample code 28.8.1 and add volume control. The sample code is as follows:
The path corresponding to the source code of this routine is: Development board CD->11, Linux C application programming routine source code->28_alsa- lib->pcm_playback_mixer.c.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <signal.h>
#include <alsa/asoundlib.h>
/************************************
宏定义
************************************/
#define PCM_PLAYBACK_DEV "hw:0,0"
#define MIXER_DEV "hw:0"
/************************************
WAV 音频文件解析相关数据结构申明
************************************/
typedef struct WAV_RIFF
{
    
    
    char ChunkID[4];     /* "RIFF" */
    u_int32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数*/
    char Format[4];      /* "WAVE" */
} __attribute__((packed)) RIFF_t;
typedef struct WAV_FMT
{
    
    
    char Subchunk1ID[4];     /* "fmt " */
    u_int32_t Subchunk1Size; /* 16 for PCM */
    u_int16_t AudioFormat;   /* PCM = 1*/
    u_int16_t NumChannels;   /* Mono = 1, Stereo = 2, etc. */
    u_int32_t SampleRate;    /* 8000, 44100, etc. */
    u_int32_t ByteRate;      /* = SampleRate * NumChannels * BitsPerSample/8 */
    u_int16_t BlockAlign;    /* = NumChannels * BitsPerSample/8 */
    u_int16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__((packed)) FMT_t;
static FMT_t wav_fmt;
typedef struct WAV_DATA
{
    
    
    char Subchunk2ID[4];     /* "data" */
    u_int32_t Subchunk2Size; /* data size */
} __attribute__((packed)) DATA_t;
/************************************
static 静态全局变量定义
************************************/
static snd_pcm_t *pcm = NULL;                      // pcm 句柄
static snd_mixer_t *mixer = NULL;                  // 混音器句柄
static snd_mixer_elem_t *playback_vol_elem = NULL; // 播放<音量控制>元素
static unsigned int buf_bytes;                     // 应用程序缓冲区的大小(字节为单位)
static void *buf = NULL;                           // 指向应用程序缓冲区的指针
static int fd = -1;                                // 指向WAV 音频文件的文件描述符
static snd_pcm_uframes_t period_size = 1024;       // 周期大小(单位: 帧)
static unsigned int periods = 16;                  // 周期数(设备驱动层buffer 的大小)
static struct termios old_cfg;                     // 用于保存终端当前的配置参数
/************************************
static 静态函数
************************************/
static void snd_playback_async_callback(snd_async_handler_t *handler)
{
    
    
    snd_pcm_t *handle = snd_async_handler_get_pcm(handler); // 获取PCM 句柄
    snd_pcm_sframes_t avail;
    int ret;
    avail = snd_pcm_avail_update(handle); // 获取环形缓冲区中有多少帧数据需要填充
    while (avail >= period_size)
    {
    
                                     // 我们一次写入一个周期
        memset(buf, 0x00, buf_bytes); // buf 清零
        ret = read(fd, buf, buf_bytes);
        if (0 >= ret)
            goto out;
        ret = snd_pcm_writei(handle, buf, period_size);
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
            goto out;
        }
        else if (ret < period_size)
        {
    
     // 实际写入的帧数小于指定的帧数
            // 此时我们需要调整下音频文件的读位置
            // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes 个字节
            // frame_bytes 表示一帧的字节大小
            if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
            {
    
    
                perror("lseek error");
                goto out;
            }
        }
        avail = snd_pcm_avail_update(handle); // 再次获取、更新avail
    }
    return;
out:
    snd_pcm_drain(pcm);                         // 停止PCM
    snd_mixer_close(mixer);                     // 关闭混音器
    snd_pcm_close(handle);                      // 关闭pcm 设备
    tcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); // 退出前恢复终端的状态
    free(buf);
    close(fd);          // 关闭打开的音频文件
    exit(EXIT_FAILURE); // 退出程序
}
static int snd_pcm_init(void)
{
    
    
    snd_pcm_hw_params_t *hwparams = NULL;
    snd_async_handler_t *async_handler = NULL;
    int ret;
    /* 打开PCM 设备*/
    ret = snd_pcm_open(&pcm, PCM_PLAYBACK_DEV, SND_PCM_STREAM_PLAYBACK, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_open error: %s: %s\n",
                PCM_PLAYBACK_DEV, snd_strerror(ret));
        return -1;
    }
    /* 实例化hwparams 对象*/
    snd_pcm_hw_params_malloc(&hwparams);
    /* 获取PCM 设备当前硬件配置,对hwparams 进行初始化*/
    ret = snd_pcm_hw_params_any(pcm, hwparams);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /**************
    设置参数
    ***************/
    /* 设置访问类型: 交错模式*/
    ret = snd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_access error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置数据格式: 有符号16 位、小端模式*/
    ret = snd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16_LE);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_format error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置采样率*/
    ret = snd_pcm_hw_params_set_rate(pcm, hwparams, wav_fmt.SampleRate, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_rate error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置声道数: 双声道*/
    ret = snd_pcm_hw_params_set_channels(pcm, hwparams, wav_fmt.NumChannels);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_channels error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期大小: period_size */
    ret = snd_pcm_hw_params_set_period_size(pcm, hwparams, period_size, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_period_size error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 设置周期数(驱动层环形缓冲区buffer 的大小): periods */
    ret = snd_pcm_hw_params_set_periods(pcm, hwparams, periods, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params_set_periods error: %s\n", snd_strerror(ret));
        goto err2;
    }
    /* 使配置生效*/
    ret = snd_pcm_hw_params(pcm, hwparams);
    snd_pcm_hw_params_free(hwparams); // 释放hwparams 对象占用的内存
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_pcm_hw_params error: %s\n", snd_strerror(ret));
        goto err1;
    }
    buf_bytes = period_size * wav_fmt.BlockAlign; // 变量赋值,一个周期的字节大小
    /* 注册异步处理函数*/
    ret = snd_async_add_pcm_handler(&async_handler, pcm, snd_playback_async_callback, NULL);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_async_add_pcm_handler error: %s\n", snd_strerror(ret));
        goto err1;
    }
    return 0;
err2:
    snd_pcm_hw_params_free(hwparams); // 释放内存
err1:
    snd_pcm_close(pcm); // 关闭pcm 设备
    return -1;
}
static int snd_mixer_init(void)
{
    
    
    snd_mixer_elem_t *elem = NULL;
    const char *elem_name;
    long minvol, maxvol;
    int ret;
    /* 打开混音器*/
    ret = snd_mixer_open(&mixer, 0);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_mixer_open error: %s\n", snd_strerror(ret));
        return -1;
    }
    /* 关联一个声卡控制设备*/
    ret = snd_mixer_attach(mixer, MIXER_DEV);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_mixer_attach error: %s\n", snd_strerror(ret));
        goto err;
    }
    /* 注册混音器*/
    ret = snd_mixer_selem_register(mixer, NULL, NULL);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_mixer_selem_register error: %s\n", snd_strerror(ret));
        goto err;
    }
    /* 加载混音器*/
    ret = snd_mixer_load(mixer);
    if (0 > ret)
    {
    
    
        fprintf(stderr, "snd_mixer_load error: %s\n", snd_strerror(ret));
        goto err;
    }
    /* 遍历混音器中的元素*/
    elem = snd_mixer_first_elem(mixer); // 找到第一个元素
    while (elem)
    {
    
    
        elem_name = snd_mixer_selem_get_name(elem); // 获取元素的名称
        /* 针对开发板出厂系统:WM8960 声卡设备*/
        if (!strcmp("Speaker", elem_name) ||   // 耳机音量<对喇叭外音输出有效>
            !strcmp("Headphone", elem_name) || // 喇叭音量<对耳机输出有效>
            !strcmp("Playback", elem_name))
        {
    
     // 播放音量<总的音量控制,对喇叭和耳机输出都有效>
            if (snd_mixer_selem_has_playback_volume(elem))
            {
    
                                                                                        // 是否是音量控制元素
                snd_mixer_selem_get_playback_volume_range(elem, &minvol, &maxvol);               // 获取音量可设置范围
                snd_mixer_selem_set_playback_volume_all(elem, (maxvol - minvol) * 0.9 + minvol); // 全部设置为90%
                if (!strcmp("Playback", elem_name))
                    playback_vol_elem = elem;
            }
        }
        elem = snd_mixer_elem_next(elem);
    }
    return 0;
err:
    snd_mixer_close(mixer);
    return -1;
}
static int open_wav_file(const char *file)
{
    
    
    RIFF_t wav_riff;
    DATA_t wav_data;
    int ret;
    fd = open(file, O_RDONLY);
    if (0 > fd)
    {
    
    
        fprintf(stderr, "open error: %s: %s\n", file, strerror(errno));
        return -1;
    }
    /* 读取RIFF chunk */
    ret = read(fd, &wav_riff, sizeof(RIFF_t));
    if (sizeof(RIFF_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("RIFF", wav_riff.ChunkID, 4) || // 校验
        strncmp("WAVE", wav_riff.Format, 4))
    {
    
    
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 读取sub-chunk-fmt */
    ret = read(fd, &wav_fmt, sizeof(FMT_t));
    if (sizeof(FMT_t) != ret)
    {
    
    
        if (0 > ret)
            perror("read error");
        else
            fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    if (strncmp("fmt ", wav_fmt.Subchunk1ID, 4))
    {
    
     // 校验
        fprintf(stderr, "check error: %s\n", file);
        close(fd);
        return -1;
    }
    /* 打印音频文件的信息*/
    printf("<<<<音频文件格式信息>>>>\n\n");
    printf(" file name: %s\n", file);
    printf(" Subchunk1Size: %u\n", wav_fmt.Subchunk1Size);
    printf(" AudioFormat: %u\n", wav_fmt.AudioFormat);
    printf(" NumChannels: %u\n", wav_fmt.NumChannels);
    printf(" SampleRate: %u\n", wav_fmt.SampleRate);
    printf(" ByteRate: %u\n", wav_fmt.ByteRate);
    printf(" BlockAlign: %u\n", wav_fmt.BlockAlign);
    printf(" BitsPerSample: %u\n\n", wav_fmt.BitsPerSample);
    /* sub-chunk-data */
    if (0 > lseek(fd, sizeof(RIFF_t) + 8 + wav_fmt.Subchunk1Size,
                  SEEK_SET))
    {
    
    
        perror("lseek error");
        close(fd);
        return -1;
    }
    while (sizeof(DATA_t) == read(fd, &wav_data, sizeof(DATA_t)))
    {
    
    
        /* 找到sub-chunk-data */
        if (!strncmp("data", wav_data.Subchunk2ID, 4)) // 校验
            return 0;
        if (0 > lseek(fd, wav_data.Subchunk2Size, SEEK_CUR))
        {
    
    
            perror("lseek error");
            close(fd);
            return -1;
        }
    }
    fprintf(stderr, "check error: %s\n", file);
    return -1;
}
static void show_help(void)
{
    
    
    printf("<<<<<<<基于alsa-lib 音乐播放器>>>>>>>>>\n\n"
           "操作菜单:\n"
           " q 退出程序\n"
           " space<空格> 暂停播放/恢复播放\n"
           " w 音量增加++\n"
           " s 音量减小--\n\n");
}
/************************************
main 主函数
************************************/
int main(int argc, char *argv[])
{
    
    
    snd_pcm_sframes_t avail;
    struct termios new_cfg;
    sigset_t sset;
    int ret;
    if (2 != argc)
    {
    
    
        fprintf(stderr, "Usage: %s <audio_file>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    /* 屏蔽SIGIO 信号*/
    sigemptyset(&sset);
    sigaddset(&sset, SIGIO);
    sigprocmask(SIG_BLOCK, &sset, NULL);
    /* 打开WAV 音频文件*/
    if (open_wav_file(argv[1]))
        exit(EXIT_FAILURE);
    /* 初始化PCM Playback 设备*/
    if (snd_pcm_init())
        goto err1;
    /* 初始化混音器*/
    if (snd_mixer_init())
        goto err2;
    /* 申请读缓冲区*/
    buf = malloc(buf_bytes);
    if (NULL == buf)
    {
    
    
        perror("malloc error");
        goto err3;
    }
    /* 终端配置*/
    tcgetattr(STDIN_FILENO, &old_cfg);                  // 获取终端<标准输入-标准输出构成了一套终端>
    memcpy(&new_cfg, &old_cfg, sizeof(struct termios)); // 备份
    new_cfg.c_lflag &= ~ICANON;                         // 将终端设置为非规范模式
    new_cfg.c_lflag &= ~ECHO;                           // 禁用回显
    tcsetattr(STDIN_FILENO, TCSANOW, &new_cfg);         // 使配置生效
    /* 播放:先将环形缓冲区填满数据*/
    avail = snd_pcm_avail_update(pcm); // 获取环形缓冲区中有多少帧数据需要填充
    while (avail >= period_size)
    {
    
                                     // 我们一次写入一个周期
        memset(buf, 0x00, buf_bytes); // buf 清零
        ret = read(fd, buf, buf_bytes);
        if (0 >= ret)
            goto err4;
        ret = snd_pcm_writei(pcm, buf, period_size); // 向环形缓冲区中写入数据
        if (0 > ret)
        {
    
    
            fprintf(stderr, "snd_pcm_writei error: %s\n", snd_strerror(ret));
            goto err4;
        }
        else if (ret < period_size)
        {
    
     // 实际写入的帧数小于指定的帧数
            // 此时我们需要调整下音频文件的读位置
            // 将读位置向后移动(往回移)(period_size-ret)*frame_bytes 个字节
            // frame_bytes 表示一帧的字节大小
            if (0 > lseek(fd, (ret - period_size) * wav_fmt.BlockAlign, SEEK_CUR))
            {
    
    
                perror("lseek error");
                goto err4;
            }
        }
        avail = snd_pcm_avail_update(pcm); // 再次获取、更新avail
    }
    sigprocmask(SIG_UNBLOCK, &sset, NULL); // 取消SIGIO 信号屏蔽
    /* 显示帮助信息*/
    show_help();
    /* 等待获取用户输入*/
    char ch;
    long vol;
    for (;;)
    {
    
    
        ch = getchar(); // 获取用户输入的控制字符
        switch (ch)
        {
    
    
        case 'q':                                // Q 键退出程序
            sigprocmask(SIG_BLOCK, &sset, NULL); // 屏蔽SIGIO 信号
            goto err4;
        case ' ': // 空格暂停/恢复
            switch (snd_pcm_state(pcm))
            {
    
    
            case SND_PCM_STATE_PAUSED: // 如果是暂停状态则恢复运行
                ret = snd_pcm_pause(pcm, 0);
                if (0 > ret)
                    fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));
                break;
            case SND_PCM_STATE_RUNNING: // 如果是运行状态则暂停
                ret = snd_pcm_pause(pcm, 1);
                if (0 > ret)
                    fprintf(stderr, "snd_pcm_pause error: %s\n", snd_strerror(ret));
                break;
            }
            break;
        case 'w': // 音量增加
            if (playback_vol_elem)
            {
    
    
                // 获取音量
                snd_mixer_selem_get_playback_volume(playback_vol_elem,
                                                    SND_MIXER_SCHN_FRONT_LEFT, &vol);
                vol++;
                // 设置音量
                snd_mixer_selem_set_playback_volume_all(playback_vol_elem, vol);
            }
            break;
        case 's': // 音量降低
            if (playback_vol_elem)
            {
    
    
                // 获取音量
                snd_mixer_selem_get_playback_volume(playback_vol_elem,
                                                    SND_MIXER_SCHN_FRONT_LEFT, &vol);
                vol--;
                // 设置音量
                snd_mixer_selem_set_playback_volume_all(playback_vol_elem, vol);
            }
            break;
        }
    }
err4:
    snd_pcm_drop(pcm);                          // 停止PCM
    tcsetattr(STDIN_FILENO, TCSANOW, &old_cfg); // 退出前恢复终端的状态
    free(buf);                                  // 释放内存
err3:
    snd_mixer_close(mixer); // 关闭混音器
err2:
    snd_pcm_close(pcm); // 关闭pcm 设备
err1:
    close(fd); // 关闭打开的音频文件
    exit(EXIT_FAILURE);
}

The main() function calls the custom function snd_mixer_init() to initialize the sound card mixer. What is done in the snd_mixer_init() function is the process introduced to you above: first open an empty mixer, Attach associates a sound card control device, registers the mixer, and loads the mixer. After the entire set of operations is completed, you can use the mixer; search for elements in the mixer and configure the elements.
In the snd_mixer_init() function, we configured the "Speaker" element (speaker output volume), "Headphone" element (headphone output volume) and "Playback" element (playback volume) of the WM8960 sound card, setting them all to 90 %; Then assign the handle of the "Playback" element to the global static variable playback_vol_elem.
Back to the main() function, in the for loop, get the control characters input by the user. Here we add w and s. When the user presses the w
key, the volume increases, and when the s key is pressed, the volume decreases. The volume controlled here It is the "Playback" volume (playback volume) of WM8960.
Compile the application
Compile the above example code:

${
    
    CC} -o testApp testApp.c -lasound

Insert image description here
Test the application.
Copy the compiled executable file to the /home/root directory of the development board's Linux system, prepare an audio file in WAV format, and execute the test program:

./testApp ./EXO-Overdose.wav

Insert image description here
After the command is executed, the operation method will be displayed. You can test it yourself according to the prompts!

Loopback test routine

alsa-utils provides a tool alsaloop for loopback testing, which can record and play at the same time. The usage of this program is relatively simple. You can view the usage help information of the alsaloop test program by executing "alsaloop --help", as shown below: For
Insert image description here
example Run "alsaloop -t 1000" directly to test, you can test it yourself.
In principle, the loopback test is very simple. Record the audio and then play it back. However, this is not the case in fact. Many factors need to be taken into consideration, because for recording and playback, one cycle of recording and one cycle of playback are processed by the hardware. The time spent in one cycle is not the same. One is the ADC process and the other is the DAC process, so XRUN is often easy to occur. Therefore, how to design your application effectively and reasonably will become very important to minimize XRUN. happened.
The author has tested the alsaloop tool. Although XRUN will also appear, it is relatively rare. If you are interested in this, you can refer to the source code of the alsaloop
program and directly download the alsa-util source code package, which can be found in the alsa-util source code package. The source code of the alsaloop program is as follows:
Insert image description here
In addition to the source code of alsaloop, the source code of tools such as aplay, alsamixer, amixer, alsactl and so on introduced earlier are all here. Interested readers can take a look.

Summarize

In this chapter, we learned audio application programming under Linux. The application is based on the alsa-lib library to implement playback, recording and other functions. This chapter does not do too much in-depth study. It just introduces some basic functions in the alsa-lib library. Most of the API interfaces have not been introduced to you. If you are interested, you can study and learn in depth by yourself!
In this section we will talk about the ALSA plug-in.

ALSA plug-in

ALSA provides some PCM plug-ins to extend the functions and features of PCM devices. The plug-ins are responsible for various sample conversions, sample copying between channels, etc.
When calling the snd_pcm_open() function, you need to fill in the PCM device name. The alsa-lib library uses the logical device name instead of the device node name. In the sample program written earlier, we used the format of "hw:i,j", which actually specifies a plug-in named hw, and the two numbers i and j after the colon represent two parameters, That is, the two parameters passed in when using this plug-in (the first parameter represents the sound card number, and the second parameter represents the device number).
There is a file named alsa.conf in the /usr/share/alsa/ directory of the development board Linux system, as shown below: This
Insert image description here
file is the configuration file of the alsa-lib library, and /usr/ share/alsa/alsa.conf file and parse it, as can be seen from the figure above, the /usr/share/alsa/alsa.conf file will load and parse the two configuration files /etc/asound.conf and ~/.asoundrc, In the factory system of our development board, there is the /etc/asound.conf configuration file, but there is no ~/.asoundrc file.
The /usr/share/alsa/alsa.conf configuration file is used as the main entry point of the alsa-lib library function, which configures alsa-lib and defines some basic and general PCM plug-ins; and the .asoundrc and asound.conf files Introduced to provide user customization requirements, users can define plug-ins according to their own needs in these two files.
For the definition of plug-ins and related explanations, you can refer to the following two documents provided by ALSA:
https://www.alsa-project.org/main/index.php/Asoundrc
https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html
For example, there are many PCM plug-ins defined in the /etc/asound.conf file of the development board factory system, as shown below:
Insert image description here
in the picture above Each pcm.name { } defines a plug-in. name represents the name of the plug-in, such as dmix_48000, dmix_44100,
dmix_32000, etc.; and the pcm before the dot indicates that name is a PCM plug-in, used for PCM equipment; the brackets { } The content is the attribute definition of the plug-in.
In square brackets { }, the type field specifies the type of plug-in. alsa-lib supports many different plug-in types, such as hw, plughw,
mmap_emul, shm, linear, plug, multi, share, dmix, dsnoop, softvol, etc. Different types of plug-ins support different functions and features, which are briefly introduced below.
hw plug-in
This plug-in communicates directly with the ALSA kernel driver, which is raw communication without any translation. The application calls the alsa-lib library function to directly operate the underlying audio hardware settings, such as the configuration of the PCM device, which directly affects the hardware.
plughw plug-in
This plug-in can provide software features such as sample rate conversion, which are not supported by the hardware itself. For example, the audio file played by the application has a sampling rate of 48000, but the underlying audio hardware itself does not support this sampling rate, so calling the
snd_pcm_hw_params_set_rate() function to set the sampling rate of the PCM device to 48000 will cause an error!
At this time, you can use the plughw plug-in, which supports software features such as sample rate conversion.
dmix plug-in
This supports mixing, mixing audio data from multiple applications.
softvol plugin
supports software volume.
For a more detailed introduction to these plug-ins, please view the documentation provided by ALSA.

Guess you like

Origin blog.csdn.net/zhuguanlin121/article/details/132588739