Linux ALSA 之二:ALSA 声卡与设备

article/2025/10/3 2:55:58

ALSA 声卡与设备

  • 一、ALSA Sound 初始化
    • 1、alsa_sound_init() 入口函数
    • 2、init_soundcore() 入口函数
  • 二、声卡结构体与创建、注册
    • 1、struct snd_card
    • 2、声卡创建流程
    • 3、声卡创建过程使用举例
  • 三、声卡之 Pcm 设备
    • 1、Pcm 设备简介
    • 2、ALSA Driver 的 Pcm 中间层
    • 3、Pcm 设备创建
  • 四、声卡之 Control 设备
    • 1、Control 设备简介
    • 2、Control 设备的建立
    • 3、Controls 的创建

一、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/soundif (snd_info_init() < 0) {unregister_chrdev(major, "alsa");return -ENOMEM;}
#ifndef MODULEpr_info("Advanced Linux Sound Architecture Driver Initialized.\n");
#endifreturn 0;
}

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

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

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;
}

如上述注释所述,在 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];

其中包含的设备类型如下,位于 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,
};

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_classsound_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、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 tosnd_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 withspace */struct module *module;		/* top-level module */void *private_data;		/* private data for soundcard */ //声卡的私有数据,可以在创建声卡时通过参数指定数据的大小void (*private_free) (struct snd_card *card); /* callback for freeing ofprivate 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 shutdownstate */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中显示,用于代表该cardconst struct attribute_group *dev_groups[4]; /* assigned sysfs attr */bool registered;		/* card_dev is registered? */wait_queue_head_t remove_sleep;#ifdef CONFIG_PMunsigned 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
};

其中 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);

通过如上描述的 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_device_new(SNDRV_DEV_CONTROL) # 填充 dev 字段,如 type,state,ops,dev_data=card,并将该 device insert 到 card->devices list 中。--> snd_info_card_create(card) # 建立 proc 文件中的 info 节点:通常就是 /proc/asound/card0

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

  • 1)内部分配,通过 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;
  • 2)外部分配,自己创建 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;

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);

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()

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);

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

snd_card_register--> device_add(&card->card_dev) 创建 /sys/devices 下的设备;--> snd_device_register_all(card) 通过 snd_card 的 devices 链表,遍历所有的 snd_device,并且调用 snd_device 的 ops->dev_register() 来实现格子设备的注册;

经过上述的创建声卡步骤后,声卡的逻辑结构则如下图所示:
声卡的软件逻辑结构

3、声卡创建过程使用举例

/sound/arm/pxa2xx-ac97.c

三、声卡之 Pcm 设备

1、Pcm 设备简介

PCM 是英文 Pulse-code modulation 的缩写,中文译名是脉冲编码调制。PCM就是要把声音从模拟转换成数字信号的一种技术,简单的来说就是利用一个固定的频率对模拟信号进行采样,采样后的信号的幅值按一定的采样精度进行量化,量化后的数值被连续地输出、传输、处理或记录到存储介质中。
在这里插入图片描述
PCM 信号的两个重要指标是采样频率量化精度,目前,CD音频的采样频率通常为 44100 Hz,量化精度是 16bit。通常,播放音乐时,用程序从存储介质中读取音频数据(MP3、WMA、AAC…),经过解码后,最终送到音频驱动程序中的就是PCM数据,反过来,在录音时,音频驱动不停地把采样所得的PCM数据送回给应用程序,由应用程序完成压缩、存储等任务。所以,音频驱动的两大核心任务就是:

  • playback 如何把用户空间的应用程序发过来的PCM数据,转化为人耳可以辨别的模拟音频;
  • capture 把mic拾取到得模拟信号,经过采样、量化,转换为PCM信号送回给用户空间的应用程序。

2、ALSA Driver 的 Pcm 中间层

ALSA 已经实现了 Pcm 中间层,我们的驱动都是调用 pcm 相关 api,基本上只需要实现底层的需要访问硬件的函数即可。

  • <sound/pcm.h> 提供访问 PCM 中间层代码的 API
  • <sound/pcm_params.h> 提供访问一些与 hw_param 相关的函数

前面提到过声卡中挂载着 Pcm Device,Pcm 设备用 snd_pcm 结构体描述,一个 Pcm 实例下面有一个 playback & capture stream,针对 alsa asoc 都是 playback & capture 下各自只有一个 substream.
在这里插入图片描述

3、Pcm 设备创建

