Linux音频ALSA机制学习笔记<一>

首先宏观看内核暴露给上层的接口:

[email protected]:/ # cat /dev/snd/

controlC0 pcmC0D10p pcmC0D13c pcmC0D15c pcmC0D2c pcmC0D3c pcmC0D5p pcmC0D8c

pcmC0D0c pcmC0D11p pcmC0D13p pcmC0D15p pcmC0D2p pcmC0D3p pcmC0D6c pcmC0D9c

pcmC0D0p pcmC0D12c pcmC0D14c pcmC0D1c pcmC0D31c pcmC0D4p pcmC0D6p pcmC0D9p

pcmC0D10c pcmC0D12p pcmC0D14p pcmC0D1p pcmC0D32p pcmC0D5c pcmC0D7p timer

主要由control与许多pcm设备组成,其中控制类control接口是通过get与put来实现上层与内核的交互的;

而pcm接口主要是实现音频数据流的,其组成C0表示0号声卡,最后面的c表示capture p表示palyback,D后面表示pcm设备号。

为什么pcm有这么多的设备号?原因是dsp底层通道不一样。必须在我们平台上audio speaker 打开 pcmC0D14p,audio handset 打开

pcmC0D12p。为什么设备号0-32有的没有?原因是有的前端dsi links定义了.no_pcm = 1这样就不会注册pcm。

static struct snd_soc_dai_link msm_dai[] = {

/* FrontEnd DAI Links */

{

.name = "MSM8960 Media1",

.stream_name = "MultiMedia1",

.cpu_dai_name = "MultiMedia1",

.platform_name = "msm-pcm-dsp",

.dynamic = 1,

.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},

.codec_dai_name = "snd-soc-dummy-dai",

.codec_name = "snd-soc-dummy",

.ignore_suspend = 1,

.ignore_pmdown_time = 1, /* this dainlink has playback support */

.be_id = MSM_FRONTEND_DAI_MULTIMEDIA1

},

-----------

{

.name = LPASS_BE_SLIMBUS_0_TX,

.stream_name = "Slimbus Capture",

.cpu_dai_name = "msm-dai-q6.16385",

.platform_name = "msm-pcm-routing",

.codec_name = "tabla_codec",

.codec_dai_name = "tabla_tx1",

.no_pcm = 1,

.be_id = MSM_BACKEND_DAI_SLIMBUS_0_TX,

.be_hw_params_fixup = msm_slim_0_tx_be_hw_params_fixup,

.ops = &msm_be_ops,

},

-----------

};

注意在 snd_soc_dai_link 中 的platform_name,最终pcm数据流是通过 platform_name对应的平台驱动将数据流发送到最底层的。在平台驱

动需要实现pcm数据流接口:

static struct snd_pcm_ops msm_pcm_ops = {

.open = msm_pcm_open,

.copy = msm_pcm_copy,

.hw_params = msm_pcm_hw_params,

.close = msm_pcm_close,

.ioctl = snd_pcm_lib_ioctl,

.prepare = msm_pcm_prepare,

.trigger = msm_pcm_trigger,

.pointer = msm_pcm_pointer,

.mmap = msm_pcm_mmap,

};

下面重点说kcontrol以及如何与上层交互:

<一>直接注册kcontrol,这类kcontrol重点是实现更改寄存器的值:

static const struct snd_kcontrol_new tabla_snd_controls[] = {

-------------

SOC_ENUM_EXT("EAR PA Gain", tabla_ear_pa_gain_enum[0],

tabla_pa_gain_get, tabla_pa_gain_put),

SOC_SINGLE_TLV("LINEOUT1 Volume", TABLA_A_RX_LINE_1_GAIN, 0, 12, 1,

line_gain),

--------------

};

static struct snd_soc_codec_driver soc_codec_dev_tabla = {

--------

.controls = tabla_snd_controls,

.num_controls = ARRAY_SIZE(tabla_snd_controls),

---------

};

