Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)

1、概述 ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构,想了解更多的关于ALSA的这一开源项目的信息和知识,请查看以下网址:http://www.alsa-project.org/。
在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为我们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,即可以完成对底层音频硬件的控制。
Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

图 1.1 alsa的软件体系结构
由图1.1可以看出,用户空间的alsa-lib对应用程序提供统一的API接口,这样可以隐藏了驱动层的实现细节,简化了应用程序的实现难度。内核空间中,alsa-soc其实是对alsa-driver的进一步封装,它针对嵌入式设备提供了一些列增强的功能。本系列博文仅对嵌入式系统中的alsa-driver和alsa-soc进行讨论。
【Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)】下图为更详细的alsa软件体系结构图:
Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

  • ALSA Library API: alsa 用户库接口,常见有 tinyalsa、alsa-lib;
  • ALSA CORE: alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备(Machine/I2S/DMA/CODEC);
  • ASoC CORE: asoc 是建立在标准 alsa core 基础上,为了更好支持嵌入式系统和应用于移动设备的音频 codec 的一套软件体系;
  • Hardware Driver: 音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec。
ALSA 标准是一个先进的 linux 声音体系。它包含内核驱动集合, API 库和工具对 Linux 声音进行支持。 ALSA 包含一系列内核驱动对不同的声卡进行支持,还提供了 libasound 的 API 库。用这些进行写程序不需要打开设备等操作,所以编程人员在写程序的时候不会被底层的东西困扰。与此相反 OSS/Free 驱动在内核层次调用,需要指定设备名和调用 ioctl 。为提供向后兼容, ALSA 提供内核模块模仿 OSS/Free 驱动,所以大多数的程序不需要改动。 ALSA 拥有调用插件的能力对新设备提供扩展,包括那些用软件模拟出来的虚拟设备。 ALSA 还提供一组命令行工具包括 mixer, sound file player 和工具控制一些特别的声卡的特别的作用。
2、分层及特点 ALSA体系主要分为三层,按照调用关系依次是,app、alsa-lib、kernel driver。
ALSA的主要特点如下:
  • 支持多种声卡设备。
  • 模块化的内核驱动程序。
  • 支持SMP和多线程。
  • 提供应用开发函数库以简化应用程序开发。
  • 支持OSS API,兼容OSS应用程序。
ALSA具有更加友好的编程接口,并且完全兼容于OSS,对应用程序来讲无疑是一个更佳地选择。ALSA系统包括驱动包alsa-driver,开发包alsa-libs,开发包插件alsa-libplugins,设置管理工具包alsa-utils,其他声音相关处理小程序包alsa-tools,特殊音频固件支持包alsa-firmware,OSS接口兼容模拟层工具alsa-oss共7个子项目,其中只有驱动包是必须的。
alsa-driver指内核驱动程序,包括硬件相关的代码和一些公共代码,非常庞大。
alsa-libs指用户空间的函数库,提供给应用程序使用,应用程序应包括头文件asoundlib.h。并使用共享库libasound.so。
alsa-utils包含一些基于ALSA的用于控制声卡的应用程序,如alsaconf(侦测系统中声卡并写一个适合的ALSA配置文件),aplay(基于命令行的声音文件播放),arecord(基于命令行的声音文件录制)等。
3、 Kernel Driver层 Kernel driver 层,为内核驱动代码,主要在内核源码中的sound目录下,负责对硬件进行控制与操作。驱动创建的设备文件,在文件系统中的/dev/snd/目录下。注意,应用层使用alsa-API中打开的设备文件,并不是/dev/snd/目录下的文件,而是alsa-lib对设备的再一次封装的产物,叫做plugins,如plughw:0,0 ,后面详细解释。
3.1 ALSA驱动的设备文件结构—字符设备 下面是我的电脑中alsa驱动的设备文件结构:
Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