/*** snd_pcm_new - create a new PCM instance* @card: the card instance* @id: the id string* @device: the device index (zero based)* @playback_count: the number of substreams for playback* @capture_count: the number of substreams for capture* @rpcm: the pointer to store the new pcm instance** Creates a new PCM instance.** The pcm operators have to be set afterwards to the new instance* via snd_pcm_set_ops().** Return: Zero if successful, or a negative error code on failure.*/
int snd_pcm_new(struct snd_card *card, const char *id, int device,int playback_count, int capture_count, struct snd_pcm **rpcm);
/*** snd_pcm_new_internal - create a new internal PCM instance* @card: the card instance* @id: the id string* @device: the device index (zero based - shared with normal PCMs)* @playback_count: the number of substreams for playback* @capture_count: the number of substreams for capture* @rpcm: the pointer to store the new pcm instance** Creates a new internal PCM instance with no userspace device or procfs* entries. This is used by ASoC Back End PCMs in order to create a PCM that* will only be used internally by kernel drivers. i.e. it cannot be opened* by userspace. It provides existing ASoC components drivers with a substream* and access to any private data.** The pcm operators have to be set afterwards to the new instance* via snd_pcm_set_ops().** Return: Zero if successful, or a negative error code on failure.*/
int snd_pcm_new_internal(struct snd_card *card, const char *id, int device,int playback_count, int capture_count,struct snd_pcm **rpcm)

通过 snd_pcm_new() / snd_pcm_new_internal() 创建 Pcm 设备,如注释所述,internal api 则不会创建 dev 供外部访问,其主要 Flow 如下:

snd_pcm_new() / snd_pcm_new_internal() # 创建 Pcm 设备(是否可供外部访问)--> call _snd_pcm_new(internal = false/true)=> 分配 snd_pcm,填充 snd_pcm 字段,如 card,device,internal=> call snd_pcm_new_stream(SNDRV_PCM_STREAM_PLAYBACK, playback_count)==> 填充 pcm_streams[PLAYBACK]字段,如stream,substream_count==> dev_set_name( pcmC0Dxp )==>d for(substream_count) 分配 substream,填充 substream 字段,如 streams,number,stream等,并将所有的 substream 填充到 snd_pcm->snd_pcm_substream->substream 中。=> snd_pcm_new_stream(SNDRV_PCM_STREAM_CAPTURE) # 同 playback,略=> snd_device_new(SNDRV_DEV_PCM)==> 填充 dev 字段,如type,state,ops,dev_data=snd_pcm,并将该 device insert 到 card->device list 中。【其中根据 internal 差异会选择不同的 snd_dev_ops/internal_ops】

在注册声卡的时候会注册挂在该声卡下所有的 Device,对于 Pcm Device 则会 call 如下函数:

snd_pcm_dev_register()--> call snd_pcm_add(pcm) # 将 pcm 插入到全局链表 snd_pcm_devices--> call snd_register_device(SNDRV_DEVICE_TYPE_PCM_PLAYBACK/CAPTURE)=> 定义 & 分配 snd_minor 并填充字段,如 type,dev,f_ops = snd_pcm_f_ops[0:playback, 1:capture],private_data=snd_pcm(在 open 时会取 private_data)=> 创建 /dev/snd/pcmC0Dxp & pcmC0Dxc 字符设备,并根据 minor 次设备号保存在 snd_minor[minor] 中(如前面所述,在 snd_open 时会根据 minor 拿到这里保存的 snd_minor[minor],并替换 f_ops=snd_pcm_f_ops)至此,Pcm Device 则已经创建。

四、声卡之 Control 设备

1、Control 设备简介

Control 接口主要让用户空间的应用程序(alsa-lib/tinyalsa)可以访问和控制音频 codec 芯片中的多路开关,滑动控件等。常见的 control 控件有 Mixer,Mux,Demux 等。

<sound/control.h> 定义了所有的 Control API. 如果你要为你的codec实现自己的controls,请在代码中包含该头文件。

2、Control 设备的建立

Control设备和PCM设备一样,都属于声卡下的逻辑设备。用户空间的应用程序通过alsa-lib访问该Control设备,读取或控制control的控制状态,从而达到控制音频Codec进行各种Mixer等控制操作。

Control设备的创建过程大体上和PCM设备的创建过程相同。对于创建 Pcm 设备只需要在驱动初始化时主动调用 snd_pcm_new() 函数创建,而 control 设备则用 snd_ctl_create() 创建。不过由于 snd_card_create() 函数中已经会调用 snd_ctl_create() 函数创建 control 设备节点,故我们无需显示地创建 control 设备,只要建立声卡,control 设备则被自动地创建。

