• Linux ALSA 之二:ALSA 声卡与设备


    一、ALSA Sound 初始化

    1、alsa_sound_init() 入口函数

    在我们创建声卡前,ALSA Sound 会先有相应的初始化,即 sound.c 的入口函数,位于 sound/core/sound.c,主要是用于申请一个字符设备的主设备号(116),后面的 pcmcontrol 等逻辑设备都是这个主设备号下的次设备。

    /*
     *  INIT PART
     */
    
    static int __init alsa_sound_init(void)
    {
    	snd_major = major;
    	snd_ecards_limit = cards_limit;
    
    	//获取字符设备主设备号,即声卡的主设备号,其他声卡设备都是其下的次设备号
    	if (register_chrdev(major, "alsa", &snd_fops)) {
    		pr_err("ALSA core: unable to register native major device number %d\n", major);
    		return -EIO;
    	}
    
    	//创建 snd_proc_root 目录为 /proc/sound
    	if (snd_info_init() < 0) {
    		unregister_chrdev(major, "alsa");
    		return -ENOMEM;
    	}
    #ifndef MODULE
    	pr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
    #endif
    	return 0;
    }
    
    • 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

    其中 struct snd_fops 定义如下,所有次设备共用一个 open 接口,

    static const struct file_operations snd_fops =
    {
    	.owner =	THIS_MODULE,
    	.open =		snd_open,
    	.llseek =	noop_llseek,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    snd_open 函数定义如下:

    static int snd_open(struct inode *inode, struct file *file)
    {
    	//获取声卡下对应的次设备
    	unsigned int minor = iminor(inode);
    	struct snd_minor *mptr = NULL;
    	const struct file_operations *new_fops;
    	int err = 0;
    
    	if (minor >= ARRAY_SIZE(snd_minors))
    		return -ENODEV;
    	mutex_lock(&sound_mutex);
    
    	//获取到具体的声卡设备,即次设备比如 control、pcm 设备等
    	mptr = snd_minors[minor];
    	if (mptr == NULL) {
    		mptr = autoload_device(minor);
    		if (!mptr) {
    			mutex_unlock(&sound_mutex);
    			return -ENODEV;
    		}
    	}
    
    	//获取次设备的 f_ops 文件结构体
    	new_fops = fops_get(mptr->f_ops);
    	mutex_unlock(&sound_mutex);
    	if (!new_fops)
    		return -ENODEV;
    
    	//用次设备的 file_operations 替换
    	replace_fops(file, new_fops);
    
    	//执行该次设备的文件 open 函数
    	if (file->f_op->open)
    		err = file->f_op->open(inode, file);
    	return err;
    }
    
    • 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
    • 34
    • 35
    • 36

    如上述注释所述,在 snd_open 函数中利用次设备号根据全局数组 snd_minors 找到相应的次设备 file_operations 并替换,最后调用相应次设备的 open 函数。(备注:很多设备框架都是使用这种做法)
    snd_minors 是定义在 sound.c 中的全局变量,表示主设备号下的次设备比如 control、pcm 设备等,定义如下:

    struct snd_minor {
    	int type;			/* SNDRV_DEVICE_TYPE_XXX */
    	int card;			/* card number */
    	int device;			/* device number */
    	const struct file_operations *f_ops;	/* file operations */
    	void *private_data;		/* private data for f_ops->open */
    	struct device *dev;		/* device for sysfs */
    	struct snd_card *card_ptr;	/* assigned card instance */
    };
    
    static struct snd_minor *snd_minors[SNDRV_OS_MINORS];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中包含的设备类型如下,位于 include/sound/minors.h

    enum {
    	SNDRV_DEVICE_TYPE_CONTROL,
    	SNDRV_DEVICE_TYPE_SEQUENCER,
    	SNDRV_DEVICE_TYPE_TIMER,
    	SNDRV_DEVICE_TYPE_HWDEP,
    	SNDRV_DEVICE_TYPE_RAWMIDI,
    	SNDRV_DEVICE_TYPE_PCM_PLAYBACK,
    	SNDRV_DEVICE_TYPE_PCM_CAPTURE,
    	SNDRV_DEVICE_TYPE_COMPRESS,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、init_soundcore() 入口函数

    该入口函数是 sound_core.c 的入口函数,主要用于创建 sound_class,如下

    static char *sound_devnode(struct device *dev, umode_t *mode)
    {
    	if (MAJOR(dev->devt) == SOUND_MAJOR)
    		return NULL;
    	return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev));
    }
    
    static int __init init_soundcore(void)
    {
    	int rc;
    
    	rc = init_oss_soundcore();
    	if (rc)
    		return rc;
    	
    	//创建全局 sound_class
    	sound_class = class_create(THIS_MODULE, "sound");
    	if (IS_ERR(sound_class)) {
    		cleanup_oss_soundcore();
    		return PTR_ERR(sound_class);
    	}
    
    	sound_class->devnode = sound_devnode;
    
    	return 0;
    }
    subsys_initcall(init_soundcore);
    
    • 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

    二、声卡结构体与创建、注册

    1、struct snd_card

    结构体 snd_card 是整个 ALSA 音频驱动最顶层的一个结构体,整个声卡的软件逻辑结构开始于该结构,几乎所有与声音相关的逻辑设备都是在 snd_card 的管理之下,声卡驱动的第一个动作就是创建一个 snd_card 结构体,其定位于 include/sound/core.h,如下:

    /* main structure for soundcard */
    
    struct snd_card {
    	int number;			/* number of soundcard (index to
    								snd_cards) */
    
    	char id[16];			/* id string of this card */
    	char driver[16];		/* driver name */
    	char shortname[32];		/* short name of this soundcard */
    	char longname[80];		/* name of this soundcard */ //会在具体驱动中设置,主要反映在/proc/asound/cards中
    	char irq_descr[32];		/* Interrupt description */
    	char mixername[80];		/* mixer name */
    	char components[128];		/* card components delimited with
    								space */
    	struct module *module;		/* top-level module */
    
    	void *private_data;		/* private data for soundcard */ //声卡的私有数据,可以在创建声卡时通过参数指定数据的大小
    	void (*private_free) (struct snd_card *card); /* callback for freeing of
    								private data */
    	struct list_head devices;	/* devices */ //记录该声卡下所有逻辑设备的链表
    
    	struct device ctl_dev;		/* control device */
    	unsigned int last_numid;	/* last used numeric ID */
    	struct rw_semaphore controls_rwsem;	/* controls list lock */
    	rwlock_t ctl_files_rwlock;	/* ctl_files list lock */
    	int controls_count;		/* count of all controls */
    	int user_ctl_count;		/* count of all user controls */
    	struct list_head controls;	/* all controls for this card */ //记录该声卡下所有控制单元的链表
    	struct list_head ctl_files;	/* active control files */ //用于管理该card下的active的control设备
    
    	struct snd_info_entry *proc_root;	/* root for soundcard specific files */
    	struct snd_info_entry *proc_id;	/* the card id */
    	struct proc_dir_entry *proc_root_link;	/* number link to real id */
    
    	struct list_head files_list;	/* all files associated to this card */
    	struct snd_shutdown_f_ops *s_f_ops; /* file operations in the shutdown
    								state */
    	spinlock_t files_lock;		/* lock the files for this card */
    	int shutdown;			/* this card is going down */
    	struct completion *release_completion;
    	struct device *dev;		/* device assigned to this card */ //和card相关的设备
    	struct device card_dev;		/* cardX object for sysfs */ //card用于在sys中显示,用于代表该card
    	const struct attribute_group *dev_groups[4]; /* assigned sysfs attr */
    	bool registered;		/* card_dev is registered? */
    	wait_queue_head_t remove_sleep;
    
    #ifdef CONFIG_PM
    	unsigned int power_state;	/* power state */
    	wait_queue_head_t power_sleep;
    #endif
    
    #if IS_ENABLED(CONFIG_SND_MIXER_OSS)
    	struct snd_mixer_oss *mixer_oss;
    	int mixer_oss_change_count;
    #endif
    };
    
    • 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
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    其中 snd_card 的 driver 字段保存着芯片的 ID 字符串,用户空间的 alsa-lib 会使用到该字符串,所以必须保证该 ID 的唯一性,shortname 字段更多地用于打印信息,longname 字段则会出现在 /proc/asound/cards 中。

    2、声卡创建流程

    1、创建 snd_card 实例

    /**
     *  snd_card_new - create and initialize a soundcard structure
     *  @parent: the parent device object
     *  @idx: card index (address) [0 ... (SNDRV_CARDS-1)]
     *  @xid: card identification (ASCII string)
     *  @module: top level module for locking
     *  @extra_size: allocate this extra size after the main soundcard structure
     *  @card_ret: the pointer to store the created card instance
     *  *  Creates and initializes a soundcard structure.
     *  *  The function allocates snd_card instance via kzalloc with the given
     *  space for the driver to use freely.  The allocated struct is stored
     *  in the given card_ret pointer.
     *  *  Return: Zero if successful or a negative error code.
     */
    int snd_card_new(struct device *parent, int idx, const char *xid,
    		    struct module *module, int extra_size,
    		    struct snd_card **card_ret);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    通过如上描述的 snd_card_new() 函数可以创建一个声卡实例,参数 & 功能描述如上,api 主要 flow 如下,

    snd_card_new
      --> 根据 extra_size 参数大小用 kzalloc 分配一个 snd_card,如果 extra_size > 0 则将 snd_card->private_data 指向 extra_size addr;
        --> 初始化 snd_card 结构体的必要字段,id、idx 及 card->card_dev;
      	  --> snd_ctl_create() [建立逻辑设备:Control]
      	    --> snd_info_card_create(card) [建立 proc 文件中的 info 节点:通常就是 /proc/asound/card0]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    声卡的专用数据主要用于存放一些资源信息,例如中断资源、io资源、dma资源等,根据 extra_size 参数分有两种方式创建 snd_card,

    • 通过 snd_card_create 函数创建 private_data
    // 创建 struct my_chip_priv 结构体
    struct my_chip_priv {
    	...
    };
    
    // 创建 snd_card
    snd_card_new(dev, idx, xid, THIS_MODULE, sizeof(my_chip_priv), &card);
    
    //从 snd_card->private_data 取数据
    struct my_chip_priv *my_chip = card->private_data;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 自己创建 priv_buf,并将 snd_card->private_data 指向它
    // 创建 struct my_chip_priv 结构体(内部成员包含 snd_card)
    struct my_chip_priv {
    	struct snd_card *card;
    	...
    };
    struct snd_card *card;
    struct my_chip_priv *my_chip;
    // 手动创建
    my_chip = kzalloc(sizeof(*chip), GFP_KERNEL);
    // 创建 snd_card
    snd_card_new(dev, idx, xid, THIS_MODULE, 0, &card);
    
    // 专用数据记录snd_card实例
    my_chip->card = card;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2、设置 snd_card 的 Driver ID 和 Name

    strcpy(card->driver, "My Chip");
    strcpy(card->shortname, "My Own Chip 123");
    sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->ioport, chip->irq);
    
    • 1
    • 2
    • 3

    snd_card 的 driver 字段保存着芯片的 ID 字符串,user空间的 alsa-lib 会使用到该字符串,所以必须要保证该 ID 的唯一性。shortname 字段更多地用于打印信息,longname 字段则会出现在 /proc/asound/cards 中。

    3、创建声卡的功能部件(逻辑设备)
    还记得 snd_card 结构体中的 devices 字段吗?在注册声卡的时候会对该 devices 链表中的所有逻辑设备都进行注册,故在注册声卡前需要调用 snd_device_new() 来生成一个 snd_device 实例,并将该实例链接到 snd_card 的 devices 链表中。

    通常,alsa-driver的已经提供了一些常用的部件的创建函数,而不必直接调用snd_device_new(),常见的如下:

    	PCM  ----       snd_pcm_new()
     	RAWMIDI --    	snd_rawmidi_new()
    	CONTROL --   	snd_ctl_create()
    	TIMER   --      snd_timer_new()
    	INFO    --      snd_card_proc_new()
    	JACK    --      snd_jack_new()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    API 详细见下方逻辑设备描述~

    4、注册声卡

    /**
     *  snd_card_register - register the soundcard
     *  @card: soundcard structure
     *
     *  This function registers all the devices assigned to the soundcard.
     *  Until calling this, the ALSA control interface is blocked from the
     *  external accesses.  Thus, you should call this function at the end
     *  of the initialization of the card.
     *
     *  Return: Zero otherwise a negative error code if the registration failed.
     */
    int snd_card_register(struct snd_card *card);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注册声卡时需要 Call snd_card_register() 函数,如注释所述,该 api 内部会 register 所有 devices,关于该 API 的主要 Flow 如下:

    snd_card_register
      --> 【待补充】
    
    • 1
    • 2

    三、声卡之 pcm 设备

    四、声卡之 control 设备

  • 相关阅读:
    leetcode 2366. Minimum Replacements to Sort the Array(数组排序的最少替换数)
    Jenkins实现自动化部署--DevOps学习第四章
    Spring MVC如何进行重定向呢?
    卷积神经网络的发展历史-VGG
    springboot和vue:十二、VueRouter(动态路由)+导航守卫
    实验二 图像的空间域增强
    【模拟电路建模与控制系统分析】01Laplace变换
    Shader for Quest 2: 自定义shader在Unity Editor中可以使用,但是在Quest 2中却不可以
    nginx热备配置
    音乐管理系统
  • 原文地址:https://blog.csdn.net/weixin_45437140/article/details/124996892