ALSA 学习小记

时间:2023-02-03 11:38:45

对于playback

snd_pcm_begin

snd_pcm_commit,

貌似 commit给的frame才会使得alsa去把数据填充

转自 http://magodo.github.io/

ALSA - PCM接口

Apr 14, 2016


Table of Content


本文大部分内容纯属个人基于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 学习小记

作为alsa lib用户,我们只需要关心alsa-lib中的ring buffer。它由两个指针维护:

  1. 对于 playback:
    • 当前硬件正在读的sample (START)
    • 上一次应用程序写的sample (END)
  2. 对于 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,它可能是多个字节的,大端或者小端,浮点数或者整数,有符号或者无符号

它们的关系如下图所示:

ALSA 学习小记

1.2.2 ring buffer - 访问方式(interleaved, non-interleaved)

Ring buffer 有三种访问方式:

  1. Interleaved access

     C0 C1 C2 C3 C0 C1 C2 C3 ....
    
  2. Non-interleaved access

     C0 C0 C0 C0 ................ C1 C1 C1 C1 ............. C2 C2 C2 C2 ............... C3 C3 C3 C3 ...........   注意:对于这种访问模式,每个通道有单独的ring buffer!
    
  3. Complex access

具体参见 PCM Ring Buffer

2. ALSA 设备打开 和 数据传输

2.1 阻塞和非阻塞打开设备

PCM设备可以以阻塞或非阻塞两种模式打开:

  1. 打开设备时,如果该设备当前正在被其他应用使用:

    • 阻塞:则调用的进程会被阻塞
    • 非阻塞:立即返回 -EBUSY给调用进程
  2. 打开设备的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

    1. snd_pcm_start(): 启动设备
    2. snd_pcm_avail_update(): 获得当前ring buffer中已读到的数据有多少
    3. (optional) snd_pcm_wait(): 等待ring buffer 中的数据达到设置的avail_min
    4. (optional) snd_pcm_avail_update(): 如果之前调用了snd_pcm_wait, 那么需要在这里再次调用snd_pcm_avail_update
    5. snd_pcm_mmap_begin(): 获得一片一定大小的内存,这片区域内是当前可被读取的数据. 其中的frame参数是[in,out], in代表你想读写的frame数,out代表实际读写的frame数
    6. 读取数据
    7. snd_pcm_mmap_commit(): 告诉alsa driver之前那片区域已经读取完毕,将那片内存标记为not ready.这个函数的返回值表示ring buffer中应用指针实际被更新的frame数,该数值如果和你实际读写的数值不同,则有两种情况:
      • 返回值 < 0: 代表进入了某种错误状态(包括xrun)
      • 返回值 >=0 但是 != 希望读写的值:代表在commit的时候(i.e. 更新指针的时候)发生了xrun
    8. 重复2-6
  • playback

    1. snd_pcm_avail_update(): 获得当前ring buffer中可以写入的数据有多少. playback设备在打开后整个buffer size的frame都是available的,因此这里会返回buffer size
    2. snd_pcm_mmap_begin(): 获得内存进行写入
    3. 写入数据
    4. snd_pcm_mmap_commit(): 告诉alsa driver之前那片区域已经写入完毕,将那片内存标记为not ready(直到那片区域的数据已经传给声卡).这个函数的返回值表示ring buffer中应用指针实际被更新的frame数,该数值如果和你实际读写的数值不同,则有两种情况:
      • 返回值 < 0: 代表进入了某种错误状态(包括xrun)
      • 返回值 >=0 但是 != 希望读写的值:代表在commit的时候(i.e. 更新指针的时候)发生了xrun
    5. snd_pcm_start(): 启动设备。 注意,如果之前没有commit过就启动设备,返回错误(-EPIPE), 设备处于PREPARED状态
    6. snd_pcm_avail_update()
    7. (optional)snd_pcm_wait()
    8. (optional) snd_pcm_avail_update(): 如果之前调用了snd_pcm_wait, 那么需要在这里再次调用snd_pcm_avail_update
    9. snd_pcm_mmap_begin()
    10. 写入数据
    11. snd_pcm_mmap_commit()
    12. 重复6-11

注意:

  1. 比较早的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而不同,如下图所示:

ALSA 学习小记

注意:这里的 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设备处于打开状态。

进入原因:

  1. snd_pcm_open调用成功
  2. snd_pcm_hw_params调用失败,目的是强制ALSA应用设置正确的硬件参数

SND_PCM_STATE_SETUP

表示PCM设备已经被正确地设置了硬件参数。此时,它正在等到snd_pcm_prepare来使设备对设置的操作(playback/capture)做准备。

进入原因:

  1. snd_pcm_hw_params调用成功
  2. snd_pcm_drop

SND_PCM_STATE_PREPARED

表示设备已经就绪。此时,ALSA应用可以调用snd_pcm_start,读或者写来进行操作。

进入原因:

  1. snd_pcm_prepare调用成功

SND_PCM_STATE_RUNNING

表示设备正在操作,即正在处理采样的数据。这个过程可以被snd_pcm_dropsnd_pcm_drain停止。

进入原因:

  1. snd_pcm_start调用成功
  2. 操作为playback,并且ALSA应用的frame超过了软件参数中设置的start threshold
  3. 操作为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来离开此状态

进入原因:

  1. overrun/underrun发生
  2. PCM设备buffer中的frame数小于stop threshold

SND_PCM_STATE_DRAINING

表示capture设备正在等待ALSA应用将ring buffer中的数据读走。

