接着前面我们写的文章《【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
Kcontrol 是 audio 中的一个 字符串,通过这个 字符串,能够执行一些定好的功能:
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