我们可以看到以下设备文件:
controlC0--> 用于声卡的控制,例如通道选择,混音,麦克风的控制等
midiC0D0--> 用于播放midi音频
pcmC0D0c --> 用于录音的pcm设备
pcmC0D0p --> 用于播放的pcm设备
seq--> 音序器
timer--> 定时器
其中,pcmC0D0p与pcmC0D0c组成一个pcm设备,C0代表0号声卡Card,D0代表0号设备device。C0D0代表的是声卡0中的设备0,pcmC0D0c最后一个c代表capture,pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。从上面的列表可以看出,我的声卡下挂了14个设备,根据声卡的实际能力,驱动实际上可以挂上更多种类的设备,在include/sound/core.h中,定义了以下设备类型:
#define SNDRV_DEV_TOPLEVEL((__force snd_device_type_t) 0) #define SNDRV_DEV_CONTROL((__force snd_device_type_t) 1) #define SNDRV_DEV_LOWLEVEL_PRE((__force snd_device_type_t) 2) #define SNDRV_DEV_LOWLEVEL_NORMAL ((__force snd_device_type_t) 0x1000) #define SNDRV_DEV_PCM((__force snd_device_type_t) 0x1001) #define SNDRV_DEV_RAWMIDI((__force snd_device_type_t) 0x1002) #define SNDRV_DEV_TIMER((__force snd_device_type_t) 0x1003) #define SNDRV_DEV_SEQUENCER((__force snd_device_type_t) 0x1004) #define SNDRV_DEV_HWDEP((__force snd_device_type_t) 0x1005) #define SNDRV_DEV_INFO((__force snd_device_type_t) 0x1006) #define SNDRV_DEV_BUS((__force snd_device_type_t) 0x1007) #define SNDRV_DEV_CODEC((__force snd_device_type_t) 0x1008) #define SNDRV_DEV_JACK((__force snd_device_type_t) 0x1009) #define SNDRV_DEV_LOWLEVEL((__force snd_device_type_t) 0x2000)

通常,我们更关心的是pcm和control这两种设备。
3.2 驱动的代码文件结构 在Linux2.6代码树中,Alsa的代码文件结构如下:
Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

  • core: 该目录包含了ALSA驱动的中间层,它是整个ALSA驱动的核心部分;
  • core/oss: 包含模拟旧的OSS架构的PCM和Mixer模块;
  • core/seq: 有关音序器相关的代码;
  • include: ALSA驱动的公共头文件目录,该目录的头文件需要导出给用户空间的应用程序使用,通常,驱动模块私有的头文件不应放置在这里;
  • drivers: 放置一些与CPU、BUS架构无关的公用代码;
  • i2c: ALSA自己的I2C控制代码;
  • pci: pci声卡的顶层目录,子目录包含各种pci声卡的代码;
  • isa: isa声卡的顶层目录,子目录包含各种isa声卡的代码;
  • soc: 针对system-on-chip体系的中间层代码;
  • soc/codecs: 针对soc体系的各种codec的代码,与平台无关;
3.3 kernel层ASoC框架 在内核中, ALSA依赖ASoC(ALSA System on Chip)驱动模型。ASoC是嵌入式系统使用的音频框架,它从硬件架构的角度来将功能相对独立的硬件单元细分出来,在驱动设备上分为 machine、 platform/CPU和CODEC 三个模块。
Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
文章图片

