Linux 驱动之 ALSA(八)Alsa Plugin

PCM (digital audio) plugins

PCM plugins extends functionality and features of PCM devices. The plugins take care about various sample conversions, sample copying among channels and so on.

Plugin: hw

This plugin communicates directly with the ALSA kernel driver. It is a raw communication without any conversions. The emulation of mmap access can be optionally enabled, but expect worse latency in the case.

The nonblock option specifies whether the device is opened in a non-blocking manner. Note that the blocking behavior for read/write access won't be changed by this option. This influences only on the blocking behavior at opening the device. If you would like to keep the compatibility with the older ALSA stuff, turn this option off.

1
2
3
4
5
6
7
8
9
10
11
12
pcm.name {
type hw # Kernel PCM
card INT/STR # Card name (string) or number (integer)
[device INT] # Device number (default 0)
[subdevice INT] # Subdevice number (default -1: first available)
[sync_ptr_ioctl BOOL] # Use SYNC_PTR ioctl rather than the direct mmap access for control structures
[nonblock BOOL] # Force non-blocking open mode
[format STR] # Restrict only to the given format
[channels INT] # Restrict only to the given channels
[rate INT] # Restrict only to the given rate
[chmap MAP] # Override channel maps; MAP is a string array
}

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pcm.!default{
type hw
card 0
device 1
}

ctl.!default{
type hw
card 0
}

pcm.microphone {
type hw
card 0
device 2
}

pcm.avs_input {
type hw
card 0
device 4
}

给 hw:0,1 设备重命名为 default

Test:

1
aplay -D default test.wav

Slave definition

The slave plugin can be specified directly with a string or the definition can be entered inside a compound configuration node. Some restrictions can be also specified (like static rate or count of channels).

1
2
3
4
5
6
7
8
9
10
pcm_slave.NAME {
pcm STR # PCM name
# or
pcm { } # PCM definition
format STR # Format or "unchanged"
channels INT # Count of channels or "unchanged" string
rate INT # Rate in Hz or "unchanged" string
period_time INT # Period time in us or "unchanged" string
buffer_time INT # Buffer time in us or "unchanged" string
}

Example:

1
2
3
4
5
6
7
8
9
pcm_slave.slave_rate44100Hz {
pcm "hw:0,0"
rate 44100
}

pcm.rate44100Hz {
type plug
slave slave_rate44100Hz
}

The equivalent configuration (in one compound):

1
2
3
4
5
6
7
pcm.rate44100Hz {
type plug
slave {
pcm "hw:0,0"
rate 44100
}
}

将 hw:0,0 声卡的采样频率转换为 44100hz。

Test:

1
aplay -D rate44100Hz test.wav

Plugin: Rate

This plugin converts a stream rate. The input and output formats must be linear.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pcm.name {
type rate # Rate PCM
slave STR # Slave name
# or
slave { # Slave definition
pcm STR # Slave PCM name
# or
pcm { } # Slave PCM definition
rate INT # Slave rate
[format STR] # Slave format
}
converter STR # optional
# or
converter [ STR1 STR2 ... ] # optional
# Converter type, default is taken from
# defaults.pcm.rate_converter
# or
converter { # optional
name STR # Convertor type
xxx yyy # optional convertor-specific configuration
}
}

Plugin: Route & Volume

This plugin converts channels and applies volume during the conversion. The format and rate must match for both of them.

SCHANNEL can be a channel name instead of a number (e g FL, LFE). If so, a matching channel map will be selected for the slave.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pcm.name {
type route # Route & Volume conversion PCM
slave STR # Slave name
# or
slave { # Slave definition
pcm STR # Slave PCM name
# or
pcm { } # Slave PCM definition
[format STR] # Slave format
[channels INT] # Slave channels
}
ttable { # Transfer table (bi-dimensional compound of cchannels * schannels numbers)
CCHANNEL {
SCHANNEL REAL # route value (0.0 - 1.0)
}
}
}

Automatic conversion plugin

This plugin converts channels, rate and format on request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
}

Example:

1
2
3
4
5
6
7
8
pcm.nfbtout {
type plug
slave {
pcm "hw:1,0"
rate 8000
channels 2
}
}

Plugin: Multiple streams to One