通过 snd_soc_add_controls完成注册:

static int snd_soc_add_controls(struct snd_card *card, struct device *dev,

const struct snd_kcontrol_new *controls, int num_controls,

const char *prefix, void *data)

{

int err, i;

for (i = 0; i < num_controls; i++) {

const struct snd_kcontrol_new *control = &controls[i];

err = snd_ctl_add(card, snd_soc_cnew(control, data,

control->name, prefix));

if (err < 0) {

dev_err(dev, "Failed to add %s: %d\n", control->name, err);

return err;

}

}

return 0;

}

<二>在widget中注册kcontrol,这类control重点是提供widget与path的桥梁来完成音频通路的切换:

static const struct snd_soc_dapm_widget tabla_dapm_widgets[] = {

--------

SND_SOC_DAPM_MIXER("DAC1", SND_SOC_NOPM, 0, 0, dac1_switch,

ARRAY_SIZE(dac1_switch)),

--------

SND_SOC_DAPM_MUX("SLIM RX1 MUX", SND_SOC_NOPM, TABLA_RX1, 0,

&slim_rx_mux[TABLA_RX1]),

}

#define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \

wcontrols, wncontrols)\

{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \

.invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}

static const struct snd_kcontrol_new dac1_switch[] = {

SOC_DAPM_SINGLE("Switch", TABLA_A_RX_EAR_EN, 5, 1, 0)

};

widget里面的get与put接口通常是现成的,不需要自己填充。

/* dapm kcontrol types */

#define SOC_DAPM_SINGLE(xname, reg, shift, max, invert) \

{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \

.info = snd_soc_info_volsw, \

.get = snd_soc_dapm_get_volsw, .put = snd_soc_dapm_put_volsw, \

.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }

通过snd_soc_dapm_new_widgets来完成注册的,会遍历 card里面的widget链表(这个链表是在之前widget注册时将widget全部放进来的),

只有当widget里面的num_kcontrols定义的情况下才会生成new widget。

int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm)

{

struct snd_soc_dapm_widget *w;

unsigned int val;

mutex_lock_nested(&dapm->card->dapm_mutex, SND_SOC_DAPM_CLASS_INIT);

list_for_each_entry(w, &dapm->card->widgets, list)

{

if (w->new)continue;

if (w->num_kcontrols) {

w->kcontrols = kzalloc(w->num_kcontrols *

sizeof(struct snd_kcontrol *),

GFP_KERNEL);

if (!w->kcontrols) {

mutex_unlock(&dapm->card->dapm_mutex);

return -ENOMEM;

}

}

switch(w->id) {

case snd_soc_dapm_switch:

case snd_soc_dapm_mixer:

case snd_soc_dapm_mixer_named_ctl:

dapm_new_mixer(w);

break;

case snd_soc_dapm_mux:

case snd_soc_dapm_virt_mux:

case snd_soc_dapm_value_mux:

dapm_new_mux(w);

break;

case snd_soc_dapm_pga:

case snd_soc_dapm_out_drv:

dapm_new_pga(w);

break;

default:

break;

}

/* Read the initial power state from the device */

if (w->reg >= 0) {

val = soc_widget_read(w, w->reg);

val &= 1 << w->shift;

if (w->invert)

val = !val;

if (val)

w->power = 1;

}

w->new = 1;

dapm_mark_dirty(w, "new widget");

dapm_debugfs_add_widget(w);

}

dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP);

mutex_unlock(&dapm->card->dapm_mutex);

return 0;

}

以 mixer类的control为例,唯独mixer类的control的name是下面的long_name 由当前widget name + kcontrol name组成。而mux类control

的name还是wigdet的名字。

/* create new dapm mixer control */

static int dapm_new_mixer(struct snd_soc_dapm_widget *w)