可以这么理解:一套嵌入式 硬件平台(Machine)包含了平台AP(Platform)和音频CODEC芯片(Codec),对应ASoC的三个设备驱动。这三个设备分别注册各自功能的dev设备,但都是以内核platform设备模型来创建。 ASoC主要代码位于kernel/sound/soc下。 下面分别来介绍一下:
3.3.1 MAchine设备驱动
Machine设备可以看成是一块嵌入式主板(Board) 或者一块声卡。machine设备驱动是ASoC驱动框架的入口, 主要功能是负责platform/cpu和codec之间的连接和控制,或者响应独立于Platform功能和Codec功能之外的特殊音频事件,如平台GPIO控制外置功放等,这些属于machine本身的特定操作代码,都会放到machine驱动里。
Machine设备驱动的主要功能是定义各种DAI(Digital Audio Interface) links,它的作用是把platform/cpu和codec设备驱动连接起来,形成完整的音频通路。
在内核设备树中,平台会定义一个声卡设备,它就是ASoC框架里的machine设备。 machine设备的初始化是整个ASoC驱动的入口。 machine设备的probe()会调用snd_soc_register_card()去注册声卡,然后在snd_soc_instantiate_card()里实例化声卡设备的时候,调用Platform和Codec设备各自的probe(),完成这两个设备的初始化。如果没有错误,那么声卡会注册成功,我们在/dev/snd下可以看到多个音频设备。
3.3.2 platform设备驱动
Platform设备可看作是平台AP(SoC主控,或CPU)。 它负责提供嵌入式平台端的音频功能, 如播放、录音、 Voice通话等。平台驱动中包括音频DMA引擎驱动,数字接口驱动(I2S, AC97, PCM)以及该平台相关的任何音频DSP驱动。此Platform可以重用,在不同的Machine中可以直接重复使用。
Platform设备驱动主要有两个作用:
(1)transfer:负责平台AP端的audio/voice数据流(stream)和DSP之间的传输;
(2)routing:将stream数据流按照特定的路线对应到其他音频模块中。
ASoC会注册多个Platform设备来负责不同功能的音频模块:
(1)负责audio回放/录音
(2)负责voice通话
(3)负责VoIP通话
(4)负责压缩格式的audio播放
(5)负责stream数据流的路线指定
3.3.3 CODEC设备驱动
对于一块嵌入式设备的主板来说,一般会集成一颗音频CODEC芯片。 ASoC架构下的CODEC设备功能和物理CODEC对应, 其在machine的控制下和platform设备连接,负责音频的实际处理,如声音播放(D/A)、声音采集(A/D) 和各种音频control控件的设置。
平台一般会集成一个CODEC单元,也会有添加外部独立CODEC芯片,已达到更好的音质。如Wolfson的WM8998芯片,它是一颗独立的CODEC,基于I2S接口从平台获取音频数据,在其内部经过DAC输出到耳机或speaker。高通有自己的外置CODEC芯片,如WCD9326/9335等,和平台AP的音频数据接口叫Slimbus,其实是和I2S复用的GPIO口。
CODEC芯片可能需要I2C或SPI控制。
COCEC设备支持耳机插拔及按键检测功能。
CODEC设备驱动中定义了大量的mixer、 mux和各种开关的kcontrol,以及DAPM使用的widget和route。
4、ALSA-Lib层 4.1 基本概念
  1. 通道(channel):即我们熟知的声道数。左/右声道,5.1channel等等。
  2. 样本(sample):sample即一次采样的样本,是记录音频数据最基本的单位。通常的sample bit指的是一个channnel上,一次采样的bit数(常见的sample bit 8/16/24/32bits)。
  3. 帧(frame):帧记录了一个声音单元,一个frame是一次采样时所有channel上的sample bit.即frame = channels * (sample bit)。
  4. 采样率(rate):每秒钟采样次数,该次数是针对帧而言。
  5. 周期(period):音频设备一次处理所需要的帧数,对于音频设备的数据访问以及音频数据的存储,都是以此为单位。每当hardware buffer 中有peroid size个frame的空间时,硬件就产生中断,来通知alsa driver来往硬件写数据。
  6. buffer size:hardware buffer size 是由多个peroid组成。buffer size = peroid size * peroids。
    Alsa|Linux ALSA声卡驱动之一(ALSA架构简介)
    文章图片
  7. Data access and layout
    在一个period以内(interleaved和non-interleaved是在一个period里面排),数据是按照channel1排完了再排channel2呢,还是一个frame一个frame的来排(frame在alsa里指的是一次采样时间内,两个channel的数据放一块儿就是一个frame)上图是interleaved。
    交错模式(interleaved):是一种音频数据的记录方式,在交错模式下,数据以连续帧的形式存放,即首先记录完帧1的左声道样本和右声道样本(假设为立体声格式),再开始帧2的记录。而在非交错模式下,首先记录的是一个周期内所有帧的左声道样本,再记录右声道样本,数据是以连续通道的方式存储。不过多数情况下,我们只需要使用交错模式就可以了。
  8. Hardware parameter
    Hardware parameter是作用于声卡硬件的,包括sample rate, sample format, interupt intervals, data access and layout, buffer size。
  9. Software parameter
    software parameter 是作用于alsa core的。通常用来控制When to start the device, what to do about xruns, Available minimum space/data for wakeup,Transfer chunk size。
    (1)When to start the device
    我们可以通过API snd_pcm_sw_params_set_start_threshold来设置什么时候开始启动声卡。对于playback,假设第设置start threshold 为320,那么就是说,当用户调用writei,写入的数据,将暂时存在alsa驱动空间里,当这个数据量达到 320帧时,alsa驱动才开始将数据写入hardware buffer,并启动DA转换。
    (2)What to do about xruns
    xrun指的是,声卡period一到,引发一个中断,告诉alsa驱动,要填入数据,或读走数据,但是,问题在于alsa的读取和写入操作必须用户调用writei和readi才会发生的,它不会去缓存数据。如果上层没有用户调用writei和readi,那么就会产生 overrun(录制时,数据都满了,还没被alsa驱动读走)和underrun(需要数据来播放,alsa驱动却不写入数据),统称为xrun。
    当xrun发生时,可以在空余空间超过stop threshold时,stop audio interface。
    也可以通过设置silence threshold,当空余空间超过silence threshold时,就hardware buffer 写入silence。
    适当地设计应用程序可以最小化xrun并且可以从中恢复过来。
    (3)Available minimum space/data for wakeup
    这个software parameters仅用在interrupt-driven模式。这个模式是alsa驱动层的,不是硬件interrupt。它的意思是,用户使用 snd_pcm_wait()时,这个实际封装的是系统的poll调用,表示用户在等待,那么在等待什么呢?对于playback来讲,就是等待下面的声卡的hardware buffer里有一定数量的空间,可以放入新的数据了,对于record来讲,就是等待下面声卡新采集的数据达到了一定数量了。这个一定数量,就是用 snd_pcm_sw_params_set_avail_min来设置,单位是frame。
    (4)Transfer chunk size
    this determines the number of frames used when transferring data to/from the device hardware buffer.
    这决定了在向设备硬件缓冲区传输数据(或从设备硬件缓冲区取数据)时使用的帧数。
  10. 声音缓存和数据传输
    每个声卡都有一个硬件缓存区来保存记录下来的样本。当缓存区足够满时,声卡将产生一个中断。内核声卡驱动然后使用直接内存(DMA)访问通道将样本传送到内存中的应用程序缓存区。类似地,对于回放,任何应用程序使用DMA将自己的缓存区数据传送到声卡的硬件缓存区中。
    这样硬件缓存区是环缓存。也就是说当数据到达缓存区末尾时将重新回到缓存区的起始位置。ALSA维护一个指针来指向硬件缓存以及应用程序缓存区中数据操作的当前位置。从内核外部看,我们只对应用程序的缓存区感兴趣,所以本文只讨论应用程序缓存区。
    应用程序缓存区的大小可以通过ALSA库函数调用来控制。缓存区可以很大,一次传输操作可能会导致不可接受的延迟,我们把它称为延时(latency)。为了解决这个问题,ALSA将缓存区拆分成一系列周期(period)(OSS/Free中叫片断fragments)。ALSA以period为单元来传送数据。
    一个周期(period)存储一些帧(frames)。每一帧包含时间上一个点所抓取的样本。对于立体声设备,一个帧会包含两个信道上的样本。分解过程:一个缓存区分解成周期,然后是帧,然后是样本。左右信道信息被交替地存储在一个帧内。这称为交错 (interleaved)模式。在非交错模式中,一个信道的所有样本数据存储在另外一个信道的数据之后。
