ALSA - PCM接口
- 0. PCM
- 1. 概述
- 2. ALSA 设备打开 和 数据传输
- 3. ALSA应用能看到的PCM设备状态迁移图
- 4. 错误码
- 5. HW/SW 参数
- 6. STREAM状态
- 7. STREAM同步
- 8. PCM 命名规范
- 9. 配置ALSA Device
- 10. 实践
- 引用
本文大部分内容纯属个人基于ALSA官网的”PCM Interface“的理解,如有理解错误的地方,欢迎邮件告诉我 :)
0. PCM
Pulse-code modulation(PCM)
将模拟信号表示为数字信号的一种方法,它是计算机,CD,数字电话以及其他数字音频设备对数字音频信号的标准表示法。在一个PCM流当中,模拟信号是按照一定间隔(采样周期)进行采集,每个采样点的采样值则被近似地量化为最接近的一个值(由采样深度决定)。
Linear pulse-code-modulation(LPCM)
PCM中的一种类型,这种类型的量化值与模拟信号强度(响度)是呈线性关系。
Non-linear pulse-code-modulation
LPCM中的一种类型PCM不同,它的量化值相对与模拟信号强度呈非线性关系。例如:u-law, A-law等。
一个PCM流的质量取决与两方面:
- 采样频率
- 采样深度(bit depth)
在ALSA的PCM接口当中,PCM泛指所有离散时间内的离散采样音频信号。
1. 概述
1.1 PCM设备的两种类型
PCM设备(内核level)可以简单地分为”输出(playback)”和”输入(capture)”两类。其中的方向是站在ALSA应用的角度而言,即:
- PCM输出设备接收从ALSA应用传入的数据,再到mixer(CTL设备),然后路由至输出socket,外接的物理声卡设备(或者路由到别的PCM输入设备)
-
PCM输入设备将数据通过socket从物理声卡设备(或者经由mixer从别的PCM输出设备)读入,传入ALSA应用
(注意:这里的PCM输入/输出设备指的是ALSA中的逻辑设备,它可以是物理的声卡设备,也可以是Plugin)
1.2 PCM设备和ALSA应用的ring buffer
具体参见这里
声卡设备在硬件层面有一个ring buffer,同时在alsa的kernel space,也维护一个ring buffer:
- playback 当ALSA设备进入RUNNING状态,每当声卡传出一个period之后(花费1个period_time的时间),会向kernel发送硬件中断,于是kernel将另一个period的数据由自身维护的ring buffer传输至硬件声卡的buffer(瞬间);
- capture 当ALSA设备进入RUNNING状态,声卡设备会不断地采集到数据,每采集一个period的数据后,声卡设备会发送硬件中断到kernel,kernel将数据由硬件buffer传输至自己维护的ring buffer中.
如下图所示:
作为alsa lib用户,我们只需要关心alsa-lib中的ring buffer。它由两个指针维护:
- 对于 playback:
- 当前硬件正在读的sample (START)
- 上一次应用程序写的sample (END)
- 对于 capture:
- 当前硬件正在写的sample (END)
- 上一次应用程序读的sample (START)
(参考Wiki-ring buffer)
1.2.1 ring buffer - 单位(period, frame, sample)
buffer的size可以通过ALSA library的API进行修改。如果buffer设的太大,那么一次数据的传输需要的延迟会增加。为了解决这个问题,ALSA将buffer分为一系列的period(在OSS/Free语境中称为fragment),然后以period为单位进行数据的传输。
因此,在buffer里有以下几个单位:
- period 一个period当中存储多个多个frame
- frame 一个frame中存储一个或多个同一时间采集的sample。多个ADC/DAC用于同一时间采集/转换多个sample,那么这几个同时间被处理的sample组成一个frame。通常,如果一个设别有N个channel,那么它的一个frame等于N个sample
- sample 每一个采样得到的数值称为一个sample,它可能是多个字节的,大端或者小端,浮点数或者整数,有符号或者无符号
它们的关系如下图所示:
1.2.2 ring buffer - 访问方式(interleaved, non-interleaved)
Ring buffer 有三种访问方式:
-
Interleaved access
C0 C1 C2 C3 C0 C1 C2 C3 ....
-
Non-interleaved access
C0 C0 C0 C0 ................ C1 C1 C1 C1 ............. C2 C2 C2 C2 ............... C3 C3 C3 C3 ........... 注意:对于这种访问模式,每个通道有单独的ring buffer!
-
Complex access
具体参见 PCM Ring Buffer
2. ALSA 设备打开 和 数据传输
2.1 阻塞和非阻塞打开设备
PCM设备可以以阻塞或非阻塞两种模式打开:
-
打开设备时,如果该设备当前正在被其他应用使用:
- 阻塞:则调用的进程会被阻塞
- 非阻塞:立即返回
-EBUSY
给调用进程
-
打开设备的mode也会影响到它的标准I/O数据传输。当应用调用PCM API对该设备进行操作(例如: playback/capture)如果该设备没被其他应用使用,但是ring buffer为满(对于playback)/空(对于capture):
- 阻塞:则调用进程会被阻塞
- 非阻塞:立即返回
-EAGAIN
给调用进程
该阻塞模式模式可以通过
snd_pcm_nonblock
函数改变。
2.2 数据传输
2.2.1 读写传输(read/write)
可以用于传输interleaved/non-interleaved的数据,并且有阻塞和非阻塞两种模式
2.2.2 直接读写传输(mmap)
可以用于传输interleaved/non-interleaved/complex的数据。应用程序的调用顺序如下:
-
capture
snd_pcm_start()
: 启动设备snd_pcm_avail_update()
: 获得当前ring buffer中已读到的数据有多少- (optional)
snd_pcm_wait()
: 等待ring buffer 中的数据达到设置的avail_min - (optional)
snd_pcm_avail_update()
: 如果之前调用了snd_pcm_wait
, 那么需要在这里再次调用snd_pcm_avail_update
snd_pcm_mmap_begin()
: 获得一片一定大小的内存,这片区域内是当前可被读取的数据. 其中的frame参数是[in,out], in代表你想读写的frame数,out代表实际读写的frame数- 读取数据
snd_pcm_mmap_commit()
: 告诉alsa driver之前那片区域已经读取完毕,将那片内存标记为not ready.这个函数的返回值表示ring buffer中应用指针实际被更新的frame数,该数值如果和你实际读写的数值不同,则有两种情况:- 返回值 < 0: 代表进入了某种错误状态(包括xrun)
- 返回值 >=0 但是 != 希望读写的值:代表在commit的时候(i.e. 更新指针的时候)发生了xrun
- 重复2-6
-
playback
snd_pcm_avail_update()
: 获得当前ring buffer中可以写入的数据有多少. playback设备在打开后整个buffer size的frame都是available的,因此这里会返回buffer sizesnd_pcm_mmap_begin()
: 获得内存进行写入- 写入数据
snd_pcm_mmap_commit()
: 告诉alsa driver之前那片区域已经写入完毕,将那片内存标记为not ready(直到那片区域的数据已经传给声卡).这个函数的返回值表示ring buffer中应用指针实际被更新的frame数,该数值如果和你实际读写的数值不同,则有两种情况:- 返回值 < 0: 代表进入了某种错误状态(包括xrun)
- 返回值 >=0 但是 != 希望读写的值:代表在commit的时候(i.e. 更新指针的时候)发生了xrun
snd_pcm_start()
: 启动设备。 注意,如果之前没有commit过就启动设备,返回错误(-EPIPE), 设备处于PREPARED状态snd_pcm_avail_update()
- (optional)
snd_pcm_wait()
- (optional)
snd_pcm_avail_update()
: 如果之前调用了snd_pcm_wait
, 那么需要在这里再次调用snd_pcm_avail_update
snd_pcm_mmap_begin()
- 写入数据
snd_pcm_mmap_commit()
- 重复6-11
注意:
- 比较早的ALSA版本中,如果当前设备处于resample状态,则
snd_pcm_wait
可能break,具体请参考这里
2.2.2.1 snd_pcm_avail_update vs snd_pcm_avail
2.2.2.2 snd_pcm_mmap_begin()
这个API的返回参数的物理意义会根据不同的access type而不同,如下图所示:
注意:这里的 offset 的单位实际上是 areas[x].step
的个数。
因此,想要得到某个channel的当前period起始地址的话,可以使用类似如下的代码,适用于两种access type:
unsigned char* getMmapBeginAddress(const snd_pcm_channel_area_t area, snd_pcm_uframes_t offset)
{
return (unsigned char*)(area.addr) + offset*(area.step/8) + area.first/8;
}
在得到了起始地址之后,如果要往里面填数据(例如填“0”)的话不能直接使用memset
之类的API直接操作,而是要根据不同的access type来操作。这里提供一段片段,应该适用于两种access type:
// pcm_mmap_begin(&areas, &offset, &frames);
unsigned int byte_per_step = areas[0].step / 8;
for (unsigned i = 0; i != n_channel; ++i)
{
unsigned char* start_address = getMmapBeginAddress(areas[i], offset);
for (unsigned int j = 0; j != frames; ++j)
{
memset(start_address + (byte_per_step * j), 0, (m_pALSACfg->bitsPerSample)/8);
}
}
// pcm_mmap_commit(offset, frames);
3. ALSA应用能看到的PCM设备状态迁移图
调用snd_pcm_state
可以获取当前PCM设备的状态
SND_PCM_STATE_OPEN
表示PCM设备处于打开状态。
进入原因:
snd_pcm_open
调用成功snd_pcm_hw_params
调用失败,目的是强制ALSA应用设置正确的硬件参数
SND_PCM_STATE_SETUP
表示PCM设备已经被正确地设置了硬件参数。此时,它正在等到snd_pcm_prepare
来使设备对设置的操作(playback/capture)做准备。
进入原因:
snd_pcm_hw_params
调用成功snd_pcm_drop
SND_PCM_STATE_PREPARED
表示设备已经就绪。此时,ALSA应用可以调用snd_pcm_start
,读或者写来进行操作。
进入原因:
snd_pcm_prepare
调用成功
SND_PCM_STATE_RUNNING
表示设备正在操作,即正在处理采样的数据。这个过程可以被snd_pcm_drop
或snd_pcm_drain
停止。
进入原因:
snd_pcm_start
调用成功- 操作为playback,并且ALSA应用的frame超过了软件参数中设置的start threshold
- 操作为capture,并且ALSA应用要求读的frame超过了软件参数中设置的start threshold
SND_PCM_STATE_XRUN
表示设备overrun(capture)或者underrun(playback). 其中,前者表示ALSA应用没有及时将PCM设备的ring buffer中的数据读走,导致ring buffer满了;后者表示ALSA应用没有及时往PCM设备ring buffer里传数据,导致ring buffer空了。
进入这种情况后,建议调用snd_pcm_recover
来恢复,也可以通过snd_pcm_prepare
/snd_pcm_drop
/snd_pcm_drain
来离开此状态
进入原因:
- overrun/underrun发生
- PCM设备buffer中的frame数小于stop threshold
SND_PCM_STATE_DRAINING
表示capture设备正在等待ALSA应用将ring buffer中的数据读走。
对于playback模式的设备调用了snd_pcm_drain
是不会进入这个状态的。
进入原因:
- 设备为capture模式,并且调用了
snd_pcm_drain
SND_PCM_STATE_PAUSED
表示支持pause(可以通过snd_pcm_hw_params_can_pause
来确定)的设备处于停止状态。
进入原因:
- 支持pause的设备调用
snd_pcm_pause
SND_PCM_STATE_SUSPENDED
表示由于电源管理系统,使设备进入一种挂起状态。
对于支持resume的设备,建议调用snd_pcm_resume
来离开该状态,直接进入SND_PCM_STATE_PREPARE状态(可以调用snd_pcm_hw_params_can_resume
来确定)。对于不支持resume的设备,可以调用snd_pcm_prepare
/snd_pcm_drop
/snd_pcm_drain
来离开该状态。
进入原因:
- 设备由于电源管理系统而进入该状态
SND_PCM_STATE_DISCONNECTED
表示设备不再连接系统,该状态下不再接受任何I/O调用。
不完整的状态迁移图
4. 错误码
-
-EPIPE 表示设备处于 xrun (underrun for playback or overrun for capture).
-
-ESTRPIPE 表示设备处于 suspended. 这时候需要循环调用
snd_pcm_resume()
直到它返回非-EAGAIN的error( setup )或者0 ( prepare ). -
-EBADFD 表示设备处于一个错误的状态,这意味着应用和alsa lib之间的握手协议已经错乱了.
-
-ENOTTY, -ENODEV 表示设备已经被物理地移除了.
5. HW/SW 参数
5.1 HW 参数
ALSA PCM设备对于硬件参数,使用了一种”逐步限制”的机制(refinement system) - snd_pcm_hw_params_t
. 应用程序在一开始设定所有支持的HW参数空间,然后通过设置其中的某几个参数为定值,直到其他参数都被限定为止。最后,将设定好了的参数空间install给该设备(snd_pcm_hw_params
)。之后,HW参数就不得再改变了。
5.1.1 API
对于某个HW参数,会有以下几种形式的API可供使用:
-
snd_pcm_hw_params_get_xxx
获得某个硬件参数的大约值。有些API中带dir参数(out),文档中说是表明实际的硬件参数值与该大约值的大小关系,但是实际测试发现该值不是0就是1,所以建议在get的API中不要依赖该值; -
snd_pcm_hw_params_set_xxx
设置某个硬件参数,如果当前硬件在当前的参数空间设置下不支持该值,则设置的实际值会与期望值不同。dir(in),用于指定当期望值不被支持的时候,硬件会选用比该值小的实际值还是大的实际值。(期望值 –(dir)–> 实际值) -
snd_pcm_hw_params_set_xxx_near
设置某个硬件参数,如果当前硬件在当前的参数空间设置下不支持该值,则设置的实际值会与期望值不同。dir(in),用于指定当期望值不被支持的时候,会选用比该值小的实际值还是大的实际值。同时,实际被设置的值也会被转换为大约值传出来,这个大约值可能由于类型关系而于实际值有所区别(例如rate的实际值是个浮点数,而大约值却是个ulong)。(期望值 –(dir)–> 实际值 –(round)–> 大约值)注意:有时候并不是完全这样的,例如在我的环境下如果设置rate为5005,会有以下结果:
set_dir expect actual approx 1 5005 5004.x 5005 -1 5005 5005.x 5004 这正好相反…(黑人问号脸) 所以这个dir参数还是不用为妙!
snd_pcm_hw_params_test_xxx
测试在硬件的当前参数空间内,某个硬件参数是否可以被设置snd_pcm_hw_params_can_xxx
测试当前硬件是否支持某种功能snd_pcm_xxx_name
返回某个硬件参数的名字(包括:type, stream, access, format,…)
5.1.2 rate/buffer/period的单位
-
rate 的单位是 frame/s
- period time 的单位是us
- period size 的单位是frame
-
period bytes 的单位是byte
- buffer time 的单位是us
- buffer size 的单位是frame
- buffer bytes 的单位是byte
5.1.3 periods vs buffer size vs buffer time
- buffer size/time指定了ring buffer的大小,它一定是period size/time的整数倍;
- periods实际也是用于指定buffer的大小的.
额外地,如果指定了periods而没有指定buffer size,则实际的buffer size将会位于:[periods*period_size, buffer_size max]
5.1.4 access type
- 选择不同的传递API(r/w or direct), 记得使用相应的access type, 否则会返回-EBADF
- 不是所有设备都满足NONINTERLEAVED/INTERLEAVED,例如我的声卡只支持INTERLEAVED,因此最好在设置前用test API判断下
5.1.5 format
- 物理长度和有效长度的关系 在使用read/write API的时候对于buffer的读写要使用物理长度
- LE 和 BE
- 浮点数类型可以通过与int一起作为一个Union来简化多字节的操作
5.2 SW 参数
软件参数可以在任意时刻修改,包括RUNNING状态。
5.2.1 avail_min
默认值一般 == period size, 用于snd_pcm_wait
, 当available的值大于avail_min时,该函数返回。
5.2.2 start threshold
PCM设备状态自动进入RUNNING的事件。注意:
- start_threshold默认值为 1 frame
- 只对
readi/writei
有效,对于 direct access 无效
具体地,
-
playback
前提:设备处于 PREPARED 状态, start threshold < buffer size(i.e. start threshold enabled):
- writei trigger: 写入后,如果ring buffer 中的数据 >= start_threshold, PCM设备进入 RUNNING
- 手动 start (晦涩的部分 - -):
- 如果ring buffer中没有数据,则手动start会使设备可能保持在 PREPARED 状态也可能进入 RUNNING 状态
- 如果ring buffer中有数据,则手动start会使设备进入 RUNNING (此时相当于发生硬件中断,如果可传输的数据不足1个period,则会出现 XRUN )
-
capture
前提:设备处于 PREPARED 状态, start threshold < buffer size(i.e. start threshold enabled):
- readi trigger: 如果ALSA应用要求读的数据 >= start_threshold, PCM设备会进入 RUNNING 状态
- 手动 start: 立即进入 RUNNING 状态
如果你希望手动地使Playback PCM设备进入RUNNING状态(snd_pcm_start
), 则可以将该值设的比buffer size更大(e.g. MAX_INT),可是要注意,如果ring buffer中没有数据或者数据不多,则手动start可能使设备进入 XRUN 状态!
5.3 典型配置项
- 打开设备,确定设备类型(capture/playback)
- 设置硬件参数:
- 分配参数空间内存
- 获取最大参数空间
- 判断并设置access type
- 设置数据类型:
- format
- channel (near)
- rate (near)
- 设置硬件中断频率: period_size/period_time(near). 典型做法为:设置period_time, 然后由它来获取period_size,以备用
- 设置ring buffer大小: buffer_size/buffer_time/periods(near). 典型做法为:设置buffer_time/periods, 然后由它来获取buffer_size,以备用
- 设置
- (可选)dump hw info
- 释放分配的参数空间内存
- 设置软件参数:
- 分配参数空间内存
- 获取当前软件参数空间
- (可选)设置start threshold (默认为0 frame)
- (可选)设置avail_min (默认为 1 period size)
- (可选)设置period event
- 设置
- (可选)dump sw info
- 释放分配的参数空间内存
6. STREAM状态
这里的流的状态(status)不同于上面所提到PCM设备的状态(snd_pcm_state
), 通过snd_pcm_status_get_state
返回的snd_pcm_status_t
结构体记录。其中包括:
- timestamp of trigger:
snd_pcm_status_get_trigger_tstamp()
- timestamp of last pointer update:
snd_pcm_status_get_tstamp()
- delay(sample):
snd_pcm_status_get_delay()
- available count(sample):
snd_pcm_status_get_avail()
- maximum available samples:
snd_pcm_status_get_avail_max()
- ADC over-range count(sample):
snd_pcm_status_get_overrange()
6.1 获得stream状态,更新r/w指针
7. STREAM同步
8. PCM 命名规范
9. 配置ALSA Device
9.1 物理声卡设备
ALSA世界中,物理的声卡设备由card, device, subdevice指定。
- card 对应一张声卡设备,它可以由STR或者INT(zero-based index)表示。例如,我的Ubuntu-12.04-LTS上执行
$ aplay -l
,可以得到card: 0 或者 Intel; - device 大部分的ALSA硬件访问都发生在这一层。它由INT(zero-based index)表示。同一声卡的不同device可以被独立地使用。通常,只要指定了__card__和__device__,就等于指定了音频信号的入/出口;
- subdevice ALSA可以识别的最小单位。最常见的情况是,一个声卡设备的不同channel有单独的subdevice。如果传输单声道的音频,则可以在某个特定的subdevice中传输;而如果指定某个subdevice传输多声道数据,则该subdevice后面的subdevice也会被自动使用。
9.2 ALSA设备
和上面提到的物理声卡设备不同,ALSA设备由字符串来指定,它们被定义在ALSA配置文件中(通常为: /usr/share/alsa/alsa.conf, 并在其中load额外的配置文件)。ALSA设备通常是plugin的封装(各种plugin也定义在在配置文件中)。
例如, /usr/share/alsa/alsa.conf 中,定义了ALSA设备”hw”, 它使用了Plugin:hw
: (比较奇怪吧…看下面的注释)
-
ALSA device: “hw” 的定义:
pcm.hw { # pcm.ALSA设备名 这里"ALSA设备名为: hw" @args [ CARD DEV SUBDEV ] # ALSA设备允许有输入参数,这里定义了三个(有默认值的)输入参数 @args.CARD { type string default { @func getenv vars [ ALSA_PCM_CARD ALSA_CARD ] default { @func refer name defaults.pcm.card } } } @args.DEV { type integer default { @func igetenv vars [ ALSA_PCM_DEVICE ] default { @func refer name defaults.pcm.device } } } @args.SUBDEV { type integer default { @func refer name defaults.pcm.subdevice } } type hw # type后面跟的是plugin的名字。这里使用的Plugin为hw, 更多plugin请见:http://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html card $CARD device $DEV subdevice $SUBDEV hint { show { @func refer name defaults.namehint.extended } description "Direct hardware device without any conversions" } }
9.3 ALSA plugin
ALSA提供了各种功能的plugin。以下记录了其中的某几个容易混淆的概念。在探索细节之前,先了解一下plugin的工作模式,如下所示:
其中,slave 也可能是另一个plug。
9.3.1 plug
这个plugin可以用于自动转换各种硬件参数,包括: format, channels, rate. 使用plug插件的ALSA设备定义格式如下:
pcm.name {
type plug # Automatic conversion PCM
slave STR # Slave name
# or
slave { # Slave definition
pcm STR # Slave PCM name
# or
pcm { } # Slave PCM definition
[format STR] # Slave format (default nearest) or "unchanged"
[channels INT] # Slave channels (default nearest) or "unchanged"
[rate INT] # Slave rate (default nearest) or "unchanged"
}
route_policy STR # route policy for automatic ttable generation
# STR can be 'default', 'average', 'copy', 'duplicate'
# average: result is average of input channels
# copy: only first channels are copied to destination
# duplicate: duplicate first set of channels
# default: copy policy, except for mono capture - sum
ttable { # Transfer table (bi-dimensional compound of cchannels * schannels numbers)
CCHANNEL {
SCHANNEL REAL # route value (0.0 - 1.0)
}
}
rate_converter STR # type of rate converter
# or
rate_converter [ STR1 STR2 ... ]
# type of rate converter
# default value is taken from defaults.pcm.rate_converter
}
这里我的理解只停留在slave中的format, channels和rate这几个配置项。以format为例,假设,当前我要向一个playback设备,输入一个8KHz采样率,5秒长度的1KHz的正弦波,则该文件有8K * 5 = 40K个frame。我希望该设备以16KHz的采样率往外传输音频。此时,我需要一个plug
plugin的设备,如下所示:
pcm.my_plug{
type plug
slave.pcm "hw:0,0"
slave.rate 16000
}
这里的slave.rate 16000
表示,my_plug
的输出为16KHz的数据,作为slave
,也就是"hw:0,0"
的输入。这里有个先决条件是,"hw:0,0"
在当前硬件参数空间中是支持16KHz的(可以用alsacap来测试)。
注意:在代码中打开该设备后,配置hw参数的时候设置的rate应该是与文件一致:8KHz (基本原则是:文件采样率是多少就应该以多少的采样率来读)
整个过程如下图所示:
需要注意的是,采样率转换后的文件并不是完全符合上面的公式的,使用plug插件的采样率转换功能以后,开始的一小部分和结束的一小部分音频的数据会是0.例如,我有一个8000Hz采样率双通道16位采样深度的5秒正弦波,其大小是:160,000 Byte. 在转换成44.1KHz采样率以后,理论值是:160,000 / 8000 * 441000 = 882,000 Byte. 而实际值是884,736 Byte. 通过audacity分析实际音频文件,发现开始的3ms和结束的12ms的数据都是0,整个文件的长度变为了5.015秒。多出的15ms的数据大小 = 0.015 * 44100 * 2 * 2 = 2646 byte ,约等于比理想状态多出来的部分了。
反过来,对于capture设备,设备中的rate代表采样的频率,设置设备hw参数的代码中配置的rate代表文件的采样率。
9.3.2 file
这个plugin可以用于将写入对应的alsa设备的数据截取出来。要注意的是:这里截取的数据是读出或写入设备的数据,不等同于实际按时间顺序传给ALSA的数据。例如,我们在一个playback设备中配置了 file plugin ,即使我们在写入数据的过程中发生了underrun(即两次写入之间有间隙),截取出来的数据依然是连续的。所以,如果发生了声音卡顿等现象,做分段分析的时候,不能通过这种方式来排除ALSA前端应用的嫌疑。
10. 实践
10.1 从Chrome浏览器中下载音乐
基本思想是:
- 如果Chrome直接通过ALSA接口播放,则有两种情况:
- 打开
default
ALSA设备进行播放,需要做:- 将 /usr/share/alsa/pulse-alsa.conf重命名,因为它会将
default
ALSA设备设置为使用Plugin: pulse
的设备; -
在~/.asoundrc加入:
pcm.!default{ type file slave { pcm { type hw card 0 device 0 } } file "/tmp/a.wav" }
- 将 /usr/share/alsa/pulse-alsa.conf重命名,因为它会将
- 打开预定义的ALSA设备:
hw
(例如:hw:0,0
):-
在 /usr/share/alsa/alsa.conf中,修改
pcm.hw
ALSA设备的定义。将以下部分:type hw card $CARD device $DEV subdevice $SUBDEV hint { show { @func refer name defaults.namehint.extended } description "Direct hardware device without any conversions" }
修改为:
type file # 使用file plugin来代替本来的hw plugin slave.pcm { type hw card 0 device 0 } file "/tmp/a.wav"
-
- 打开
- 如果Chrome使用的是PulseAudio接口,那么需要做别的处理.
于是,我进行了如下步骤:
- 判断浏览器是否使用pulseaudio播放还是使用ALSA接口播放
-
暂停PulseAudio(关机前记得回复):
$ echo "autospawn = no" > ~/.pulse/client.conf $ pactl exit
-
打开Chrome,用网易云音乐播放歌曲,果然无法播放了. 因此,Chrome使用的是PulseAudio接口。
-
-
修改/usr/share/alsa/中的
Plugin: pulse
定义:#pcm.pulse { # type pulse # hint { # show on # description "PulseAudio Sound Server" # } #} pcm.pulse { type file slave { pcm "hw:0,1" } file "/tmp/c.wav" }
- 然而,并无任何ruan用。。。未完待续。
EDIT ON 2019-05-22
根据这个回答,通过以下指令即可录音:
# 先查看一下哪个sink在工作
$ pacmd # 使用pacmd的原因是它的list可以看到状态
>>> list-sink-inputs
# 找到状态为RUNNING的那个sink input,记录它的index
>>> exit
# 创建一个新的sink
$ pactl load-module module-null-sink sink_name=steam
# 将当前的sink-input输出到这个新创建的sink,而不是speaker对应的sink
$ pactl move-sink-input $INDEX steam
# 输出这个sink的数据(raw pcm)到lame,转成mp3
$ parec -d steam.monitor | lame -r -s 44.1 --bitwidth 16 --signed --little-endian - output.mp3
引用
[1] PCM interface
[2] Introduction to Sound Programming with ALSA
[3] PCM Ring Buffer
Comments