{

struct snd_soc_dapm_context *dapm = w->dapm;

int i, ret = 0;

size_t name_len, prefix_len;

struct snd_soc_dapm_path *path;

struct snd_card *card = dapm->card->snd_card;

const char *prefix;

struct snd_soc_dapm_widget_list *wlist;

size_t wlistsize;

if (dapm->codec)

prefix = dapm->codec->name_prefix;

else

prefix = NULL;

if (prefix)

prefix_len = strlen(prefix) + 1;

else

prefix_len = 0;

/* add kcontrol */

for (i = 0; i < w->num_kcontrols; i++) {

/* match name */

list_for_each_entry(path, &w->sources, list_sink) {

/* mixer/mux paths name must match control name */

if (path->name != (char *)w->kcontrol_news[i].name)

continue;

if (w->kcontrols[i]) {

path->kcontrol = w->kcontrols[i];

continue;

}

wlistsize = sizeof(struct snd_soc_dapm_widget_list) +

sizeof(struct snd_soc_dapm_widget *),

wlist = kzalloc(wlistsize, GFP_KERNEL);

if (wlist == NULL) {

dev_err(dapm->dev,

"asoc: can‘t allocate widget list for %s\n",

w->name);

return -ENOMEM;

}

wlist->num_widgets = 1;

wlist->widgets[0] = w;

/* add dapm control with long name.

* for dapm_mixer this is the concatenation of the

* mixer and kcontrol name.

* for dapm_mixer_named_ctl this is simply the

* kcontrol name.

*/

name_len = strlen(w->kcontrol_news[i].name) + 1;

if (w->id != snd_soc_dapm_mixer_named_ctl)

name_len += 1 + strlen(w->name);

path->long_name = kmalloc(name_len, GFP_KERNEL);

if (path->long_name == NULL) {

kfree(wlist);

return -ENOMEM;

}

switch (w->id) {

default:

/* The control will get a prefix from

* the control creation process but

* we‘re also using the same prefix

* for widgets so cut the prefix off

* the front of the widget name.

*/

snprintf(path->long_name, name_len, "%s %s",

w->name + prefix_len,

w->kcontrol_news[i].name);

break;

case snd_soc_dapm_mixer_named_ctl:

snprintf(path->long_name, name_len, "%s",

w->kcontrol_news[i].name);

break;

}

path->long_name[name_len - 1] = ‘\0‘;

path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i],

wlist, path->long_name,

prefix);

ret = snd_ctl_add(card, path->kcontrol);

if (ret < 0) {

dev_err(dapm->dev,

"asoc: failed to add dapm kcontrol %s: %d\n",

path->long_name, ret);

kfree(wlist);

kfree(path->long_name);

path->long_name = NULL;

return ret;

}

w->kcontrols[i] = path->kcontrol;

}

}r

eturn ret;

}

<三>kcontrol统一最后注册接口snd_ctl_add:

每注册一个kcontrol, numid就会加一,而上层调用kcontrol也是通过 numid来识别的。

int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)

{

struct snd_ctl_elem_id id;

unsigned int idx;

int err = -EINVAL;

if (! kcontrol)

return err;

if (snd_BUG_ON(!card || !kcontrol->info))

goto error;

id = kcontrol->id;

down_write(&card->controls_rwsem);

if (snd_ctl_find_id(card, &id)) {

up_write(&card->controls_rwsem);

snd_printd(KERN_ERR "control %i:%i:%i:%s:%i is already present\n",

id.iface,

id.device,

id.subdevice,

id.name,

id.index);

err = -EBUSY;

goto error;

} if (

snd_ctl_find_hole(card, kcontrol->count) <

0) {

up_write(&card->controls_rwsem);

err = -ENOMEM;

goto error;

} list_add_tail(&kcontrol->list, &

card->controls);

card->controls_count += kcontrol->count;

kcontrol->id.numid = card->last_numid + 1;

printk(KERN_ERR "%s kcontrol numid=%d name=%s is already present\n",

__func__,

kcontrol->id.numid,

kcontrol->id.name);

card->last_numid += kcontrol->count;

up_write(&card->controls_rwsem);

for (idx = 0; idx < kcontrol->count; idx++, id.index++, id.numid++)

snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);