4.2 ALSA-Lib接口 Alsa-lib层,为不同的驱动提供统一的接口alsa API,简化了开发人员对于驱动层的调用开发。主要有如下接口:
网址:https://www.alsa-project.org/alsa-doc/alsa-lib/
The currently designed interfaces are listed below:
  • Information Interface (/proc/asound) 信息接口
  • Control Interface (/dev/snd/controlCX) 控制接口:提供管理声卡注册和请求可用设备的通用功能
  • Mixer Interface (/dev/snd/mixerCXDX) 混音器接口:控制发送信号和控制声音大小的声卡上的设备。
  • PCM Interface (/dev/snd/pcmCXDX) PCM 接口:管理数字音频回放(playback)和录音(capture)的接口。后续重点放在这个接口上,因为它是开发数字音频程序最常用到的接口。
  • Raw MIDI Interface (/dev/snd/midiCXDX) 原始MIDI接口:支持MIDI(Musical Instrument Digital Interface),标准的电子乐器。这些API提供对声卡上MIDI总线的访问。这个原始接口基于MIDI事件工作,由程序员负责管理协议以及时间处理。
  • Sequencer Interface (/dev/snd/seq) 音序器接口:一个比原始 MIDI 接口高级的 MIDI 编程和声音同步高层接口。它可以处理很多的 MIDI 协议和定时器。
  • Timer Interface (/dev/snd/timer) 定时接口:为同步音频事件提供对声卡上时间处理硬件的访问。