对于playback模式的设备调用了snd_pcm_drain是不会进入这个状态的。

进入原因:

  1. 设备为capture模式,并且调用了snd_pcm_drain

SND_PCM_STATE_PAUSED

表示支持pause(可以通过snd_pcm_hw_params_can_pause来确定)的设备处于停止状态。

进入原因:

  1. 支持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来离开该状态。

进入原因:

  1. 设备由于电源管理系统而进入该状态

SND_PCM_STATE_DISCONNECTED

表示设备不再连接系统,该状态下不再接受任何I/O调用。

不完整的状态迁移图

ALSA 学习小记

4. 错误码

  1. -EPIPE 表示设备处于 xrun (underrun for playback or overrun for capture).

  2. -ESTRPIPE 表示设备处于 suspended. 这时候需要循环调用snd_pcm_resume() 直到它返回非-EAGAIN的error( setup )或者0 ( prepare ).

  3. -EBADFD 表示设备处于一个错误的状态,这意味着应用和alsa lib之间的握手协议已经错乱了.

  4. -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

  1. buffer size/time指定了ring buffer的大小,它一定是period size/time的整数倍;
  2. periods实际也是用于指定buffer的大小的.

额外地,如果指定了periods而没有指定buffer size,则实际的buffer size将会位于:[periods*period_size, buffer_size max]

5.1.4 access type

  1. 选择不同的传递API(r/w or direct), 记得使用相应的access type, 否则会返回-EBADF
  2. 不是所有设备都满足NONINTERLEAVED/INTERLEAVED,例如我的声卡只支持INTERLEAVED,因此最好在设置前用test API判断下

5.1.5 format

  1. 物理长度和有效长度的关系 在使用read/write API的时候对于buffer的读写要使用物理长度
  2. LE 和 BE
  3. 浮点数类型可以通过与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的事件。注意:

  1. start_threshold默认值为 1 frame
  2. 只对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 典型配置项

  1. 打开设备,确定设备类型(capture/playback)
  2. 设置硬件参数:
    1. 分配参数空间内存
    2. 获取最大参数空间
    3. 判断并设置access type
    4. 设置数据类型:
      1. format
      2. channel (near)
      3. rate (near)
    5. 设置硬件中断频率: period_size/period_time(near). 典型做法为:设置period_time, 然后由它来获取period_size,以备用
    6. 设置ring buffer大小: buffer_size/buffer_time/periods(near). 典型做法为:设置buffer_time/periods, 然后由它来获取buffer_size,以备用
    7. 设置
    8. (可选)dump hw info
    9. 释放分配的参数空间内存
  3. 设置软件参数:
    1. 分配参数空间内存
    2. 获取当前软件参数空间
    3. (可选)设置start threshold (默认为0 frame)
    4. (可选)设置avail_min (默认为 1 period size)
    5. (可选)设置period event
    6. 设置
    7. (可选)dump sw info
    8. 释放分配的参数空间内存

6. STREAM状态

这里的流的状态(status)不同于上面所提到PCM设备的状态(snd_pcm_state), 通过snd_pcm_status_get_state返回的snd_pcm_status_t结构体记录。其中包括:

  1. timestamp of trigger: snd_pcm_status_get_trigger_tstamp()
  2. timestamp of last pointer update: snd_pcm_status_get_tstamp()
  3. delay(sample): snd_pcm_status_get_delay()
  4. available count(sample): snd_pcm_status_get_avail()
  5. maximum available samples: snd_pcm_status_get_avail_max()
  6. 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的工作模式,如下所示:

ALSA 学习小记

其中,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的采样率往外传输音频。此时,我需要一个plugplugin的设备,如下所示:

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 (基本原则是:文件采样率是多少就应该以多少的采样率来读)

整个过程如下图所示:

ALSA 学习小记

需要注意的是,采样率转换后的文件并不是完全符合上面的公式的,使用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代表文件的采样率。

10. 实践

10.1 从Chrome浏览器中下载音乐

基本思想是:

  1. 如果Chrome直接通过ALSA接口播放,则有两种情况:
    1. 打开defaultALSA设备进行播放,需要做:
      1. 将 /usr/share/alsa/pulse-alsa.conf重命名,因为它会将defaultALSA设备设置为使用Plugin: pulse的设备;
      2. ~/.asoundrc加入:

         pcm.!default{
             type file
             slave {
                 pcm {
                     type hw
                     card 0
                     device 0
                 }
             }
             file "/tmp/a.wav"
         }
        
    2. 打开预定义的ALSA设备:hw(例如: hw:0,0):
      1. 在 /usr/share/alsa/alsa.conf中,修改pcm.hwALSA设备的定义。将以下部分:

           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"
        
  2. 如果Chrome使用的是PulseAudio接口,那么需要做别的处理.

于是,我进行了如下步骤:

  1. 判断浏览器是否使用pulseaudio播放还是使用ALSA接口播放
    1. 暂停PulseAudio(关机前记得回复):

       $ echo "autospawn = no" > ~/.pulse/client.conf
       $ pactl exit
      
    2. 打开Chrome,用网易云音乐播放歌曲,果然无法播放了. 因此,Chrome使用的是PulseAudio接口。

  2. 修改/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"
     }
    
  3. 然而,并无任何ruan用。。。未完待续。

引用

[1] PCM interface

[2] Introduction to Sound Programming with ALSA

[3] PCM Ring Buffer

[4] A close look at ALSA

[5] Linux AlSA sound notes

  • Zhaoting's Blog
  • 2015-2016
  • Made in Shanghai