return 0;

error:

snd_ctl_free_one(kcontrol);

return err;

}

下面是打印的log:

<3>[ 2.187774] snd_ctl_add kcontrol numid=1 name=Voice Rx Device Mute is already present

<3>[ 2.187835] snd_ctl_add kcontrol numid=2 name=Voice Tx Mute is already present

<3>[ 2.187927] snd_ctl_add kcontrol numid=3 name=Voice Rx Volume is already present

<3>[ 2.187988] snd_ctl_add kcontrol numid=4 name=TTY Mode is already present

<3>[ 2.188049] snd_ctl_add kcontrol numid=5 name=Widevoice Enable is already present

-----

<3>[ 2.267761] snd_ctl_add kcontrol numid=128 name=VoLTE Stub Tx Mixer SLIM_1_TX is already present

<3>[ 2.267883] snd_ctl_add kcontrol numid=129 name=VoLTE Stub Tx Mixer STUB_1_TX_HL is already present

<3>[ 2.268035] snd_ctl_add kcontrol numid=130 name=VoLTE Stub Tx Mixer MI2S_TX is already present

<3>[ 2.268157] snd_ctl_add kcontrol numid=131 name=VoLTE Stub Tx Mixer SLIM_3_TX is already present

--------

<3>[ 2.375000] snd_ctl_add kcontrol numid=462 name=HDMI RX Format is already present

<四>上层如何操作kcontrol以及内核接收调用

alsa机制linux提供了alsa库,高通平台在/hardware/qcom/audio/libalsa-intf里面,同时通过ucm来管理需要发送的kcontrol

sequence。

Name "Speaker"

Comment "Speaker Rx device"

EnableSequence

‘SLIM_0_RX Channels‘:0:Two

‘RX3 MIX1 INP1‘:0:RX1

‘RX3 MIX1 INP2‘:0:RX6

‘RX5 MIX1 INP1‘:0:RX2

‘RX5 MIX1 INP2‘:0:RX7

‘RX4 DSM MUX‘:0:DSM_INV

‘RX6 DSM MUX‘:0:DSM_INV

‘LINEOUT1 Volume‘:1:72

‘LINEOUT2 Volume‘:1:72

‘LINEOUT3 Volume‘:1:72

‘LINEOUT4 Volume‘:1:72

------

EndSequence

播放mp3 时 中间层logcat log,可以看见通过numid传到内核来识别kcontrol:

D/alsa_ucm( 263): set_controls_of_device_for_all_usecases: Speaker

D/alsa_ucm( 263): print_list: head 0x0

D/alsa_ucm( 263): Empty list

D/alsa_ucm( 263): Empty list

D/alsa_ucm( 263): snd_use_case_set(): uc_mgr 0x41b09d38 identifier _verb value HiFi Lowlatency

D/alsa_ucm( 263): No switch device/modifier option found: _verb

D/alsa_ucm( 263): Index:17 Verb:HiFi Lowlatency

D/alsa_ucm( 263): set_use_case_ident_for_all_devices(): HiFi Lowlatency

D/alsa_ucm( 263): getUseCaseType: use case is HiFi Lowlatency

D/alsa_ucm( 263): Applying mixer controls for device: Speaker

D/alsa_ucm( 263): Set mixer controls for Speaker enable 1

D/alsa_ucm( 263): Empty list

D/alsa_ucm( 263): No voice use case found

D/alsa_ucm( 263): acdb_id 15 cap 1 enable 1

D/alsa_ucm( 263): Setting mixer control: SLIM_0_RX Channels, value: Two

D/alsa_mixer( 263): mixer_ctl_select numid =464 item =1