我们在应用中,主要使用的是 PCM 接口。如Snd_pcm_open()函数。
除了Alsa-API接口以外,alsa-lib还可以通过配置文件,开放其附加功能,如采样率转换、软件混音等。
我们就是利用alsa-lib的附加功能实现我们的重采样功能,修改的地方主要包括alsa-lib的配置和APP调用两方面。下一小节对如何利用alsa-lib的配置文件开放其采样率转换功能进行描述。
4.3 配置文件asound.conf asound.conf配置文件,是alsa-lib的默认配置文件,路径在 /etc/,可以用来配置alsa库的一些附加功能。这个文件不是alsa库运行时所必须的,没有它alsa库也可以正常运行。
关于asound.conf的配置,可以参考以下文档:
http://www.alsa-project.org/main/index.php/Asoundrc
先阐述一些重要的名词:
  • Card: 声卡,直接对应硬件,ID从0开始计数。
  • Device: 设备,在一个card上,可以有多个device,每个device可以独立被打开和使用,ID从0开始计数。
  • Plugin: 插件,前文说过,应用层调用alsa库时,操作的并不是驱动层创建的设备文件,而是这个plugins,plugin是alsa库对音频处理设备的抽象,hw plugin为硬件设备抽象出的plugin,是最基础的模块,不需要对alsa-lib进行配置即可使用,我们常见的plughw:0,0含义就是类型为hw的plugin,编号声卡0上面的设备0。除了hw类型的plugin外,还有一些纯软件实现的模块,可以用来进行音频处理,例如,可以实现音频采样率转换的rate plugin,可以用来混音的dmix plugin等等。
  • Slave: 从属设备,可以把几个plugin连接起来,sink端的设备就是source端设备的slave。
需要使用这些附加的plugin,就要对配置文件(asound.conf)进行配置,这个配置是实时生效的,所以我们不必修改文件系统中的文件,而是在运行我们的应用程序之前,将自己的配置文件拷贝到/etc/下,对默认的配置文件进行覆盖就行了。
具体如何配置,书写格式,请参看Asoundrc文档。
以下就是我的配置,
pcm_slave.sl2 { pcm "plughw:0,1" rate 48000 }pcm.rate_convert { type rate slave sl2 }