和 Pcm Device 一样,在注册声卡的时候会注册挂在该声卡下所有的 Device,对于 Control Device 则会 call 如下函数:

snd_ctl_dev_register()
--> call snd_register_devices(SNDRV_DEVICE_TYPE_CONTROL)=> 定义 & 分配 snd_minor 并填充字段,如 type,device,f_ops=snd_ctl_f_ops,private_data=card(在 open 时会取 private_data)=> 创建 /dev/snd/ControlCx 字符设备,并根据 minor 次设备号保存在 snd_minor[minor] 中(如前面所述,在 snd_open 时会根据 minor 拿到这里保存的 snd_minor[minor],并替换 f_ops=snd_ctl_f_ops)

3、Controls 的创建

当用户空间需要控制某个控件,如 mixer 等,我们必须要将该控件定位为 control,使得用户空间可以访问控制,Controls 的创建步骤如下:
(1)定义一个 snd_kcontrol_new 实例;
(2)通过 snd_ctl_new1() 分配 snd_kcontrol 对象,并将 snd_kcontrol_new 对象相应的值复制到该实例中;
(3)通过 snd_ctl_add(card, kcontrol) 将 snd_kcontrol 添加到 card->controls list 中。

(1)snd_kcontrol_new 的定义
要自定义一个Control,我们首先要定义3各回调函数:info,get 和 put。然后,定义一个 snd_kcontrol_new 结构:

static struct snd_kcontrol_new my_control __devinitdata = {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,.name = "PCM Playback Switch",.index = 0,.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,.private_value = 0xffff,.info = my_control_info,.get = my_control_get,.put = my_control_put
};

iface 字段指出了 control 的类型,alsa定义了几种类型(SNDDRV_CTL_ELEM_IFACE_XXX),常用的类型是 MIXER,当然也可以定义属于全局的 CARD 类型,也可以定义属于某类设备的类型,例如 HWDEP,PCMRAWMIDI,TIMER 等,这时需要在 device 和 subdevice 字段中指出卡的设备逻辑编号。

name 字段是该 control 的名字,从ALSA 0.9.x开始,control 的名字是变得比较重要,因为 control 的作用是按名字来归类的。ALSA 已经预定义了一些 control 的名字,我们在后面的章节中会详细讨论。

index 字段用于保存该 control 的在该卡中的编号。如果声卡中有不止一个 codec,每个 codec 中有相同名字的 control,这时我们可以通过index 来区分这些 controls。当 index 为 0 时,则可以忽略这种区分策略。

access 字段包含了该control的访问类型。每一个 bit 代表一种访问类型,这些访问类型可以多个“或”运算组合在一起。

private_value 字段包含了一个任意的长整数类型值。该值可以通过info,get,put 这几个回调函数访问。你可以自己决定如何使用该字段。例如可以把它拆分成多个位域,又或者是一个指针,指向某一个数据结构。

tlv字段为该control提供元数据。

Control的名字
control 的名字需要遵循一些标准,通常可以分成 3 部分来定义 control 的名字:源–方向–功能。

  • 源:可以理解为该control的输入端,alsa 已经预定义了一些常用的源,例如:Master,PCM,CD,Line等等.。
  • 方向:代表该control的数据流向,例如:Playback,Capture,Bypass,Bypass Capture 等等,也可以不定义方向,这时表示该Control 是双向的( playback 和 capture )。
  • 功能:根据 control 的功能,可以是以下字符串:Switch,Volume,Route 等等。

也有一些命名上的特例:

  • 全局的 capture 和 playback “Capture Source”,“Capture Volume”,“Capture Switch”,它们用于全局的 capture source,switch 和 volume。同理,“Playback Volume”,“Playback Switch”,它们用于全局的输出 switch 和 volume。

  • Tone-controles 音调控制的开关和音量命名为:Tone Control - XXX,例如。“Tone Control - Switch”,“Tone Control - Bass”,“Tone Control - Center”.

  • 3D controls 3D控件的命名规则:“3D Control - Switch”,“3D Control - Center”,“3D Control - Space”.

  • Mic boost 麦克风音量加强控件命名为:“Mic Boost” 或 “Mic Boost(6dB)”.

