• 了解ALSA架构

1.Advanced Linux Sound Architecture (ALSA)

  The Advanced Linux Sound Architecture (ALSA) subsystem provides audio and MIDI capabilities to Linux systems, including a user space library to simplify application programming (alsa-lib) and support for the older Open Sound System (OSS) architecture through legacy compatibility modes. Specifically for system-on-chips, the architecture defines an ALSA system-on-chip (ASoC) layer which provides optimized support for embedded devices.

  ALSA 是一个完全开放源码的音频驱动程序集,是由志愿者维护的开源项目,而 OSS 则是由公司提供的商业产品。ALSA 系统包括驱动包alsa-driver(集成在内核源码),开发包 alsa-libs,开发包插件 alsalibplugins,设置管理工具包 alsa-utils,其他声音相关处理小程序包alsa-tools,特殊音频固件支持包 alsa-firmware,OSS 接口兼容模拟层工具 alsa-oss 共 7 个子项目,其中只有 alsa-driver 是必须的。除了 alsa-driver,ALSA 包含在用户空间的 alsa-lib 函数库,具有更加友好的编程接口,并且完全兼容于 OSS,开发者可以通过这些高级 API 使用驱动,不必直接与内核驱动 API 进行交互。

ALSA 主要有如下特点:

  • 支持多种声卡设备、
  • 模块化的内核驱动程序 、
  • 支持 SMP(对称多处理)和多线程、
  • 提供应用开发函数库
  • 兼容OSS应用程序

2.ALSA框架
在这里插入图片描述

  • User空间:主要由Alsa Libray API对应用程序提供统一的API接口,各个APP应用程序只要调用 alsa-lib 提供的 API接口来实现放音、录音、控制。现在提供了两套基本的库,tinyalsa是一个简化的alsa-lib库,现在Android的系统中主要使用它。

  • Kernel空间:

    • ASOC Core:是 ALSA 的标准框架,是 ALSA-driver 的核心部分,提供了各种音频设备驱动的通用方法和数据结构,为 Audio driver提供 ALSA Driver API

    • ALSA CORE:alsa 核心层,向上提供逻辑设备(PCM/CTL/MIDI/TIMER/…)系统调用,向下驱动硬件设备。

    • Hardware Driver:音频硬件设备驱动,由三大部分组成,分别是 Machine、Platform、Codec,提供的 ALSA Driver API 和相应音频设备的初始化及工作流程,实现具体的功能组件,这也是驱动开发人员需要具体实现的部分;

      • Codec类: Codec即编解码芯片的驱动,此Codec驱动是和平台无关,包含的功能有: 音频的控制接口,音频读写IO接口,以及DAPM的定义等。如果需要的话,此Codec类可以在BT,FM,MODEM模块中不做修改的使用。因此Codec就是一个可重复使用的模块,同一个Codec在不同的SOC中可以使用。

      • Platform类: 可以理解为某款SOC平台,平台驱动中包括音频DMA引擎驱动,数字接口驱动(I2S, AC97, PCM)以及该平台相关的任何音频DSP驱动。同样此Platform也可以重用,在不同的Machine中可以直接重复使用。

      • Machine类: Machine可以理解为是一个桥梁,用于在Codec和Platform之间建立联系。此Machine指定了该机器使用那个Platform,那个Codec,最终会通过Machine建立两者之间的联系。