这个配置的含义是,
下面一段:创建一个使用pcm API接口的设备,叫做rate_convert,它的类型是rate(可以实现采样率转换的plugin),它有一个slave叫做sl2;
上面一段:定义一个使用pcm接口的slave,叫做sl2,他实际上是plughw:0,1这个设备的别名,即等同于plughw:0,1(对应我用于播放的AIC3104芯片),这个设备需要的采样率是48KHz。
通过这个配置,就可以在app中,使用pcm API打开rate_convert这个设备,并把解码后的8K采样率的PCM数据,直接使用snd_pcm_writei写入设备,rate_convert这个设备就可以自动将PCM数据重采样至48KHz,然后自动传递给plughw:0,1进行播放。
在看Asoundrc的文档中,一直不明白,为什么在配置文件中,只有输出的采样率配置,而没有输入的采样率配置,要重采样,alsa-lib总得知道把啥转换成48K吧,
在实践中发现,可以通过APP调用alsa-API中的snd_pcm_hw_params_set_rate_near()函数将数据源的采样率为8K传递给alsa-lib。下面一章对app调用alsa-API进行说明。
注意:设备命名
API库使用逻辑设备名而不是设备文件。设备名字可以是真实的硬件名字也可以是插件名字。硬件名字使用hw:i,j这样的格式。其中i是卡号,j是这块声卡上的设备号。
第一个声音设备是hw:0,0.这个别名默认引用第一块声音设备并且在本文示例中一真会被用到。
插件使用另外的唯一名字,比如 plughw:,表示一个插件,这个插件不提供对硬件设备的访问,而是提供像采样率转换这样的软件特性,硬件本身并不支持这样的特性。
5、App调用方法: 在app调用这块儿,其他的通用调用流程在这里就不累述了,使用个项目原来的那套代码就行,只有三块儿需要修改和注意:
  • 1、使用snd_pcm_open()打开的设备文件rate_convert(不需再打开plughw:0,1了),PCM数据的原始采样率,用snd_pcm_hw_params_set_rate_near()对rate_convert进行配置。
  • 2、重要的参数Period_size,即播放周期大小,单位为Byte,需使用snd_pcm_hw_params_set_period_size_near()进行配置,如果period_size配的不对,或者不配置,会发生underRun,播放声音断断续续。
    8K pcm, 配置sample_rate为8000,配置period_size为256
    48K pcm, 配置sample_rate为48000,配置period_size为1024
  • 3、配置负责playback的AIC3104的采样率为48K
6、声卡的缓存和数据的传输: 一块声卡有一个声卡内存用来存储记录的样本。当它被写满时就产生中断。内核驱动就使用 DMA 将数据传输到内存中。同样地,当在播放时就将内存中的声音样本使用 DMA 传到声卡的内存中!
声卡的缓存是环状的,这里只讨论应用程序中的内存结构: ALSA 将数据分成连续的片段然后传到按单元片段传输。
典型的声音程序结构:
open interface for capture or playback set hardware parameters (access mode, data format, channels, rate, etc.) while there is data to be processed: read PCM data (capture) or write PCM data (playback) close interface

7、参考文档与例子 (1)在 ALSA 的文档页面上有两篇为应用程序开发者提供的文章:
ALSA 0.9.0 HOWTO [ http://www.suse.de/~mana/alsa090_howto.html ]
(2)当然,还有音频库API的在线参考: http://www.alsa-project.org/alsa-doc/alsa-lib
(3)音频库API中各个函数的使用说明:http://www.alsa-project.org/alsa-doc/alsa-lib/modules.html
(4)http://www.sabi.co.uk/Notes/linuxSoundALSA.html
(5)a LINUX Journal article about basic ALSA programming 包含一些示例代码
(6)tutorials on the ALSA project web site 包含一些示例代码

    推荐阅读