D/alsa_ucm( 263): Setting mixer control: RX3 MIX1 INP1, value: RX1

D/alsa_mixer( 263): mixer_ctl_select numid =401 item =5

D/alsa_ucm( 263): Setting mixer control: RX3 MIX1 INP2, value: RX6

D/alsa_mixer( 263): mixer_ctl_select numid =400 item =10

D/alsa_ucm( 263): Setting mixer control: RX5 MIX1 INP1, value: RX2

D/alsa_mixer( 263): mixer_ctl_select numid =397 item =6

D/alsa_ucm( 263): Setting mixer control: RX5 MIX1 INP2, value: RX7

D/alsa_mixer( 263): mixer_ctl_select numid =396 item =11

D/alsa_ucm( 263): Setting mixer control: RX4 DSM MUX, value: DSM_INV

D/alsa_mixer( 263): mixer_ctl_select numid =408 item =1

D/alsa_ucm( 263): Setting mixer control: RX6 DSM MUX, value: DSM_INV

D/alsa_mixer( 263): mixer_ctl_select numid =407 item =1

D/alsa_ucm( 263): Setting mixer control: LINEOUT1 Volume, value: 72

alsa 库中 mixer_ctl_select函数:

int mixer_ctl_select(struct mixer_ctl *ctl, const char *value)

{

unsigned n, max;

struct snd_ctl_elem_value ev;

unsigned int input_str_len, str_len;

if (ctl->info->type != SNDRV_CTL_ELEM_TYPE_ENUMERATED) {

errno = EINVAL;

return -1;

}

input_str_len = strnlen(value,64);

max = ctl->info->value.enumerated.items;

for (n = 0; n < max; n++) {

str_len = strnlen(ctl->ename[n], 64);

if (str_len < input_str_len)

str_len = input_str_len;

if (!strncmp(value, ctl->ename[n], str_len)) {

memset(&ev, 0, sizeof(ev));

ev.value.enumerated.item[0] = n;

ev.id.numid = ctl->info->id.numid;

ALOGD("mixer_ctl_select numid =%d item =%d \n",ctl->info->id.numid,n);

if (ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev) < 0)

return -1;

return 0;

}

}

errno = EINVAL;

return errno;

}

下面再看看如何调用内核的,搜索SNDRV_CTL_IOCTL_ELEM_WRITE,以下是调用流程:

<-------snd_ctl_ioctl

<-------snd_ctl_elem_write_user

<-------snd_ctl_elem_write//在这里通过snd_ctl_find_id调用snd_ctl_find_numid 来找到kctl

<-------kctl->put//在这里调用kcontrol对应的put

到这里kcontrol讲完,后面重点分析widget route path以及音频通道如何切换。

时间: 2024-10-20 10:03:09

Linux音频ALSA机制学习笔记<一>的相关文章

Linux音频ALSA机制学习笔记&lt;二&gt;

首先是dapm是什么?就是音频电源动态管理.相信电源管理大家都不会陌生.dapm设计的目的就是只有需要时才打开必要的部件 (widget),不需要时则关闭部件, 达到省电的目的.ALSA通过kcontrol来切换音频通道,当playback或者capture时会更新通道激活下 的widget power,这个是由内核event统一完成的,无须上层干预. <一>widget 定义widget static const struct snd_soc_dapm_widget tabla_dapm_w

Linux System Programming 学习笔记(二) 文件I/O

1.每个Linux进程都有一个最大打开文件数,默认情况下,最大值是1024 文件描述符不仅可以引用普通文件,也可以引用套接字socket,目录,管道(everything is a file) 默认情况下,子进程会获得其父进程文件表的完整拷贝 2.打开文件 open系统调用必须包含 O_RDONLY,O_WRONLY,O_RDWR 三种存取模式之一 注意 O_NONBLOCK模式 int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644

JAVA的反射机制学习笔记(二)

