接着前面我们写的文章《【Android Audio 入门 二】— /dev/snd下的pcm节点 创建 及 open 过程代码分析》
看到一位大佬的博客,对Audio分析的非常详细,记录下网址:
ALSA https://blog.csdn.net/sepnic/article/category/777239
Android Audio https://blog.csdn.net/sepnic/article/category/778492
控制接口对于许多开关(switch)和调节器(slider)应用广泛,它能被用户空间存取,从而读写CODEC相关寄存器。snd kcontrol的主要用于mixer。
它用snd_kcontrol_new结构体描述。
其结构体介绍如下:
snd_ctl_elem_iface_t iface
ifcase 字段 定义了 Control 的类型,形式为 snd_ctl_elem_iface_t ,
对于Mixer 是 snd_ctl_elem_iface_mixer,对于不属于 mixer 的全局控制,使用 card ;
如果关联到某设备,则是 PCM、RAWMINI、TIMER 或 SEQUENCER。在这里,我主要关注 mixer 。
name
这个字段是名称标识,这个字段非常重要,因为在conrol中主要是由名称来区分的。
如果 kcontrol 名称相同,则使用 index 来区分。
name 的定义标准是“ source direction function ” 即 “ 源 方向 功能”,
source 定义了 control 的源,如“Master” ,“PCM” 等。
Direction 则为 “Playback” , “Capture” 等,如果 direction 忽略,意味着 playback 和 capture 是双向的;
function 则可以是 “switch” ,“volume”,和 “Route” 等。
上层也可以根据 num id 来找到对应的control,snd_ctl_find_id() 也是优先判断 上层是否传递了num id,
是则直接返回这个 num id 对应的control 。
用户层设置 num id 和 control 的关联时,可以用 alsa-lib 的 snd_mixer_elem_set_enum_item 函数。
snd_kconrol_new 结构体并没有 numid 这个成员,是因为numid 是系统自动管理的,原则是该 control 的注册顺序,
保存到 snd_ctl_elem_value 结构体中。
access
access 字段是访问控制权限。snd_ctl_elem_acces_read 意味着是只读,这时 put() 函数不必实现。
snd_ctl_elem_access_write 意味着是只写,这时 get() 函数不必实现。
若 control 值频繁变化,则需定义 volatile 标志。
当 control 处于非激活状态时,应设置 inactive 标志。
其结构体描述如下:
@ \kernel\msm-3.18\include\sound\control.h
struct snd_kcontrol_new {
snd_ctl_elem_iface_t iface; /* interface identifier */
unsigned int device; /* device/client number */
unsigned int subdevice; /* subdevice (substream) number */
const unsigned char *name; /* ASCII name of item */
unsigned int index; /* index of item */
unsigned int access; /* access rights */
unsigned int count; /* count of same elements */
snd_kcontrol_info_t *info;
snd_kcontrol_get_t *get;
snd_kcontrol_put_t *put;
union {
snd_kcontrol_tlv_rw_t *c;
const unsigned int *p;
} tlv;
unsigned long private_value;
};
如下是,一个codec 中 kcontrol 的定义代码示例:
@ \kernel\msm-3.18\sound\soc\codecs\88pm860x-codec.c
static const struct snd_kcontrol_new pm860x_snd_controls[] = {
SOC_DOUBLE_R_TLV("ADC Capture Volume", PM860X_ADC_ANA_2, PM860X_ADC_ANA_3, 6, 3, 0, adc_tlv),
SOC_DOUBLE_TLV("AUX Capture Volume", PM860X_ADC_ANA_3, 0, 3, 7, 0, aux_tlv),
SOC_SINGLE_TLV("MIC1 Capture Volume", PM860X_ADC_ANA_2, 0, 7, 0, mic_tlv),
SOC_SINGLE_TLV("MIC3 Capture Volume", PM860X_ADC_ANA_2, 3, 7, 0, mic_tlv),
SOC_DOUBLE_R_EXT_TLV("Sidetone Volume", PM860X_SIDETONE_L_GAIN, PM860X_SIDETONE_R_GAIN, 0, ARRAY_SIZE(st_table)-1,
0, snd_soc_get_volsw_2r_st, snd_soc_put_volsw_2r_st, st_tlv),
SOC_SINGLE_TLV("Speaker Playback Volume", PM860X_EAR_CTRL_1, 0, 7, 0, out_tlv),
SOC_DOUBLE_R_TLV("Line Playback Volume", PM860X_LO1_CTRL, PM860X_LO2_CTRL, 0, 7, 0, out_tlv),
SOC_DOUBLE_R_TLV("Headset Playback Volume", PM860X_HS1_CTRL, PM860X_HS2_CTRL, 0, 7, 0, out_tlv),
SOC_DOUBLE_R_EXT_TLV("Hifi Left Playback Volume", PM860X_HIFIL_GAIN_LEFT, PM860X_HIFIL_GAIN_RIGHT, 0, 63, 0,
snd_soc_get_volsw_2r_out,
snd_soc_put_volsw_2r_out, dpga_tlv),
SOC_DOUBLE_R_EXT_TLV("Hifi Right Playback Volume", PM860X_HIFIR_GAIN_LEFT, PM860X_HIFIR_GAIN_RIGHT, 0, 63, 0,
snd_soc_get_volsw_2r_out,
snd_soc_put_volsw_2r_out, dpga_tlv),
SOC_DOUBLE_R_EXT_TLV("Lofi Playback Volume", PM860X_LOFI_GAIN_LEFT, PM860X_LOFI_GAIN_RIGHT, 0, 63, 0,
snd_soc_get_volsw_2r_out,
snd_soc_put_volsw_2r_out, dpga_tlv),
SOC_ENUM("Headset1 Operational Amplifier Current", pm860x_hs1_opamp_enum),
SOC_ENUM("Headset2 Operational Amplifier Current", pm860x_hs2_opamp_enum),
SOC_ENUM("Headset1 Amplifier Current", pm860x_hs1_pa_enum),
SOC_ENUM("Headset2 Amplifier Current", pm860x_hs2_pa_enum),
SOC_ENUM("Lineout1 Operational Amplifier Current", pm860x_lo1_opamp_enum),
SOC_ENUM("Lineout2 Operational Amplifier Current", pm860x_lo2_opamp_enum),
SOC_ENUM("Lineout1 Amplifier Current", pm860x_lo1_pa_enum),
SOC_ENUM("Lineout2 Amplifier Current", pm860x_lo2_pa_enum),
SOC_ENUM("Speaker Operational Amplifier Current", pm860x_spk_ear_opamp_enum),
SOC_ENUM("Speaker Amplifier Current", pm860x_spk_pa_enum),
SOC_ENUM("Earpiece Amplifier Current", pm860x_ear_pa_enum),
};
可以看出,在 alsa 代码中,定义kcontrol 是通过宏控来实现的:
SOC_SINGLE_TLV
SOC_SINGLE_TLV(“MIC1 Capture Volume”, PM860X_ADC_ANA_2, 0, 7, 0, mic_tlv),
结合以下的宏代码原型,可以看出要定义的宏控属性如下:
xname = “MIC1 Capture Volume”; 该kcontrol 的名字为 MIC1 Capture Volume
reg = PM860X_ADC_ANA_2; #define PM860X_ADC_ANA_2 0xd1,要写的寄存器地址为 0xD1
shift = 0; 寄存器偏移 0 bit
max = 7; 最大的值是 7 ,也就是说这个寄存器可以写的值是 0 - 7
invert = 0; 是否需要位反转
tlv_array = mic_tlv; 写入的值的数组为 mic_tlv
总结上面的意思就是:
上层在调用 “MIC1 Capture Volume” 这个名字的 kcontrol 时,就会往 0xD1 这个寄存器写入 0-7 的值 ,要写的的数据在 mic_tlv 数组中。
SOC_SINGLE_TLV("MIC1 Capture Volume", PM860X_ADC_ANA_2, 0, 7, 0, mic_tlv),
@ \kernel\msm-3.18\include\sound\soc.h
#define SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
.put = snd_soc_put_volsw, \
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert, 0) }
#define SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, xautodisable) \
SOC_DOUBLE_VALUE(xreg, xshift, xshift, xmax, xinvert, xautodisable)
#define SOC_DOUBLE_VALUE(xreg, shift_left, shift_right, xmax, xinvert, xautodisable) \
((unsigned long)&(struct soc_mixer_control) \
{.reg = xreg, .rreg = xreg, .shift = shift_left, \
.rshift = shift_right, .max = xmax, .platform_max = xmax, \
.invert = xinvert, .autodisable = xautodisable})
SOC_DOUBLE_TLV("AUX Capture Volume", PM860X_ADC_ANA_3, 0, 3, 7, 0, aux_tlv),
@ \kernel\msm-3.18\include\sound\soc.h
#define SOC_DOUBLE_TLV(xname, reg, shift_left, shift_right, max, invert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw, \
.put = snd_soc_put_volsw, \
.private_value = SOC_DOUBLE_VALUE(reg, shift_left, shift_right, \
max, invert, 0) }
SOC_DOUBLE_R_TLV("ADC Capture Volume", PM860X_ADC_ANA_2, PM860X_ADC_ANA_3, 6, 3, 0, adc_tlv),
@ \kernel\msm-3.18\include\sound\soc.h
#define SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
SNDRV_CTL_ELEM_ACCESS_READWRITE,\
.tlv.p = (tlv_array), \
.info = snd_soc_info_volsw, \
.get = snd_soc_get_volsw, .put = snd_soc_put_volsw, \
.private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \
xmax, xinvert) }
SOC_DOUBLE_R_EXT_TLV("Hifi Left Playback Volume", PM860X_HIFIL_GAIN_LEFT, PM860X_HIFIL_GAIN_RIGHT, 0, 63, 0,
snd_soc_get_volsw_2r_out,
snd_soc_put_volsw_2r_out, dpga_tlv),
@ \kernel\msm-3.18\include\sound\soc.h
#define SOC_DOUBLE_R_EXT_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert,\
xhandler_get, xhandler_put, tlv_array) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.tlv.p = (tlv_array), \
.info = snd_soc_info_volsw, \
.get = xhandler_get, .put = xhandler_put, \
.private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \
xmax, xinvert) }
SOC_ENUM("Headset1 Amplifier Current", pm860x_hs1_pa_enum),
@ \kernel\msm-3.18\include\sound\soc.h
#define SOC_ENUM(xname, xenum) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
.info = snd_soc_info_enum_double, \
.get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \
.private_value = (unsigned long)&xenum }
通过前面的宏控,可以看分别定义了 get 和 put 两个函数,后面,
分别在读 和 写 时就会调用对应的 get 和 put 函数。
kcontrol 主要用于mixer,可以对audio 整个过程进行控制,
其实对对应的就是操作 /dev/snd/controlC%u 节点。
在 mixer_open() 函数中,主要工作如下:
代码如下:
@ \src\external\tinyalsa\mixer.c
struct mixer *mixer_open(unsigned int card)
{
struct snd_ctl_elem_list elist;
struct snd_ctl_elem_info tmp;
struct snd_ctl_elem_id *eid = NULL;
struct mixer *mixer = NULL;
unsigned int n, m;
int fd;
char fn[256];
// 1. 根据声卡号来拼凑 kcontrol 节点的字符串名字,并打开节点
snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
fd = open(fn, O_RDWR);
// 2. 通过 ioctrl 获取 所有 支持的Kcontrol 的数量
memset(&elist, 0, sizeof(elist));
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
goto fail;
// 3. 分配 Mixer 的内存,用于保存 kernel kontrol 信息的结构体
mixer = calloc(1, sizeof(*mixer));
mixer->ctl = calloc(elist.count, sizeof(struct mixer_ctl));
mixer->elem_info = calloc(elist.count, sizeof(struct snd_ctl_elem_info));
// 4. 获得 Mixer 结构体中的 card_info 信息
if (ioctl(fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)
goto fail;
// 临时存储空间分配空间
eid = calloc(elist.count, sizeof(struct snd_ctl_elem_id));
mixer->count = elist.count;
mixer->fd = fd;
elist.space = mixer->count;
elist.pids = eid;
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
goto fail;
for (n = 0; n < mixer->count; n++) {
struct snd_ctl_elem_info *ei = mixer->elem_info + n;
ei->id.numid = eid[n].numid;
// 5. 取出kontrol的id 存入 ei 中
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
goto fail;
mixer->ctl[n].info = ei;
mixer->ctl[n].mixer = mixer;
if (ei->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED) {
char **enames = calloc(ei->value.enumerated.items, sizeof(char*));
mixer->ctl[n].ename = enames;
for (m = 0; m < ei->value.enumerated.items; m++) {
memset(&tmp, 0, sizeof(tmp));
tmp.id.numid = ei->id.numid;
tmp.value.enumerated.item = m;
// 6. 获取到所有的 kcontrol list
if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, &tmp) < 0)
goto fail;
enames[m] = strdup(tmp.value.enumerated.name);
if (!enames[m])
goto fail;
}
}
}
free(eid);
return mixer;
在 ioctl 中通过 解析上层传递下来的cmd,执行相应的函数,做相应的操作。
@ \src\kernel\msm-3.18\sound\core\control.c
static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
ctl = file->private_data;
card = ctl->card;
switch (cmd) {
case SNDRV_CTL_IOCTL_PVERSION:
return put_user(SNDRV_CTL_VERSION, ip) ? -EFAULT : 0;
case SNDRV_CTL_IOCTL_CARD_INFO:
return snd_ctl_card_info(card, ctl, cmd, argp);
case SNDRV_CTL_IOCTL_ELEM_LIST:
return snd_ctl_elem_list(card, argp);
case SNDRV_CTL_IOCTL_ELEM_INFO:
return snd_ctl_elem_info_user(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_READ:
return snd_ctl_elem_read_user(card, argp);
case SNDRV_CTL_IOCTL_ELEM_WRITE:
return snd_ctl_elem_write_user(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_LOCK:
return snd_ctl_elem_lock(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_UNLOCK:
return snd_ctl_elem_unlock(ctl, argp);
case SNDRV_CTL_IOCTL_ELEM_ADD:
return snd_ctl_elem_add_user(ctl, argp, 0);
case SNDRV_CTL_IOCTL_ELEM_REPLACE:
return snd_ctl_elem_add_user(ctl, argp, 1);
case SNDRV_CTL_IOCTL_ELEM_REMOVE:
return snd_ctl_elem_remove(ctl, argp);
case SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS:
return snd_ctl_subscribe_events(ctl, ip);
case SNDRV_CTL_IOCTL_TLV_READ:
return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_READ);
case SNDRV_CTL_IOCTL_TLV_WRITE:
return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_WRITE);
case SNDRV_CTL_IOCTL_TLV_COMMAND:
return snd_ctl_tlv_ioctl(ctl, argp, SNDRV_CTL_TLV_OP_CMD);
case SNDRV_CTL_IOCTL_POWER:
return -ENOPROTOOPT;
case SNDRV_CTL_IOCTL_POWER_STATE:
#ifdef CONFIG_PM
return put_user(card->power_state, ip) ? -EFAULT : 0;
#else
return put_user(SNDRV_CTL_POWER_D0, ip) ? -EFAULT : 0;
#endif
}
// 如果不是前的面的这些命令,则遍历
list_for_each_entry(p, &snd_control_ioctls, list) {
err = p->fioctl(card, ctl, cmd, arg);
}
dev_dbg(card->dev, "unknown ioctl = 0x%x\n", cmd);
return -ENOTTY;
}
@ \src\kernel\msm-3.18\sound\core\control.c
static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl, struct snd_ctl_elem_info __user *_info)
{
struct snd_ctl_elem_info info; // 定义局部变量用于保存 kcontrol list
copy_from_user(&info, _info, sizeof(info);
result = snd_power_wait(ctl->card, SNDRV_CTL_POWER_D0);
result = snd_ctl_elem_info(ctl, &info);
copy_to_user(_info, &info, sizeof(info);
return result;
}
接下来,通过 snd_ctl_elem_info() 函数来获取 info
static int snd_ctl_elem_info(struct snd_ctl_file *ctl, struct snd_ctl_elem_info *info)
{
struct snd_card *card = ctl->card;
struct snd_kcontrol *kctl;
struct snd_kcontrol_volatile *vd;
kctl = snd_ctl_find_id(card, &info->id);
result = kctl->info(kctl, info); //
return result;
}
调用kcontrl 的info 函数 来获取 所有kcontrol
static int snd_ctl_elem_add(struct snd_ctl_file *file,
struct snd_ctl_elem_info *info, int replace)
{
if (info->type == SNDRV_CTL_ELEM_TYPE_ENUMERATED)
kctl.info = snd_ctl_elem_user_enum_info;
else
kctl.info = snd_ctl_elem_user_info;
kctl.get = snd_ctl_elem_user_get;
kctl.put = snd_ctl_elem_user_put;
kctl.tlv.c = snd_ctl_elem_user_tlv;
_kctl = snd_ctl_new(&kctl, access);
}
static int snd_ctl_elem_user_enum_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct user_element *ue = kcontrol->private_data;
const char *names;
unsigned int item;
item = uinfo->value.enumerated.item;
*uinfo = ue->info;
item = min(item, uinfo->value.enumerated.items - 1);
uinfo->value.enumerated.item = item;
names = ue->priv_data;
for (; item > 0; --item)
names += strlen(names) + 1;
strcpy(uinfo->value.enumerated.name, names);
return 0;
}
kcontrol 定义的代码在: @\kernel\msm-3.18\sound\soc\msm\qdsp6v2\msm-pcm-routing-v2.c
如下,
通过 mixer_get_ctl_by_name 就能通过 字符串获得对应的ctrl。
通过 mixer_ctl_set_value(ctl, 0, false); 设置对应值
@ \src\hardware\qcom\audio\hal\audio_extn\usb.c
static const char * const usb_sidetone_enable_str[] = {
"Sidetone Playback Switch",
"Mic Playback Switch",
};
static const char * const usb_sidetone_volume_str[] = {
"Sidetone Playback Volume",
"Mic Playback Volume",
};
static void usb_get_sidetone_mixer(struct usb_card_config *usb_card_info)
{
struct mixer_ctl *ctl;
unsigned int index;
usb_card_info->usb_snd_mixer = mixer_open(usb_card_info->usb_card);
for (index = 0; index < sizeof(usb_sidetone_enable_str)/sizeof(usb_sidetone_enable_str[0]); index++) {
ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,usb_sidetone_enable_str[index]);
if (ctl) {
usb_card_info->usb_sidetone_index[USB_SIDETONE_ENABLE_INDEX] = index;
/* Disable device sidetone by default */
mixer_ctl_set_value(ctl, 0, false);
break;
}
}
for (index = 0;
index < sizeof(usb_sidetone_volume_str)/sizeof(usb_sidetone_volume_str[0]);
index++) {
ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,usb_sidetone_volume_str[index]);
if (ctl) {
usb_card_info->usb_sidetone_index[USB_SIDETONE_VOLUME_INDEX] = index;
usb_card_info->usb_sidetone_vol_min = mixer_ctl_get_range_min(ctl);
usb_card_info->usb_sidetone_vol_max = mixer_ctl_get_range_max(ctl);
break;
}
}
return;
}
相关的函数如下
@\src\external\tinyalsa\include\tinyalsa\asoundlib.h
int mixer_ctl_get_value(struct mixer_ctl *ctl, unsigned int id);
int mixer_ctl_is_access_tlv_rw(struct mixer_ctl *ctl);
int mixer_ctl_get_array(struct mixer_ctl *ctl, void *array, size_t count);
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value);
int mixer_ctl_set_array(struct mixer_ctl *ctl, const void *array, size_t count);
int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string);
其原型如下:
int mixer_ctl_set_enum_by_string(struct mixer_ctl *ctl, const char *string)
{
num_enums = ctl->info->value.enumerated.items;
for (i = 0; i < num_enums; i++) {
if (!strcmp(string, ctl->ename[i])) {
memset(&ev, 0, sizeof(ev));
ev.value.enumerated.item[0] = i;
ev.id.numid = ctl->info->id.numid;
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
return 0;
}
}
@ \src\external\tinyalsa\mixer.c
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)
{
memset(&ev, 0, sizeof(ev));
ev.id.numid = ctl->info->id.numid;
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
switch (ctl->info->type) {
case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
ev.value.integer.value[id] = !!value;
break;
case SNDRV_CTL_ELEM_TYPE_INTEGER:
ev.value.integer.value[id] = value;
break;
case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
ev.value.enumerated.item[id] = value;
break;
case SNDRV_CTL_ELEM_TYPE_BYTES:
ev.value.bytes.data[id] = value;
break;
default:
return -EINVAL;
}
return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);
}
可以发现最终是通过 ioctl 来 SNDRV_CTL_IOCTL_ELEM_WRITE 来设置值的。
@ \src\kernel\msm-3.18\sound\core\control.c
case SNDRV_CTL_IOCTL_ELEM_WRITE:
return snd_ctl_elem_write_user(ctl, argp);
static int snd_ctl_elem_write_user(struct snd_ctl_file *file, struct snd_ctl_elem_value __user *_control)
{
struct snd_ctl_elem_value *control;
struct snd_card *card;
control = memdup_user(_control, sizeof(*control));
card = file->card;
result = snd_ctl_elem_write(card, file, control);
if (copy_to_user(_control, control, sizeof(*control)))
result = -EFAULT;
kfree(control);
return result;
}
最终通过 put 函数写入值。
static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file,struct snd_ctl_elem_value *control)
{
kctl = snd_ctl_find_id(card, &control->id);
result = kctl->put(kctl, control);
return result;
}
最终通过 put 函数写入值 ,或者 get 函数来得到值。
put 和 get 定义的地方在:
@ \src\kernel\msm-3.18\sound\soc\msm\qdsp6v2\msm-dai-q6-v2.c
static const struct snd_kcontrol_new tdm_config_controls_data_format[] = {
SOC_ENUM_EXT("PRI_TDM_RX_0 Data Format", tdm_config_enum[0],
msm_dai_q6_tdm_data_format_get, msm_dai_q6_tdm_data_format_put),
SOC_ENUM_EXT("PRI_TDM_RX_1 Data Format", tdm_config_enum[0],
msm_dai_q6_tdm_data_format_get, msm_dai_q6_tdm_data_format_put),
又比如:
@ \src\kernel\msm-3.18\sound\soc\intel\mfld_machine.c
static const struct snd_kcontrol_new mfld_snd_controls[] = {
SOC_ENUM_EXT("Playback Switch", headset_enum, headset_get_switch, headset_set_switch),
SOC_ENUM_EXT("Lineout Mux", lo_enum, lo_get_switch, lo_set_switch),
};
tinymix Android 自带的audio 调试工具,代码位于 \external\tinyalsa\ 目录。
包括: tinymix、tinypcminfo、tinyplay、tinycap 等工具
1. tinymix
(1)adb shell tinymix :列出所有 kcontrol 的状态列出来
(2)adb shell tinymix ctrl_index value :配置某个 kcontrol 的值
(3)adb shell tinymix ctrl_index :获得某个 kcontrol 的值
2. tinypcminfo
tinypcminfo -D card -d device : 获得 声卡card 下第n 个deivce 相关的配置
比如: tinypcminfo -D 0 -d 0
3. tinyplay 直接播放某个文件
tinyplay file.wav -D card -d device -p period_size -n periods
注意,使用它播放前,要用 tinymix 把所有的 kcontrol 先配置好才行。
比如: tinyplay file.wav -D 0 -d 0 -p 2048 -n 2
4. tinycap 录音
tinycap file.wav -D card -d device -c channels -r rate -b bits -p periods_size -n n_periods