[转载] 小白快速入门 Linux alsa 应用编程
我在 Linux阅码场 微信公众号发表的文章,由于版权原因,通过转载分享到博客,原文链接为:
《小白快速入门 Linux alsa 应用编程》:https://mp.weixin.qq.com/s/5TSTHWyZ8Ihul8Xv-4UclQ
1. 背景
网上大多数是 alsa 底层框架、音频驱动的文章,应用开发的入门少得可怜。从业务需求出发,摸索积累了一些 alsa 应用开发心得。出此文以便后来者快速入门。
本文不会涉及底层框架,也不会使用很高级的特性,适合需要做 alsa 应用开发的初学者。毕竟是半路出家,与沉浸多年对 alsa 框架了如指掌的大牛没得比。如果有理解不准确的地方,希望指导共同进步。
- Alsa 主页:https://www.alsa-project.org/wiki/Main_Page
- Alsa 文档主页:https://www.alsa-project.org/alsa-doc/alsa-lib/index.html
- Alsa PCM 接口说明:https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html
- Alsa HCTL 接口说明:https://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html
- Alsa CTL 接口说明:https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html
在学习过程中,有两篇前人的分享给了我很多帮助,一并贴上:
- 《学习Linux操作系统中Alsa音频编程》:https://www.elecfans.com/emb/20190402899220.html
- 《【转】Alsa音频编程【精华】》:https://www.cnblogs.com/cslunatic/p/3677729.html
2. 基本概念
一些基本的概念还是要有理解的,不然无法理解 API 意义和参数作用。
我们知道声音是靠震动传播的,自然界传播的声音都是连续的模拟信号,经过采样转换成数字信号。其实有不少概念都是在描述怎么采样。
2.1 样本长度 Format
上图中,每一个黑色小球就是采样出来的数值,这个数值是多少比特位的,就是我们说的采样精度,也就是样本长度,常见的有 8 bit 和 16 bit,偶尔也有 32 bit。例如采样值是 3,如果是 8 bit 位采样结果就是 0x03,16 bit位采样结果就是 0x0003。
2.2 通道数 Channels
我们常说的左声道、右声道,可以理解为左右各来一个 mic 采样,左 mic 采样出来的样本就是左声道数据,右边 mic 采样出来的样本就是右声道数据。而一个音频既可以只有1个声道,也可以有左右两个声道,后者也称为立体声。而这个音频究竟有几个声道,就是我们说的通道数。
2.3 帧 Frame
我们每一次采样出来的结果,就是一帧。很明显,一帧数据有多大,取决于我们采样的精度以及通道数。
2.4 交错模式 Interleaved
我们每一次采样出的音频帧,怎么保存呢?提供了两种保存思路,也就是我们说的交错模式和非交错模式。我们常用的也是交错模式。
2.5 周期 Period
我们总不可能一次处理1帧数据吧,太低效了,那就做成批量处理吧。而一次处理多少帧就是我们说的周期。
一次周期结束切到下一次周期,都是需要额外处理损耗的,就类似于进程切换。周期大,一次处理数据量就多,每次连续处理时间长,切换损耗就少,但也因为数据要满一个周期后才处理,导致数据处理延时长。反之,如果周期设置的小,延时短了,但周期切换更频繁,损耗就更大,更容易出现卡顿。
2.6 缓存大小 Buffer Size
这里说的是 alsa 底层 DMA 搬运数据的缓存大小,这是一个环形的缓存空间。我们设置 DMA 一次连续搬运 1 个周期的数据,搬运期间如果又来数据怎么办?我们就需要更大的缓存空间来保存更多的数据。缓存空间往往是周期的整数倍,例如设置了缓存 8 个周期,每个周期 6000 帧,那么最多可以缓存 8 * 6000 = 48000 帧的数据。
2.7 采样率 Rate
不同于周期是人为定义的一次处理多少帧,采样率就是固定的 1s 时间内会有多少次采样,同时也表示 1s 播放需要多少帧。常用的采样率如 8KHz 的人声, 44.1KHz 的 mp3 音乐, 96Khz 的蓝光音频。
假设一个周期是 6000 帧,采样率是 48 Khz,那么一个周期的数据能播放 125 ms。
2.8 Xrun
录音的应用中,底层是持续不断采样的,如果应用程序读取数据不够快,底层数据缓存区还没被取走就被新的数据覆盖,导致数据丢失,称为 over run。
播放的应用中,底层是持续不断从缓存中获取数据播放的,如果应用程序写入数据慢了,缓存区已经没有有效数据了,导致播放“饿死”,称为 under run。
Xrun 是 under run 和 over run 的统称,前者可以理解为播放卡顿,后者则是录音卡顿。
当出现卡顿的时,大多情况调整周期、缓存大小,调整应用进程调度优先级能解决问题。
3. 框架初探与声卡设备
以下是网上优秀的文章:
《ALSA架构简介》:http://t.zoukankan.com/-glb-p-13722212.html
Alsa 的架构包括用户空间的 Alsa Library,也包括内核空间的 Alsa Core 和 ASoC Core,如下图所示:
- APP:应用程序通过调用 alsa 库 API 来实现声卡播放、录音、控制。此外,官方还提供了一些标准命令行程序,例如aplay/amixer。
- Alsa-Library:alsa 库封装了底层复杂的系统调用,向上提供更直观的 API。常见的 alsa 库有 alsa-lib 和 tinyalsa。
- Alsa Core:Alsa 的核心层在内核,向上提供逻辑设备、系统调用,向下驱动硬件设备。
- ASoC Core:asoc是建立在标准 alsa core 上为更好支持嵌入式系统和移动设备音频 codec 设计的软件体系
Alsa 用户空间的 API 库主要通过 open/read/write/ioctl
操作 /dev/snd/xxx
下的设备文件实现与内核交互,常见的设备文件有:
文件名 | 用途 |
---|---|
controlC0 | 第0号声卡的控制设备,例如音量、混音等 |
pcmC0D0c | 第0号声卡第0个设备,用于录音(Capture)的设备 |
pcmC0D0p | 第0号声卡第0个设备,用于播放(Playback)的设备 |
seq | 音序器 |
timer | 定时器 |
命名规则显而易见,pcm
表示设备类型,C0
表示声卡0,D0
表示设备0,c/p
分别表示录音、播放功能。
4. 系统配置与插件
最完整的介绍还是来自于官网原文:
《Asoundrc》配置文件:https://www.alsa-project.org/main/index.php/Asoundrc
《PCM (digital audio) plugins》:https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html
alsa-lib 会从 /usr/share/alsa/alsa.conf
开始加载,进而根据 alsa.conf
的记录加载 /etc/asond.conf
和 ~/.asoundrc
。前者是系统级别的配置,后者是用户级别的配置,两者的语法是一致的。
我们从配置默认声卡开始,以下是一个标准示例:
pcm.!default {
type hw
card 0
}
ctl.!default {
type hw
card 0
}
一般情况下,我们配置的格式 pcm.<card name> {...}
,而关键字 !default
可以理解为保留字。pcm.!default
配置的是默认录播声卡,ctl.!default
配置的是默认控制声卡。如此配置后,我们可以用 "default" 做声卡名指代 "hw:0,0",所以下两个命令就是等效的了。
aplay -D hw:0,0 test.wav
aplay -D default test.wav
此时 "default" 声卡不管是播放还是录音,都指向 "hw:0,0" 设备。如果我希望 "default" 录音和播放指向不同(虚拟)声卡,我们可以用 asym 插件,例如:
pcm.!default {
type asym
playback.pcm "Playback"
capture.pcm "Capture"
}
pcm.Playback {
...
}
pcm.Capture {
...
}
当然,我们可以跳过 "default" 直接用 "Playback" 的虚拟声卡播放,例如
aplay -D Playback test.wav
Alsa 配置的节点是一个个声卡节点串联起来的,例如下面的配置,实现了从插件 A 开始串上插件 B ,用插件实现各种音效功能,最后到物理声卡播放。
pcm.A {
type XXX
# 下一个节点是声卡 B
slave.pcm B
...
}
pcm.B {
type XXX
# 下一个节点是物理声卡
salve.pcm "hw:0,0"
}
type
字段就标识此节点使用什么插件。我们可以自己实现插件,也可以用官网提供的插件,详见官网 《PCM (digital audio) plugins》。其中有几个非常有意思的插件,这里简单介绍下。
插件类型 | 功能 |
---|---|
Plugin: asym | 实现录音、播放分流到不同声卡 |
Plugin: plug | 实现通道数、采样率、样本长度的自动转换,也就是重采样 |
Plugin: dmix | 实现播放混音,让声卡支持同时被多次打开播放,经过此插件混音 |
Plugin: dsnoop | 实现多路同时获取录音数据(默认A打开声卡后B无法打开) |
Plugin: Soft Volume | 实现音量调节 |
例如 "default" 声卡支持分流,播放时支持音量调节,且支持混音。
pcm.!default {
type asym
playback.pcm "Playback"
capture.pcm "..."
}
pcm.Playback {
type softvol
slave.pcm PlaybackDmix
...
}
pcm.PlaybackDmix {
type plug # dmix 再套一层plug,实现重采样
slave.pcm {
type dmix
...
slave {
pcm "hw:0,0"
format S16_LE
...
}
}
}
5. 基本录播
官网上有个最最最精简的示例 pcm_mini,其作用仅仅是播放一段随机数据。
精炼核心逻辑如下:
int main(void)
{
...
/* 打开alsa设备,类型为 PlayBack */
if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
...
}
/* 设置alsa设备基本属性 */
if ((err = snd_pcm_set_params(handle,
SND_PCM_FORMAT_U8,
SND_PCM_ACCESS_RW_INTERLEAVED,
1,
48000,
1,
500000)) < 0) {
...
}
/* 播放音频数据 */
frames = snd_pcm_writei(handle, buffer, sizeof(buffer));
/* 关闭声卡设备 */
snd_pcm_close(handle);
return 0;
}
录音跟播放非常相似,播放调用 snd_pcm_writei()
,录音则调用 snd_pcm_readi()
。
总的来说,一个简单的录播包括以下 4 个步骤:
- 打开声卡设备
- 初始化设备
- 录音、播放
- 关闭声卡设备
围绕这 4 个步骤,介绍下常用的 API,更多的介绍请看官网:pcm api
5.1 打开声卡设备
int snd_pcm_open(
snd_pcm_t **pcmp,
const char *name,
snd_pcm_stream_t stream,
int mode
)
- pcmp:声卡设备句柄,类似于文件句柄
- name:声卡设备名,类似于文件名,声卡设备名参考第3章节
- stream:数据流向,指定用于录音还是播放
- mode:打开模式,例如nonBlock,async等,大多数情况用 0 即可
数据流向有两种,分别指代录音 or 播放。
enum | 流向 |
---|---|
SND_PCM_STREAM_PLAYBACK | (Playback)播放流 |
SND_PCM_STREAM_CAPTURE | (Capture)录音流 |
一个简单的示例如下,从 default 设备录音:
#include <alsa/asoundlib.h>
snd_pcm_t *snd_handle;
err = snd_pcm_open(&snd_handle, "default", SND_PCM_STREAM_CAPTURE, 0)
5.2 初始化设备
alsa 设置声卡参数的接口非常多,可以分为软件参数(software parameters)和硬件参数(software parameters)两类。上文例子调用的snd_pcm_set_params()
如官网API文档所说,只是简单设置软件、硬件参数的方法,实际项目中很少这么用。
5.2.1 设置硬件参数
官网有非常多的例子,以 pcm 为例,设置硬件参数常见以下步骤:
int set_hwparams(...)
{
/* 从栈里分配硬件参数对象内存 */
snd_pcm_hw_params_alloca(¶ms);
/* 初始化参数对象 */
err = snd_pcm_hw_params_any(handle, params);
/* 设置采样率 */
err = snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);
/* 设置交错模式 */
err = snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
/* 设置采样格式,例如样本长度、有无符号 */
err = snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
/* 设置通道数 */
err = snd_pcm_hw_params_set_channels(handle, params, 2);
/* 设置缓存大小 */
err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, &dir);
/* 设置周期大小 */
err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, &dir);
/* 把上述设置值写入设备 */
err = snd_pcm_hw_params(handle, params);
}
不妨回顾章节2的基本概念,可以发现硬件参数基本都是围绕这些概念设置的。不管是交错模式、采样率,还是样本长度、通道数都比较直观,如果设置错大概率无法正常运行。而缓存大小和周期大小往往跟卡顿、延时有关,且有好几种设置维度,值得展开介绍。
调整卡顿、延时参数时,我们需要记住一个核心的关系:
缓存大小 = 周期大小 * 周期数,即 buffer size = period size * periods
3 个参数知其2 就可以换算出另外一个参数。例如我们可以设置 周期大小 和 周期数,alsa 会自动换算出 缓存大小。同理,我们可以设置缓存大小和周期大小,alsa 也能自动换算出周期数。在上述的例子中,就是设置了缓存大小和周期大小。
在一定的采样率下,缓存大小也可以换算成时间,毕竟有些人需要从时间维度设置缓存。
播放时间 = 缓存大小 / 采样率
因此我们可以发现,除了 set_buffer/period_size()
之外,我们还可以 set_buffer/period_time()
,他们是等效的。
更多的 pcm 硬件参数设置API,可以看官网 hw参数API文档,这里再补充一点。
同样是设置周期大小,我们可以用 snd_pcm_hw_params_set_period_size()
,但更多会选择用 snd_pcm_hw_params_set_period_size_near()
。这里的 near
后缀表示就近设置,因为不管是缓存大小还是周期大小、周期数,有时候会受其他配置制约,这时候就采用可用的接近的值。例如声卡设备在系统配置中限制了周期大小不超过 1024 帧,此时如果设置 6000 帧,就会就近复位为1024。
5.2.2 设置软件参数
同样在 pcm 的例子中提取软件参数设置步骤:
int set_swparams(...)
{
/* 从栈里分配软件参数对象内存 */
snd_pcm_sw_params_alloca(¶ms);
/* 获取当前的软件参数配置以初始化对象 */
err = snd_pcm_sw_params_current(handle, swparams);
/* 设置起播阈值 */
err = snd_pcm_sw_params_set_start_threshold(...);
/* 设置最小可用 */
err = snd_pcm_sw_params_set_avail_min(...);
/* 把上述设置值写入设备 */
err = snd_pcm_sw_params(handle, swparams);
}
软件参数设置我用的也不多,更多时候干脆不设置软件参数采用默认值。为了不误人子弟,每个参数的具体作用不展开介绍。以下是相关的文档链接,请读者辨证分析。
https://blog.csdn.net/weixin_39560924/article/details/110569666
https://blog.csdn.net/zz2862625432/article/details/101787316
https://www.cnblogs.com/cslunatic/p/3677729.html
5.3 录音与播放
本文只讲常用的读写,不展开 mmap 等方法。
交错模式和非交错模式用的读(录音)和写(播放)接口不一样。
# 交错模式
snd_pcm_sframes_t snd_pcm_readi (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)
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_readn (snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
snd_pcm_sframes_t snd_pcm_writen (snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size)
- pcm:声卡设备句柄
- buffer:读/写的buffer指针
- size:指代buffer大小,单位帧
一个简单的转播(读->写)示例如下:
frames = 6000; # 假设一个周期 6000 帧
buffer = malloc( frames * 4 ); # 双通道16Bit采样精度,因此一帧 4B
while (true) {
ret = snd_pcm_readi(chandle, buffer, frames);
snd_pcm_writei(phandle, buffer, ret);
}
5.4 关闭声卡设备
int snd_pcm_close(snd_pcm_t *pcm)
关闭声卡设备的接口非常简单,类似于关闭文件句柄。
6. 设置音量
录播用 pcm APIs,而设置音量需要使用 ctrl APIs。Alsa 有两类控制接口,一个是 ctrl,一个则是更高层抽象的 hctrl,以下是官网两个类型接口文档:
Alsa HCTL 接口说明:https://www.alsa-project.org/alsa-doc/alsa-lib/group___h_control.html
Alsa CTL 接口说明:https://www.alsa-project.org/alsa-doc/alsa-lib/group___control.html
以及下面链接是官网关于控制的一些概念介绍:
Control interface 概述:https://www.alsa-project.org/alsa-doc/alsa-lib/control.html
下文会先介绍关于 ctrl 的基本概念,再参考 Alsa Utils 的 amixer 命令学习如何获取、设置音量。
6.1 控制基本概念
不管是实体声卡还是虚拟声卡,每一个声卡都会提供一个控制方法,我们需要设置声卡属性,就必须要先打开丢应的声卡控制。打开声卡设置需要使用 snd_ctl_open()
,并用以下几种方法可以指定声卡:
- 使用声卡编号,例如: hw:1
- 使用声卡名,例如: hw:sndtasXXXX 或者 hw:CARD=sndtasXXXX
- 使用设备文件,例如:hw:/dev/snd/controlC0
每个声卡可以有很多控制项,在 Alsa 里叫做 要素(Elements)。要素可以有多个成员(Member),例如立体声有左右声道音量两个成员。一个要素的所有成员共享一样的属性,例如最大、最小音量。此外,要素数据也区分类型,例如音量是整型。以下是所有支持的要素类型:
elem | 含义 |
---|---|
SND_CTL_ELEM_TYPE_NONE |
Invalid type |
SND_CTL_ELEM_TYPE_BOOLEAN |
Boolean contents |
SND_CTL_ELEM_TYPE_INTEGER |
Integer contents |
SND_CTL_ELEM_TYPE_ENUMERATED |
Enumerated contents |
SND_CTL_ELEM_TYPE_BYTES |
Bytes contents |
SND_CTL_ELEM_TYPE_IEC958 |
IEC958 (S/PDIF) setting content |
SND_CTL_ELEM_TYPE_INTEGER64 |
64-bit integer contents |
既然一个声卡可以有很多要素(控制项),我们设置要素需要先定位哪个要素吧。要定位每个要素,可以有以下方法:
- 使用numid:当声卡被检测到的时候就会赋予一个编号,但每次开机可能都不一样。使用此编号主要是减少根据属性遍历时间。
- 使用固定属性:固定属性包括方法类型(interface type)、设备(device)、子设备(subname)、名字(name)或者编号(index)。可以一次指定多个属性以便准确定位要素。
6.2 获取设置音量
Alsa Libs 关于设置音量的示例不多,这时候我们不妨看看 Alsa Utils 里 amixer 命令的实现,毕竟其我们通过命令行设置音量往往是通过 amixer 命令,例如:
amixer -D default cset name='Master Volume' 60 # 设置默认声卡音量为60(要素名为:Master Volume)
6.2.1 关键数据类型
在了解相关代码实现前,需要先了解几个很重要的数据类型。
snd_ctl_elem_id_t
:记录了定位要素的属性,例如设备、numid等snd_ctl_elem_value_t
:存储了要素值,需要根据不同类型用不同接口获取具体值snd_ctl_elem_info_t
:要素的信息
对 hctl API,还有 snd_hctl_elem_t
描述具体的要素对象。
- id 唯一标识了要素,在定位要素时可以赋值部分已知属性到 id,用于遍历要素。
- 通过绑定 id 后获取要素的 info,info包含了要素的所有属性,例如类型、完整要素 id 信息。
- 通过绑定 id 后读取要素的 value,最后根据类型调用对应接口从 value 中获取具体值。
6.2.2 代码实现
以下是精简后设置音量的实现(为了方便理解跟源代码 amixer 的调用API不完全相同):
int cset(...)
{
/* 从栈申请 info/id/value 对象空间 */
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
/* 根据命令行指定的要素信息来初始化 id (id 记录了定位要素的信息) */
snd_ctl_ascii_elem_id_parse(id, "name='Master Volume'");
/* 打开默认声卡,获取控制句柄 */
snd_ctl_open(&handle, "default", 0);
/* 绑定要素 id 到 info 对象,此处仅仅是把 id 赋值到 info 的成员 */
snd_ctl_elem_info_set_id(info, id);
/* 从声卡中,依据绑定的 id,获取要素完整的 info 信息 */
snd_ctl_elem_info(handle, info);
/* 读取当前音量 */
/* 绑定要素 id 到 value 对象,此处仅仅是把 id 复制到 value 的成员 */
snd_ctl_elem_value_set_id(control, id);
/* 从声卡中,依据绑定的 id,获取要素完整的 value 信息 */
snd_ctl_elem_read(handle, control);
/* 从 value 对象中,获取通道 idx 的整型音量值 */
vol = snd_ctl_elem_value_get_integer(control, idx);
/* 设置新音量 */
/*
* info 里记录了要素类型、成员数量等属性,此接口根据要素属性,
* 解析命令行设置字符串的值,获取新的的 value 信息。
* 此方法用于命令行字符串解析,如果是自己编程实现,应该用 snd_ctl_elem_value_set_xxxx()。
*/
snd_ctl_ascii_value_parse(handle, control, info, "60");
/* 把最终的 value 设置入声卡 */
snd_ctl_elem_write(handle, control);
/* 关闭声卡控制 */
snd_ctl_close(handle);
}
以获取音量为例,就以下几个关键的步骤:
- 初始化 id ,赋值已知的要素属性,方便遍历定位要素。
snd_ctl_ascii_elem_id_parse()
- 绑定 id,根据 id 获取要素 value。
snd_ctl_elem_value_set_id()
snd_ctl_elem_read()
- (默认音量是int类型)调用 int 类型获取接口,从 value 对象获取实际音量值
snd_ctl_elem_value_get_integer()
设置音量与获取音量相比,多了以下几个步骤:
- (默认音量是int类型)调用 int 类型设置接口,设置 value 对象新音量值
snd_ctl_elem_value_set_integer()
- 把 value 对象写入声卡
snd_ctl_elem_write()
当然,如果想要做的兼容性更好,我们还需要获取要素 info,以根据 info 记录的要素类型调用不同接口:
- 绑定 id,根据 id 获取要素 info
snd_ctl_elem_info_set_id()
snd_ctl_elem_info()
- 从 info 获取要素类型、要素成员数量、完整的id信息等
snd_ctl_elem_info_get_type()
snd_ctl_elem_info_get_count()
snd_ctl_elem_info_get_id()
补充一点,我们可以直接用 snd_ctl_elem_id_set_numid/name/index/...
直接初始化 id,也可以参考 amixer 通过字符串自行解析初始化 id,调用 snd_ctl_ascii_elem_id_parse()
。自行解析字符串支持以下格式:
[[iface=<iface>,][name='name',][index =<index>,][device=<device>,][subdevice=<subdevice>]]|[numid=<numid>]
7. 调试信息
调试信息用于打印声卡详细的属性,类似于 aplay 命令的 -v
选项。
alsa debug API文档:https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___dump.html
以下是我常用的一个例子:
/* 定义dump输出对象 */
snd_output_t *output = NULL;
/* 绑定输出对象到 stdout */
snd_output_stdio_attach(&output, stdout, 0);
/* dump 出声卡 handle (pcm)的信息 */
snd_pcm_dump(handle, output);
/* 关闭输出对象 */
snd_output_close(output);
snd_pcm_dump()
可以dump出播放链路中每一个节点的配置信息,例如 dmix 插件的信息:
Slave: Direct Stream Mixing PCM
Its setup is:
stream : PLAYBACK
access : MMAP_INTERLEAVED
format : S16_LE
subformat : STD
channels : 2
rate : xxxx
exact rate : xxxx (xxxx/1)
msbits : xxx
buffer_size : xxx
period_size : xxx
period_time : xxx
tstamp_mode : NONE
tstamp_type : GETTIMEOFDAY
period_step : 1
avail_min : 6000
period_event : 0
start_threshold : xxx
stop_threshold : xxxx
silence_threshold: 0
silence_size : 0
boundary : 5066549580791808000
当然,如果想看声卡的 hw/sw_params,也可以直接读 proc 的文件,例如声卡0的播放设备0节点:
cat /proc/asound/card0/pcm0p/sub0/{hw_params,sw_params}
8. 命令工具集
Alsa Utils 提供了一系列非常有用的工具集,常用的包括 arecord 录音、aplay 播放、amixer 设置。
每个命令都有详细的 --help
信息,本文只提供几个简单的例子。
# 从 default 设备录音,采样精度为 16 bit,采样率为16K,1通道
arecord -D default -f S16_LE -r 16000 -c 1 ./record.wav
# 向 default 设备播放
# wav 可以自行从头信息读取,PCM格式需要指定更多参数,不支持mp3等需要解码的音频格式
aplay -D default ./record.wav
# 修改系统音量为90
amixer -D default cset name='xxxxx Volume' 90