基于Linux UAC gadget驱动实现Audio Control处理-应用层

date
Mar 23, 2022
slug
2022-03-23-linux-uac-gadget-audio-control-application
status
Published
tags
USB
UAC
summary
本文总结了SigmaStar提供的在基于Linux kernel的UAC gadget设备中增加audio control控制功能的完整工作流程,应用层部分。
type
Post
基于对SigmaStar Webcam方案源代码的学习,通过三篇文章的篇幅,整体总结了SigmaStar提供的在基于Linux kernel的UAC gadget设备中增加audio control控制功能的完整工作流程:
  1. 应用层
  1. 内核层1-Audio Control Get类命令的处理流程
  1. 内核层2-Audio Control Set类命令的处理流程
 
以下为应用层部分的完整总结。详细解析了SigmaStar SDK中提供的在应用层通过ALSA KControl机制实现audio control命令的通知和功能实现。
 
Linux USB Gadget中的UAC部分与kernel部分的交互,实际上使用了Linux的ALSA音频驱动框架。这一点有点类似于在UVC Gadget中使用了v4l2摄像头驱动框架一样:
  • v4l2摄像头框架原本的设计是要实现一个内核中的摄像头驱动,以方便应用层以统一的流程和接口从kenel中读取摄像头的数据;而在UVC中使用v4l2的图像流的走向刚好与原本的v4l2框架相反,是把应用层的图像流通过v4l2机制发到内核,然后由内核调用USB底层驱动发给Host;
  • 在UAC上使用ALSA音频驱动框架的逻辑也是类似。ALSA原本的设计,是通过ALSA框架定义的标准,从kernel中读取麦克风的数据到应用层,向kernel中写入需要通过喇叭来播放的音频数据,以及通过kcontrol机制实现对音频硬件的控制;UAC中对于ALSA的使用刚好相反,是把应用层整理好的音频数据使用SNDRV_PCM_IOCTL_WRITEI_FRAMES ioctl code写入kernel,然后由kernel调用USB硬件发给Host,以及把USB Host发过来的音频数据通过使用SNDRV_PCM_IOCTL_READI_FRAMES ioctl code读取到应用层,再调用SDK应用层的API把这个声音从喇叭中播放出来,而USB Host所发过来的audio control类的命令则是通过kcontrol机制返回给应用层,由应用层做针对的处理。

应用层部分的实现逻辑

应用层实现audio control的大致流程,与ALSA声卡驱动框架的kcontrol一致:
  • 调用mixer_open函数打开audio control设备文件,以及通过这个设备文件读取kernel中支持kcontrol列表;
  • 调用mixer_subscribe_events函数开启内核audio control事件的订阅;
  • 循环执行:
    • 调用mixer_wait_event函数等待audio control设备文件是否有新的command通知上来;
    • 调用mixer_ctl_get_event和mixer_ctl_get_value获取事件详细信息及其参数;
    • 利用从内核设备文件中读取到的事件和参数,通过SigmaStar SDK应用层的MI_AI_SetVqeVolume接口设置新的音量参数。

mixer_open函数的解析:

struct mixer *mixer_open(unsigned int card)
{           
    struct mixer *mixer = NULL;
    char fn[256];
            
    mixer = calloc(1, sizeof(*mixer));
    if (!mixer)
        return 0;
            
		//打开的ALSA框架下的audio control设备文件,注意这个设备文件的名称
    snprintf(fn, sizeof(fn), "/dev/snd/controlC%u", card);
    mixer->fd = open(fn, O_RDWR);
    if (mixer->fd < 0)
        goto fail;

		//从打开的设备文件中获取声卡信息
    if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_CARD_INFO, &mixer->card_info) < 0)
        goto fail;
    
		//读取kernel中支持的kcontrol的列表
    if (add_controls(mixer) != 0)
        goto fail;
        
    return mixer;
        
fail:
    mixer_close(mixer);
    return NULL;
}

static int add_controls(struct mixer *mixer)
{
    struct snd_ctl_elem_list elist;
    struct snd_ctl_elem_id *eid = NULL;
    struct mixer_ctl *ctl;
    int fd = mixer->fd;
    const unsigned int old_count = mixer->count;
    unsigned int new_count;
    unsigned int n;

		//以下通过ioctl code SNDRV_CTL_IOCTL_ELEM_LIST读取kernel中支持的kcontrol的数量
    memset(&elist, 0, sizeof(elist));
    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
        goto fail;

		//判断内核中支持的kcontrol的数量是否有增加?因为是第一次读,所以old_count=0,后续都按照这个逻辑处理
    if (old_count == elist.count)
        return 0; /* no new controls return unchanged */

    if (old_count > elist.count)
        return -1; /* driver has removed controls - this is bad */

    ctl = mixer_realloc_z(mixer->ctl, old_count, elist.count,
                          sizeof(struct mixer_ctl));
    if (!ctl)
        goto fail;

    mixer->ctl = ctl;

    /* ALSA drivers are not supposed to remove or re-order controls that
     * have already been created so we know that any new controls must
     * be after the ones we have already collected
     */
    new_count = elist.count;
    elist.space = new_count - old_count; /* controls we haven't seen before */
    elist.offset = old_count; /* first control we haven't seen */

		eid = calloc(elist.space, sizeof(struct snd_ctl_elem_id));
    if (!eid)
        goto fail;

    elist.pids = eid;

    if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_LIST, &elist) < 0)
        goto fail;
		//利用ioctl code SNDRV_CTL_IOCTL_ELEM_INFO把所有的kcontrol结构读出来,这样应用层就能知道ALSA底层驱动支持哪些audio control参数
    for (n = old_count; n < new_count; n++) {
        struct snd_ctl_elem_info *ei = &mixer->ctl[n].info;
        ei->id.numid = eid[n - old_count].numid;
        if (ioctl(fd, SNDRV_CTL_IOCTL_ELEM_INFO, ei) < 0)
            goto fail_extend;
        ctl[n].mixer = mixer;
    }

    mixer->count = new_count;
    free(eid);
    return 0;