访问标志(ACCESS Flags)
Access字段是一个 bitmask,它保存了该 control 的访问类型。默认的访问类型是:SNDDRV_CTL_ELEM_ACCESS_READWRITE,表明该 control 支持读和写操作。如果 access 字段没有定义(.access==0),此时也认为是 READWRITE 类型。

如果是一个只读 control,access 应该设置为:SNDDRV_CTL_ELEM_ACCESS_READ,这时,我们不必定义 put 回调函数。类似地,如果是只写 control,access 应该设置为:SNDDRV_CTL_ELEM_ACCESS_WRITE,这时,我们不必定义 get 回调函数。

如果control的值会频繁地改变(例如:电平表),我们可以使用VOLATILE 类型,这意味着该 control 会在没有通知的情况下改变,应用程序应该定时地查询该control的值。

回调函数
info 回调函数用于获取control的详细信息。它的主要工作就是填充通过参数传入的 snd_ctl_elem_info 对象,以下例子是一个具有单个元素的 boolean 型 control 的 info 回调:

static int snd_myctl_mono_info(struct snd_kcontrol *kcontrol,struct snd_ctl_elem_info *uinfo)
{uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;uinfo->count = 1;uinfo->value.integer.min = 0;uinfo->value.integer.max = 1;return 0;
}

type 字段指出该 control 的值类型,值类型可以是 BOOLEAN,INTEGER,ENUMERATED,BYTES,IEC958 和 INTEGER64 之一。
count 字段指出了该 control 中包含有多少个元素单元,比如,立体声的音量 control 左右两个声道的音量值,它的 count 字段等于2。
value 字段是一个联合体(union),value 的内容和 control 的类型有关。其中,boolean 和 integer 类型是相同的。

ENUMERATED 类型有些特殊。它的 value 需要设定一个字符串和字符串的索引,请看以下例子:

static int snd_myctl_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{static char *texts[4] = {"First", "Second", "Third", "Fourth"};uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;uinfo->count = 1;uinfo->value.enumerated.items = 4;if (uinfo->value.enumerated.item > 3)uinfo->value.enumerated.item = 3;strcpy(uinfo->value.enumerated.name,texts[uinfo->value.enumerated.item]);return 0;
}

alsa 已经为我们实现了一些通用的 info 回调函数,例如:snd_ctl_boolean_mono_info(),snd_ctl_boolean_stereo_info() 等等。

get 回调函数用于读取 control 的当前值,并返回给用户空间的应用程序。

static int snd_myctl_get(struct snd_kcontrol *kcontrol,struct snd_ctl_elem_value *ucontrol)
{struct mychip *chip = snd_kcontrol_chip(kcontrol);ucontrol->value.integer.value[0] = get_some_value(chip);return 0;
}

value 字段的赋值依赖于 control 的类型(如同 info 回调)。很多声卡的驱动利用它存储硬件寄存器的地址、bit-shift 和 bit-mask,这时,private_value 字段可以按以下例子进行设置:

private_value = reg | (shift << 16) | (mask << 24);

然后,get 回调函数可以这样实现:

static int snd_sbmixer_get_single(struct snd_kcontrol *kcontrol,struct snd_ctl_elem_value *ucontrol){int reg = kcontrol->private_value & 0xff;int shift = (kcontrol->private_value >> 16) & 0xff;int mask = (kcontrol->private_value >> 24) & 0xff;....//根据以上的值读取相应寄存器的值并填入value中
}

如果 control 的 count 字段大于1,表示 control 有多个元素单元,get回调函数也应该为 value 填充多个数值。

put 回调函数用于把应用程序的控制值设置到 control 中。

static int snd_myctl_put(struct snd_kcontrol *kcontrol,struct snd_ctl_elem_value *ucontrol)
{struct mychip *chip = snd_kcontrol_chip(kcontrol);int changed = 0;if (chip->current_value !=ucontrol->value.integer.value[0]) {change_current_value(chip,ucontrol->value.integer.value[0]);changed = 1;}return changed;
}

如上述例子所示,当 control 的值被改变时,put 回调必须要返回1,如果值没有被改变,则返回0。如果发生了错误,则返回一个负数的错误号。

和 get 回调一样,当 control 的 count 大于1时,put 回调也要处理多个 control 中的元素值。

(2)创建 Controls
当把以上讨论的内容都准备好了以后,我们就可以创建我们自己的control 了。alsa-driver 为我们提供了两个用于创建 control 的API:

snd_ctl_new1()
snd_ctl_add()