上次写JAVA的反射机制学习笔记(一)的时候,还是7月22号,这些天就瞎忙活了,自己的步伐完全被打乱了~不能继续被动下去,得重新找到自己的节奏. 4.获取类的Constructor 通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例 Class<T>类提供了几个方法获取类的构造器. public Constructor<T> getConstructor(Class<?>... parameterTypes) 返回一个 Constructor 对象,它反

Linux学习笔记二:Linux的文件处理命令

1.文件处理命令:ls 作用:显示目录文件 语法:ls [选项] [文件或目录] [选项]:-a 显示所有文件,包括隐藏文件   -l 显示详细信息 -d 查看目录属性 [文件或目录]:当省略时默认将当前路径作为参数 实例: · ls 显示当前文件夹下所有文件(不包括隐藏文件)的信息 · ls -l 显示当前文件夹下所有文件(不包括隐藏文件)的详细信息 · ls -ld  显示当前文件夹的详细信息 技巧:要查看文件夹的信息,必须加-d参数:要查看详细参数,必须加-l参数. 2.文件的属性 使用”

linux学习笔记二:硬盘信息查询

在linux管理中,硬盘管理是很重要的一部分.包括阵列,分区,逻辑卷等操作,在对硬盘操作前,需要充分的了解硬盘的信息.常用的硬盘查询有以下几种: 1.df  查看文件系统空间使用情况: linux-lszd-db:~ # dfFilesystem     1K-blocks     Used Available Use% Mounted on/dev/sda6      809262496 37615092 770825244   5% /udev             8076412    

linux学习笔记二:linux文件系统

各大linux的版本都遵循着FHS(Filesystem Hierarchy Standard)文件系统目录标准,是一个树形结构的组织文件.在此简要记录各目录. linux下所有文件都处在/文件下. 树形结构图: /boot:  系统启动相关的文件 主要文件 1.vmliunx:内核    2.initramfs:磁盘映像文件   3.grub(bootloader) /dev:设备文件 块设备:随机访问设备. 字符设备:线性设备,顺序访问.按字符为单位.键盘.鼠标. 设备号:主设备号(majo

APUE 学习笔记(二) 文件I/O

1. 文件I/O 对于内核而言,所有打开的文件都通过文件描述符引用,内核不区分文本文件和二进制文件 open函数:O_RDONLY  O_WRONLY  O_RDWR create函数: close函数:关闭一个文件时还会释放该进程加在该文件上的所有记录锁 lseek函数:显式地为一个打开的文件设置其偏移量 每个打开的文件都有一个与其相关联的 "当前文件偏移量",用以度量从文件开始处计算的字节数,通常,读.写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数 文件偏移量可以大于

NFC学习笔记二——Libnfc简介与安装

一直想把自己对过的英文文章做一下翻译记录下来,趁着学习NFC,现将libnfc首页的对libnfc介绍和在不同操作系统上对libnfc安装的文章做一下翻译,一方面提高一下自己的英语,另一方面学习一下libnfc. 原文地址:http://nfc-tools.org/index.php?title=Libnfc 公共平台独立的近场通讯(NFC)库 libnfc是GNU公共许可正下发布的第一个免费的底层的NFC开发包和编程API.它对任何人事完全免费和公开的.这个列表显示了libnfc支持的功能.l

Android学习笔记二十之Toast吐司、Notification通知、PopupWindow弹出窗

Android学习笔记二十之Toast吐司.Notification通知.PopupWindow弹出窗 Toast吐司 Toast吐司是我们经常用到的一个控件,Toast是AndroidOS用来显示消息的一种机制,它与Dialog不同,Toast不会获取到焦点,通常显示一段时间之后就会自动消失,下面我们来介绍Toast的几种常用方式: 第一种,默认显示方式,也是最常用的方式: Toast.makeText(MainActivity.this, "这是默认的显示方式", Toast.LE