This plugin converts multiple streams to one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pcm.name {
type multi # Multiple streams conversion PCM
slaves { # Slaves definition
ID STR # Slave PCM name
# or
ID {
pcm STR # Slave PCM name
# or
pcm { } # Slave PCM definition
channels INT # Slave channels
}
bindings { # Bindings table
N {
slave STR # Slave key
channel INT # Slave channel
}
}
[master INT] # Define the master slave
}

For example, to bind two PCM streams with two-channel stereo (hw:0,0 and hw:0,1) as one 4-channel stereo PCM stream, define like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pcm.quad {
type multi

slaves.a.pcm "hw:0,0"
slaves.a.channels 2
slaves.b.pcm "hw:0,1"
slaves.b.channels 2

bindings.0.slave a
bindings.0.channel 0
bindings.1.slave a
bindings.1.channel 1
bindings.2.slave b
bindings.2.channel 0
bindings.3.slave b
bindings.3.channel 1
}

Note that the resultant pcm "quad" is not in the interleaved format but in the "complex" format. Hence, it's not accessible by applications which can handle only the interleaved (or the non-interleaved) format. In such a case, wrap this PCM with route or plug plugin.

1
2
3
4
5
6
7
8
pcm.quad2 {
type route
slave.pcm "quad"
ttable.0.0 1
ttable.1.1 1
ttable.2.2 1
ttable.3.3 1
}

Plugin: dmix

This plugin provides direct mixing of multiple streams. The resolution for 32-bit mixing is only 24-bit. The low significant byte is filled with zeros. The extra 8 bits are used for the saturation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pcm.name {
type dmix # Direct mix
ipc_key INT # unique IPC key
ipc_key_add_uid BOOL # add current uid to unique IPC key
ipc_perm INT # IPC permissions (octal, default 0600)
hw_ptr_alignment STR # Slave application and hw pointer alignment type
# STR can be one of the below strings :
# no
# roundup
# rounddown
# auto (default)
slave STR
# or
slave { # Slave definition
pcm STR # slave PCM name
# or
pcm { } # slave PCM definition
format STR # format definition
rate INT # rate definition
channels INT
period_time INT # in usec
# or
period_size INT # in bytes
buffer_time INT # in usec
# or
buffer_size INT # in bytes
periods INT # when buffer_size or buffer_time is not specified
}
bindings { # note: this is client independent!!!
N INT # maps slave channel to client channel N
}
slowptr BOOL # slow but more precise pointer updates
}

ipc_key 必须是整数形式的唯一 ipc key。对于每个不同的 dmix 定义,这个数字必须是唯一的,因为共享内存是用这个 key 创建的。当 ipc_key_add_uid 设置为 true 时,uid 值将添加到 ipc_key 设置中。这样可以避免同一个 IPC 密钥与不同用户同时发生冲突。

hw_ptr_alignment 这个配置默认是 auto

An example configuration for setting 44100 Hz, S32_LE format as the slave PCM of "hw:0" is like below:

1
2
3
4
5
6
7
8
9
10
pcm.dmix_44 {
type dmix
ipc_key 321456 # any unique value
ipc_key_add_uid true
slave {
pcm "hw:0"
format S32_LE
rate 44100
}
}

Test:

1
aplay -Dplug:dmix_44 foo_48k.wav

Plugin: dsnoop

此插件将一个 capture 流拆分为多个。它的工作方式与 dmix 插件相反,从多个客户端同时读取共享捕获缓冲区。以下参数的含义与 dmix 插件几乎相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pcm.name {
type dsnoop # Direct snoop
ipc_key INT # unique IPC key
ipc_key_add_uid BOOL # add current uid to unique IPC key
ipc_perm INT # IPC permissions (octal, default 0600)
slave STR
# or
slave { # Slave definition
pcm STR # slave PCM name
# or
pcm { } # slave PCM definition
format STR # format definition
rate INT # rate definition
channels INT
period_time INT # in usec
# or
period_size INT # in bytes
buffer_time INT # in usec
# or
buffer_size INT # in bytes
periods INT # when buffer_size or buffer_time is not specified
}
bindings { # note: this is client independent!!!
N INT # maps slave channel to client channel N
}
slowptr BOOL # slow but more precise pointer updates
}

Open 流程

下面以 dsnoop 插件为例梳理下 alsa-lib 使用插件的流程,详细如下:

原图

  • 解析配置文件并生成配置树
  • 打开实际设备,通过 mmap 映射驱动中的控制信息和 buffer

Read 流程

以下是 read 流程,write 流程不再列出来了。

原图

  • memcopy 驱动 DMA buffer 中的数据
  • hw_ptr 数据指针的 sync

可以看到 alsa plug 支持多进程,可以多个进程打开同一个 slave 声卡,多进程间通过信号量同步。 如果要自己创建一个声卡,同时让创建的声卡支持 plugin,那么最好不要使用 appl_ptr 和依赖 appl_ptr 的 api。

参考文献

https://www.alsa-project.org/alsa-doc/alsa-lib/pcm_plugins.html
https://alsa.opensrc.org/ALSA_plugins