【Android Audio 入门 三】--- KControl 介绍

【Android Audio 入门 三】--- KControl 介绍

    • 三、KControl 介绍
      • 3.1 如何调用kcontrol ( /dev/snd/controlC%u )
      • 3.2 IOCTL,snd_ctl_ioctl() 函数介绍
      • 3.3 SNDRV_CTL_IOCTL_ELEM_INFO 获得所有 kcontrl 列表
      • 3.4 hal层如何使用kcontrol
      • 3.5 tinymix 介绍


接着前面我们写的文章《【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 介绍

Kcontrol 是 audio 中的一个 字符串,通过这个 字符串,能够执行一些定好的功能:
【Android Audio 入门 三】--- KControl 介绍_第1张图片

3.1 如何调用kcontrol ( /dev/snd/controlC%u )

kcontrol 主要用于mixer,可以对audio 整个过程进行控制,
其实对对应的就是操作 /dev/snd/controlC%u 节点。

在 mixer_open() 函数中,主要工作如下:

  1. 根据声卡号来拼凑 kcontrol 节点的字符串名字,并打开节点
  2. 通过 ioctrl 获取 所有 支持的Kcontrol 的字符串列表
  3. 分配 Mixer 的内存
  4. 获得 Mixer 结构体中的 card_info 信息
  5. 取出kontrol的id 存入 ei 中
  6. 获取到所有的 kcontrol list

代码如下:

@ \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;

3.2 IOCTL,snd_ctl_ioctl() 函数介绍

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


3.3 SNDRV_CTL_IOCTL_ELEM_INFO 获得所有 kcontrl 列表

@ \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

这块目前,我能理解,但没法全部写清楚,就写到这,后面功力更深时,再重新写

3.4 hal层如何使用kcontrol

如下,
通过 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),
};



3.5 tinymix 介绍

tinymix Android 自带的audio 调试工具,代码位于 \external\tinyalsa\ 目录。

如下图:
【Android Audio 入门 三】--- KControl 介绍_第2张图片

包括: 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

【Android Audio 入门 三】--- KControl 介绍_第3张图片
【Android Audio 入门 三】--- KControl 介绍_第4张图片

你可能感兴趣的:(05--Android,Audio)