我们可以用以下最简单的方式创建 control:

err = snd_ctl_add(card, snd_ctl_new1(&my_control, chip));
if (err < 0)return err;

在这里,my_control 是一个之前定义好的 snd_kcontrol_new 对象,chip 对象将会被赋值在 kcontrol->private_data 字段,该字段可以在回调函数中访问。

snd_ctl_new1() 会分配一个新的 snd_kcontrol 实例,并把 my_control中相应的值复制到该实例中,所以,在定义 my_control 时,通常我们可以加上 __devinitdata 前缀。snd_ctl_add 则把该 control 绑定到声卡对象 card 当中。

snd_ctl_new1(snd_kcontrol_new)
--> 定义 snd_kcontrol,call snd_ctl_new(&snd_control)=> 根据 count 给 snd_kcontrol 分配空间,并填充 kcontrol 参数,如 vd[idx],access,count 等--> 将 snd_kcontrol_new 中相应的值复制到 snd_control.
snd_ctl_add(card, snd_kcontrol)
--> call __snd_ctl_add(card, snd_kcontrol)=> 根据 kcontrol->id find card->controls 是否已经有添加,对于新的 snd_kcontrol 则添加到 card->controls list 中,更新 card->controls_count 等参数.

参考链接:
https://www.cnblogs.com/xinghuo123/category/1786302.html?page=2


http://chatgpt.dhexx.cn/article/yyxxwcyV.shtml

相关文章

ALSA音频编程

一、前序 这里了解一下各个参数的含义以及一些基本概念。 声音是连续模拟量&#xff0c;计算机将它离散化之后用数字表示&#xff0c;就有了以下几个名词术语。 样本长度(sample)&#xff1a;样本是记录音频数据最基本的单位&#xff0c;计算机对每个通道采样量化时数字比特…

alsa 驱动介绍

Machine 以装配有CS4270的一款android 智能电视的为例 /sound/soc/samsung/exynos.c Platform 以Samsung cpu exynos4412为例 /sound/soc/samsung/ Codec 以wolfson的Codec芯片cs4270为例 /sound/soc/codecs/cs4270.c ALSA 框架介绍 Alsa 太多太杂&#xff0c;很难整理的规整&a…

ALSA Configure

0. 前言 本文主要介绍alsa-lib配置文件相关代码的分析内容。 1. 配置文件的路径 在alsa-lib中&#xff0c;函数 snd_config_topdir 用于获取配置文件的路径&#xff0c;有两个方法可以进行配置&#xff1a; 使用环境变量 ALSA_CONFIG_DIR 进行配置。在生成configure时&…

ALSA应用层编程播放音乐

关于ALSA&#xff0c;网上也有介绍&#xff0c;但是我在看的时候看的也是一脸懵逼&#xff0c;不是介绍的不好&#xff0c;是因为我之前对于嵌入式软件这一块实在没什么了解&#xff0c;之前一直学的JAVA&#xff0c;整个体系跟JAVA还是有很大的区别&#xff0c;要学的也完全是…

ALSA系统简析

一 音频架构 如图所示 是 嵌入式系统的音频连接 音频编解码器将数字音频信号 转换成 扬声器播放所需要的模拟声音信号。而通过麦克风时&#xff0c;则执行相反的过程。 数字音频信号通过 PCM技术对模拟信号以某个比特率采样得到的&#xff0c;编解码器的任务就是以支持的PCM…

ALSA编程精华

https://www.cnblogs.com/cslunatic/p/3677729.html 一、前序 这里了解一下各个参数的含义以及一些基本概念。 声音是连续模拟量&#xff0c;计算机将它离散化之后用数字表示&#xff0c;就有了以下几个名词术语。 样本长度(sample)&#xff1a;样本是记录音频数据最基本的…

ALSA学习笔记

文章目录 一、概述二、系统架构三、常用操作命令1、安装ALSA2、查看音频设备3、列出音频设备4、音量控制器 四、常见问题1、cannot open mixer: 没有那个文件或目录 一、概述 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;&#xff0c;高级Linux声音架构的简…

音频ALSA架构简介

一、ALSA架构 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;即高级 Linux 声音架构。 嵌入式移动设备的音频子系统目前主要是ALSA 驱动 asoc 框架&#xff0c;其中包括 codec driver、 platform driver、 machine driver 等。 codec driver只关心 codec 本身…

Linux ALSA 之一:ALSA 架构简介

