Linux ALSA 概述

1. 介绍

ALSA(即Advanced Linux Sound Architecture), 是目前Linux的主流音频体系结构, 提供了音频和MIDI的支持, 其架构图如下所示

TIP: 笔者的代码分析基于linux-4.14.19

2. 初始化

系统启动中ALSA初始化过程如下

alsa_sound_init()
  /* 注册alsa字符设备 */
  register_chrdev(116, "alsa", &snd_fops)
  /* 创建/proc/asound目录及下属version、devices、cards、modules等文件 */
  snd_info_init()
  
const struct file_operations snd_fops =
{
    .owner =    THIS_MODULE,
    .open =     snd_open,
    .llseek =   noop_llseek,
};

从用户空间打开PCM设备过程如下

snd_pcm_open("default", SND_PCM_STREAM_PLAYBACK)  // alsa-lib接口
  open("/dev/snd/controlC0")         // 打开控制设备; 主设备116, 次设备0
  open("/dev/snd/pcmC0D0p")          // 打开PCM设备; 主设备116, 次设备16
    snd_open()                       // 根据主设备号找到该入口
      snd_minors[minor]              // 根据次设备号找到对应操作集
        snd_ctl_f_ops::open()        // 控制设备打开方法
          snd_ctl_open()
        snd_pcm_f_ops::open()        // PCM设备打开方法
          snd_pcm_playback_open()
            snd_lookup_minor_data()  // 根据次设备号查找对应PCM设备(snd_pcm)
            snd_pcm_open()           // 打开PCM播放子流

3. 核心层

核心层为用户空间提供逻辑设备接口, 同时为驱动提供接口来驱动硬件设备, 主要位于sound/core目录下

3.1 数据结构

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

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

各结构体之间主要关系图如下所示

Linux ALSA详解

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.2 接口

该层主要接口如下

/* 创建和初始化声卡结构体 */
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);

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对应文件系统

snd_card_register完成了如下事宜
1. 如果声卡未注册(snd_card::registered), 调用device_add(snd_card::card_dev)将声卡添加到sysfs
2. 调用snd_device_register_all(snd_card)注册该声卡下所有声卡设备(即snd_card::devices链表), 即完成snd_device_register相同的功能
2.1 遍历snd_card::devices链表, 依次调用__snd_device_register注册声卡设备
2.1.1 调用snd_device::snd_device_ops::dev_register注册该设备, 对于Control设备, 即snd_ctl_dev_register; 对于PCM设备, 即snd_pcm_dev_register; 最终则都会调用snd_register_device
2.1.1.1 snd_ctl_dev_register: 调用snd_register_device(snd_ctl_f_ops)注册该Control设备
2.1.1.2 snd_pcm_dev_register: 调用snd_pcm_add将该PCM设备添加至全局PCM链表snd_pcm_devices中, 然后调用snd_register_device(snd_pcm_f_ops)注册该PCM设备
2.1.1.x.1 snd_register_device: 分配snd_minor空间, 设置type、card、device、f_ops、card_ptr等成员变量; 通过snd_find_free_minor找到合适的minor并通过MKDEV(116, minor)创建设备节点, 然后通过device_add向系统添加该设备; 最后将该声卡设备添加至全局声卡主设备的次设备数组snd_minors中
3. 将该声卡放入全局静态声卡数组snd_cards中
4. 调用init_info_for_card()向proc文件系统注册该声卡

snd_pcm_new完成了如下事宜
1. 分配snd_pcm空间, 并设置snd_pcm::card、snd_pcm::device等成员变量
2. 调用snd_pcm_new_stream(SNDRV_PCM_STREAM_PLAYBACK)创建playback_count个子流用于播放
3. 调用snd_pcm_new_stream(SNDRV_PCM_STREAM_CAPTURE)创建capture_count个子流用于录制
4. 调用snd_device_new(SNDRV_DEV_PCM, ops)添加PCM设备

    static struct snd_device_ops ops = {
        .dev_free = snd_pcm_dev_free,
        .dev_register = snd_pcm_dev_register,
        .dev_disconnect = snd_pcm_dev_disconnect,
    };

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值