2.1.Hardware Driver三者关系:
在这里插入图片描述

  • Platform
    指某款soc平台的音频模块,比如qcom,omap,amlogic,atml等等。platform又可细分为二个部分:

    • cpu dai:在嵌入式系统里面通常指soc的i2s,pcm总线控制器,负责把音频数据从I2S tx FIFO搬运到codec(playback,capture则相反)。cpu_dai通过 snd_soc_register_dai()来注册。注:DAI是Digital Audio Interface的简称,分为cpu_dai和codec_dai,这两者通过i2s/pcm总线连接;AIF是Audio Interface母的简称,嵌入式系统中一般是I2S和PCM接口。
    • PCM dma:负责把dma buffer中的音频数据搬运到i2s tx fifo。值得留意的是:某些情形下是不需要dma操作的,比如modem和codec直连,因为modem本身已经把数据送到fifo了,这时只需要启动codec_dai接收数据即可;该情形下,machine驱动dai_link中需要设定.platform_name = “snd_soc_dummy”,这是虚拟dma驱动,实现见sound/soc/soc-utils.c. 音频dma驱动通过 snd_soc_register_platform()来注册,故也常用platform来指代音频dma驱动(这里的platform需要与soc platfrom区分开)。
  • Codec:对于回放来说,userspace送过来的音频数据是经过采样量化的数字信号,在codec经过DAC转换成模拟信号然后输出到外放或耳机,这样你就可以听到声音了,codec字面意思是编解码器,但芯片(codec)里面的功能部件很多,常见的有AIF,DAC,ADC,Mixer,PGA,line-in,line-out,有些高端的codec芯片还有EQ,DSP,SRC,DRC,AGC,Echo-Canceller,Noise-Suppression等部件。

  • Machine:指某款机器,通过配置dai_link把cpu_dai,codec_dai,modem_dai各个音频接口给链结成一条条音频链路,然后注册snd_soc_card.和上面两个不一样,platform和codec驱动一般是可以重用的,而machine有它特定的硬件特性,几乎是不可重用的。所谓的硬件特性指:Soc Platform与Codec的差异;DAIs之间的链结方式;通过某个GPIO打开Amplifier;通过某个GPIO检测耳机插拔;使用某个时钟如MCLK/External-OSC作为i2s,CODEC的时钟源等等。

2.2.以s3c24xx为例:

Machine:
    s3c24xx_uda134x.c
    该文件是设备模型建立要执行的第一个文件。它联系了以上三个 文 件,导致了以 上三文件中的初始化函数的调用执行。填充了结构体 s3c24xx_uda134x_ops, s3c24xx_uda134x_dai_link,snd_soc_s3c24xx_uda134x, s3c24xx_uda134x, s3c24xx_uda134x_snd_devdata。添加了平台设备 s3c24xx_uda134x_snd_device到内 核。注册了与平台设备"s3c24xx_uda134x"(移植时需要手动添加,还需要添加的是该设 备的platform_data)相匹配 的驱动 s3c24xx_uda134x_driver。   
    
Platform:
    s3c24xx-i2s.c  :该文件主要实现了配置cpu上iis接口寄存器的一些操作函数,填充了结构 体 s3c24xx_i2s_dai。
    dma.c 

Codec:
    uda134x.c 
    该文件主要实现了对编解码芯片uda1341寄存器的设置,声音调节,静音设 置等操 作函数。填 充了  结  构 体 uda134x_dai。该文件还实现了一些重要的初始化,比如 创 建结构体类型为snd_card的card实例,创建pcm实例等。

3.ALSA核心层

  核心层为用户空间提供逻辑设备接口, 同时为驱动提供接口来驱动硬件设备, 主要位于sound/core目录下。
在这里插入图片描述
分析是谁调用snd_register_device_for_dev函数来注册sound设备的。
在这里插入图片描述

3.1.字符设备注册

sound/core/sound.c:
static int __init alsa_sound_init(void)  
{  
    snd_major = major;  
    snd_ecards_limit = cards_limit;  
    if (register_chrdev(major, "alsa", &snd_fops)) {  
        snd_printk(KERN_ERR "unable to register native major device number %d/n", major);  
        return -EIO;  
    }  
    if (snd_info_init() < 0) {  
        unregister_chrdev(major, "alsa");  
        return -ENOMEM;  
    }  
    snd_info_minor_register();  
    return 0;  
}  

  注册了主设备号为116的字符设备文件,文件操作是snd_fops,snd_open接口比较重要,snd_open接口通过文件节点inode得到了次设备号,通过snd_minors数组得到对应的声音逻辑设备的文件操作,调用对应的open接口,并调用fops_put替换成了对应的逻辑设备的文件操作(snd_minors里维护)。

  snd_info_init创建proc目录下的asound,并在该目录下分别建立version EVM card0 cards device pcm timers,用来查看相关的信息。

static const struct file_operations snd_fops =
{
	.owner =	THIS_MODULE,
	.open =		snd_open,
	.llseek =	noop_llseek,
}

alsa驱动设备文件结构:

$ cd /dev/snd
$ ls -l