一、概述 ALSA是 Advanced Linux Sound Architecture 的缩写&#xff0c;目前已经成为了linux的主流音频体系结构。 在 Linux 内核设备驱动层&#xff0c;ALSA 提供了 alsa-driver&#xff0c;在应用层&#xff0c;ALSA 为我们提供了 alsa-lib&#xff0c;故在其支持下&#…

ALSA (高级Linux声音架构)、ASOC基础知识

目录 第一节&#xff1a;什么是ALSA和ASOC 第二节&#xff1a;ALSA框架 第三节&#xff1a;ALSA的使用 第四节&#xff1a;ASOC的硬件框架 第四节&#xff1a;ASOC的软件框架 第一节&#xff1a;什么是ALSA和ASOC ALSA是Advanced Linux Sound Architecture&#xff0c;高级…

动态链接库dlopen的函数的使用

转自&#xff1a;http://blog.const.net.cn/a/17154.htm 编译时候要加入 -ldl (指定dl库) dlopen 基本定义 功能&#xff1a;打开一个动态链接库 [喝小酒的网摘]http://blog.const.net.cn/a/17154.htm 包含头文件&#xff1a; #include <dlfcn.h> 函数定义&#xff…

dlopen的用法

1、前言 为了使程序方便扩展&#xff0c;具备通用性&#xff0c;可以采用插件形式。采用异步事件驱动模型&#xff0c;保证主程序逻辑不变&#xff0c;将各个业务已动态链接库的形式加载进来&#xff0c;这就是所谓的插件。linux提供了加载和处理动态链接库的系统调用&#xff…

织梦上一篇下一篇没有了改为英文

织梦上一篇下一篇没有了改为英文 网站根目录找到 include/arc.archives.class.php 文件 打开找到 上一篇改为 Previous上一篇后面的“没有了” 改为 No Previous原图修改后 接着放下翻&#xff0c;紧贴着下面的“下一篇&#xff0c;没有了” 找到 下一篇改为 在这里插入…

在wordpress文章页中显示上一篇和下一篇文章

查看原文&#xff1a;http://www.hellonet8.com/1162.html 今天博主在看别人博客的时候发现他们的文章末尾会有显示上一篇和下一篇的文章链接&#xff0c;所以也想在自己的博客中添加这个功能。这么做顺便可以增加文章之间的相关性&#xff0c;对搜索引擎的蜘蛛也会友好些。废话…

js 实现 点击上一篇、下一篇功能

列表界面&#xff1a; 详细界面&#xff1a; 思路&#xff1a; 1. 首先目录列表渲染的数据是通过接口调用取到的值&#xff0c;然后点击具体某一条数据的时候&#xff0c;获取到他的 ID&#xff0c;然后通过路由跳转的时候带到详细信息页面。 2. 在详细页面中&#xff0c;先再…

Vue中 实现上一篇下一篇的功能

效果&#xff1a; 看下html页面 <div class"NewsDetails_cont_footer"><!-- 使用三目运算符判断 按钮是否可以点击 --><div click"last" :class"lastNoShow ? noClick : btn"><img src"../assets/img/newsDetail/公共…

java实现上一篇下一篇功能

根据文章类型查询&#xff0c;实现上一篇、下一篇的效果 自定义实体Dto(这里只放出扩展字段) Getter Setter public class OsArticleDto extends BaseDto {/** */private static final long serialVersionUID 1L;/** 上一篇文章id*/private String beforeId;/*** 上一篇文章…

vue实现上一篇下一篇

先来看一下效果图 请求回来所有文章&#xff0c;根据索引进行上一篇下一篇的判断 首先为两个按钮绑定点击事件 <buttonclick"last(lastId, columnId)":disabled"isLast":class"{ disClick: isLast true }">上一篇:{{ lastTitle }}</b…

Django针对上一篇和下一篇文章标题的实现逻辑

1、要求显示效果 2、前端html内容 <div><nav aria-label"..."><ul class"pager"><li><a href"/blog/detail/{{ previous_article.article_id }}">上一篇&#xff1a;{{ previous_article.title }}</a><…

程序员,我们应该如何去学习

IT技术的发展日新月异&#xff0c;新技术层出不穷&#xff0c;具有良好的学习能力&#xff0c;能及时获取新知识、随时补充和丰富自己&#xff0c;已成为程序员职业发展的核心竞争力。本文中&#xff0c;作者结合多年的学习经验总结出了提高程序员学习能力的三个要点。 众所周知…