fail_extend:
    /* cleanup the control we failed on but leave the ones that were already
     * added. Also no advantage to shrinking the resized memory block, we
     * might want to extend the controls again later
     */
    mixer_cleanup_control(&ctl[n]);

    mixer->count = n;   /* keep controls we successfully added */
    /* fall through... */
fail:
    free(eid);
    return -1;
}

mixer_subscribe_events

mixer_subscribe_events的实现比较简单,实际上就是通过ioctl code SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS开启或者关闭对内核ALSA control事件的订阅;
int mixer_subscribe_events(struct mixer *mixer, int subscribe)
{   
    if (ioctl(mixer->fd, SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS, &subscribe) < 0) {
        return -1;
    }   
    return 0;
}

Audio Control消息处理循环结构

		while(!g_bExit)
    {
        ret = mixer_wait_event(mixer, 1000);
        if(ret == 1)
        {
            ret = mixer_ctl_get_event(ctl, 0);
            if(MI_SUCCESS != ret)
            {
                printf("mixer_ctl_get_event failed\n");
                break;
            }

            AiVolume = mixer_ctl_get_value(ctl, 0);

            //在此调用应用层的音量控制函数
						......
            printf("current volume is %d\n", s32VolumeDb);
        }
    }

//采用poll机制阻塞等待kernel中的事件发生
int mixer_wait_event(struct mixer *mixer, int timeout)
{   
    struct pollfd pfd;
    
    pfd.fd = mixer->fd; 
    pfd.events = POLLIN | POLLOUT | POLLERR | POLLNVAL;

    for (;;) {
        int err;
        err = poll(&pfd, 1, timeout);
        if (err < 0)
            return -errno;
        if (!err)
            return 0;
        if (pfd.revents & (POLLERR | POLLNVAL))
            return -EIO;
        if (pfd.revents & (POLLIN | POLLOUT))
            return 1;
    }
}

//利用read API从打开的control设备文件中读取事件结构体,每个ALSA事件都是一个snd_ctl_event结构体
int mixer_ctl_get_event(const struct mixer_ctl *ctl, unsigned int id)
{           
    struct snd_ctl_event ctl_ev;
    int ret = read(ctl->mixer->fd, &ctl_ev, sizeof(struct snd_ctl_event));
    if (ret < 0)
        return -1;
		//这个函数的处理上,读完snd_ctl_event结构体就直接返回了,这个结构体并没有使用。实际中应该要通过这个结构体来判断是哪个audio control推送的事件
    return 0;
}

//读取audio control的当前设置参数
int mixer_ctl_get_value(const struct mixer_ctl *ctl, unsigned int id)
{           
    struct snd_ctl_elem_value ev;
    int ret;    
                
    if (!ctl || (id >= ctl->info.count))
        return -EINVAL;
            
    memset(&ev, 0, sizeof(ev));
    ev.id.numid = ctl->info.id.numid;
		//利用ioctl code SNDRV_CTL_IOCTL_ELEM_READ从kernel中读取指定audio control的设置值
    ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);
    if (ret < 0)
        return ret;
   
		//根据设置值的具体数据类型对其进行解析和数据格式转换
    switch (ctl->info.type) {
    case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
        return !!ev.value.integer.value[id];
            
    case SNDRV_CTL_ELEM_TYPE_INTEGER:
        return ev.value.integer.value[id];

    case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
        return ev.value.enumerated.item[id];

    case SNDRV_CTL_ELEM_TYPE_BYTES:
        return ev.value.bytes.data[id];

    default:
        return -EINVAL;
    }

    return 0;
}
  • 通过在事件监听和处理循环中,通过mixer_ctl_get_event可以知道哪个audio control的参数发生了变化,然后通过mixer_ctl_get_value函数读取这个audio control在kernel中的当前设置值,然后就可以在应用层调用对应的audio control的功能函数,把Host发过来的audio control功能实现出来。
  • 以上例子代码只是实现了一个volume control的例子。如果要增加其他的audio control功能,可以在mixer_ctl_get_value函数中进行判断和识别,究竟是哪个audio control的参数发生了变化,然后对应的在mixer_ctl_get_value函数中读取其内核中的设置值,并在应用层设置功能。

© Pavel Han 2020 - 2022