crw-rw----+ 1 root audio 116, 8 2011-02-23 21:38 controlC0
crw-rw----+ 1 root audio 116, 4 2011-02-23 21:38 midiC0D0
crw-rw----+ 1 root audio 116, 7 2011-02-23 21:39 pcmC0D0c
crw-rw----+ 1 root audio 116, 6 2011-02-23 21:56 pcmC0D0p
crw-rw----+ 1 root audio 116, 5 2011-02-23 21:38 pcmC0D1p
crw-rw----+ 1 root audio 116, 3 2011-02-23 21:38 seq
crw-rw----+ 1 root audio 116, 2 2011-02-23 21:38 timer
  • controlC0 :用于声卡的控制,例如通道选择,混音,麦克风的控制等
  • midiC0D0:用于播放midi音频
  • pcmC0D0c:用于录音的pcm设备
  • pcmC0D0p:用于播放的pcm设备
  • seq :音序器
  • timer :定时器

  其中, C0D0代表的是声卡0中的设备0, pcmC0D0c最后一个c代表capture, pcmC0D0p最后一个p代表playback,这些都是alsa-driver中的命名规则。

/proc/asound

dr-xr-xr-x 6 root root 0 Oct  8 00:04 card0
-r–r–r– 1 root root 0 Oct  8 00:04 cards
-r–r–r– 1 root root 0 Oct  8 00:04 devices
-r–r–r– 1 root root 0 Oct  8 00:04 modules
dr-xr-xr-x 2 root root 0 Oct  8 00:04 oss
-r–r–r– 1 root root 0 Oct  8 00:04 pcm
dr-xr-xr-x 2 root root 0 Oct  8 00:04 seq
-r–r–r– 1 root root 0 Oct  8 00:04 timers
-r–r–r– 1 root root 0 Oct  8 00:04 version
  • cards : 可显示系统中存在多少个声卡
  • card0 : 代表某个声卡
  • devices : 可显示系统中存在多少个逻辑设备

/proc/asound/card0:该节点提供该card的一些info.

-r–r–r– 1 root root 0 Oct  7 19:57 audiopci
dr-xr-xr-x 2 root root 0 Oct  7 19:57 codec97#0
-r–r–r– 1 root root 0 Oct  7 19:57 id
-r–r–r– 1 root root 0 Oct  7 19:57 midi0
dr-xr-xr-x 3 root root 0 Oct  7 19:57 pcm0c
dr-xr-xr-x 3 root root 0 Oct  7 19:57 pcm0p
dr-xr-xr-x 3 root root 0 Oct  7 19:57 pcm1p

3.2 数据结构
在这里插入图片描述

该层包含的主要数据结构包括:

  • snd_card 表示一个声卡实例, 包含多个声卡设备
  • snd_device 表示一个声卡设备部件
  • snd_pcm 表示一个PCM设备, 声卡设备的一种, 用于播放和录音
  • snd_control 表示Control设备, 声卡设备的一种, 用于控制声卡
  • snd_pcm_str 表示PCM流, 分为playback和capture
  • snd_pcm_substream PCM子流, 用于音频的播放或录制
  • snd_pcm_ops PCM流操作集

在这里插入图片描述

snd_card主要字段如下:

struct snd_card {
    int number;             /* 索引 */
    char id[16];            /* 标识符 */

    char driver[16];        /* 驱动名称 */
    char shortname[32];     /* 短名 */
    char longname[80];      /* 名字 */

    void *private_data;     /* 声卡私有数据*/
    void (*private_free) (struct snd_card *); /* 私有数据释放回调 */

    struct list_head devices;     /* 该声卡下所有设备*/
    struct list_head controls;    /* 该声卡下所有控制设备*/

    struct list_head files_list;  /* 声卡管理文件 */
    struct device *dev;           /* 声卡相关的device */
    struct device card_dev;       /* 用于sysfs, 代表该声卡 */
    bool registered;              /* 是否注册标记 */
};

snd_device主要字段如下:

struct snd_device {
    struct list_head list;        /* 所有注册的声卡设备链表 */
    struct snd_card *card;        /* 设备所属声卡 */
    enum snd_device_state state;  /* 设备状态*/
    enum snd_device_type type;    /* 设备类型*/
    void *device_data;            /* 指向具体的声卡设备, 如snd_pcm */
    struct snd_device_ops *ops;   /* 设备操作集*/
};

snd_pcm主要字段如下:

struct snd_pcm {
    struct snd_card *card;   /* 该PCM设备所属声卡*/
    struct list_head list;   /* 所有注册的PCM设备链表 */
    int device;              /* PCM索引 */
    unsigned int info_flags; /* SNDRV_PCM_INFO_ */
    char id[64];             /* PCM设备标识 */
    char name[80];           /* PCM设备名 */
    struct snd_pcm_str streams[2];  /* 指向PCM设备的capture(1)和playback(0)流 */
    void *private_data;      /* PCM设备私有数据*/
    void (*private_free) (struct snd_pcm *); /* 私有数据释放回调 */
};

3.3 APIs

该层主要接口如下:

/* 创建和初始化声卡结构体 */
int snd_card_new(struct device *parent, int idx, const char *xid, struct module *, int extra_size, struct snd_card **card_ret);
/* 释放声卡结构体 */
int snd_card_free(struct snd_card * card);
/* 注册声卡 */
int snd_card_register(struct snd_card * card);

/* 创建声卡设备部件, 通常由snd_pcm_new和snd_card_new自动完成 */
int snd_device_new(struct snd_card *, enum snd_device_type type, void *device_data, struct snd_device_ops *ops);
/* 注册声卡设备部件, 通常由snd_card_register自动完成 */
int snd_device_register(struct snd_card *card, void *device_data);

/* 创建PCM设备 */
int snd_pcm_new(struct snd_card *, const char *id, int device, int playback_count, int capture_count, struct snd_pcm **rpcm);
/* 创建PCM流, 通常snd_pcm_new会自动创建capture和playback两个PCM流 */
int snd_pcm_new_stream(struct snd_pcm * pcm, int stream, int substream_count);
/* 设置PCM设备操作集 */
void snd_pcm_set_ops(struct snd_pcm *, int direction, const struct snd_pcm_ops *ops);

4.card的创建与注册

  struct snd_card可以说是整个ALSA音频驱动最顶层的一个结构, 整个声卡的软件逻辑结构开始于该结构, 几乎所有与声音相关的逻辑设备都是在snd_card的管理之下, 声卡驱动的第一个动作通常就是创建一个snd_card结构体。

4.1.snd_card_new

1. 分配snd_card+extra_size空间大小
2. 如果extra_size大于0,将private_data指向extra_size所在首地址
3. 如果指定了xid, 将其拷贝至snd_card::id中, 即声卡标识符
4. 根据idx获取可用的声卡索引并赋值给snd_card::number
5. 分别将parent、module赋值给snd_card::dev、snd_card::module
6. 初始化链表snd_card::devices、snd_card::controls、snd_card::ctl_files、snd_card::files_list
7. 调用device_initialize()初始化snd_card::card_dev, 并设置snd_card::card_dev相关成员变量, 用于sysfs
8. 调用snd_ctl_create()创建控制接口
8.1 调用snd_device_initialize初始化snd_card::ctl_dev, 并设置相关成员变量, 用于sysfs
8.2 调用snd_device_new(SNDRV_DEV_CONTROL, ops)创建声卡控制设备部件
	static struct snd_device_ops ops = {
		.dev_free = snd_ctl_dev_free,
		.dev_register =	snd_ctl_dev_register,
		.dev_disconnect = snd_ctl_dev_disconnect,
    };
9. 调用snd_info_card_create()创建proc对应文件系统

4.2.snd_card_register

  函数的主要目的是注册card下面挂载的所有device; 另外只有该函数成功返回后, 用户空间才能通过control interface来访问底层。
在这里插入图片描述
4.3.struct snd_device

  一个struct snd_device用于描述一个逻辑设备,每个逻辑设备都有一个对应的snd_device_ops, 我们在新增一个逻辑设备时, 需要为此设备准备好该数据结构。

4.3.1.snd_device_new

  该API用于创建一个逻辑设备, 主要内容是分配一个struct snd_device空间, 初始化相关字段, 并把该逻辑设备添加到card->devices链表下。注意在添加新设备到card->devices链表时, 采用的是有序插入方式: type小的在前、type大的在后。

4.3.2.snd_device_register

  一般在注册声卡时(snd_card_register)会自动调用此处的API; 不过也可以在card注册完毕后, 在手动调用此API注册一个新的逻辑设备。

  该API的实现很简单: 首先回调snd_device_ops->dev_register, 然后把核心层的状态标记为SNDRV_DEV_REGISTERED。

refer to

  • https://www.cnblogs.com/hzl6255/p/9979377.html
  • https://cloud.tencent.com/developer/article/1603864
  • https://cloud.tencent.com/developer/article/1541259
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