首页 > 代码库 > Linux音频ALSA机制学习笔记<一>

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


首先宏观看内核暴露给上层的接口:
root@android:/ # 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 = http://www.mamicode.com/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以及音频通道如何切换。

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