Showing Posts From
Uac
- 24 Mar, 2022
基于Linux UAC gadget驱动实现Audio Control处理-Kernel层GET
基于对SigmaStar Webcam方案源代码的学习,通过三篇文章的篇幅,整体总结了SigmaStar提供的在基于Linux kernel的UAC gadget设备中增加audio control控制功能的完整工作流程: 1. 应用层 2. 内核层1-Audio Control Get类命令的处理流程 3. 内核层2-Audio Control Set类命令的处理流程 以下为内核层GET类命令的完整总结。 kernel部分UAC的实现主要用到三个文件:drivers/usb/gadget/function目录下的f_uac1.c和u_audio.c,和drivers/usb/gadget/legacy下的audio.c文件。 Audio control在kernel中的实现流程需要从f_uac1结构的定义开始进行分析: 以上使用Control Set Command字符串标准的这部分就是在Linux标准uac实现的基础上增加的,以提供对audio control功能的支持。 以上数据结构中增加了一个列表定义cs,用于保存该设备可以支持的unit基本的audio control命令队列: - uac规范把audio control command分为多个不同的unit,每个unit中又包含有多个command。所以每次device收到host发出的audio control命令以后,首先要找到这个命令归属于那个unit,然后在这个unit中再找到对应的command进行处理。 - 这里的cs保存的是unit的列表。 在f_uac1.c文件中f_audio_alloc函数的最后通过control_selector_init函数来实现两个队列的初始化,后续在接收到host的audio control命令后需要对这两个队列进行轮询。在下面的例子中,提供了一个包含在Feature Unit中的volume control音量控制功能的例子: 以上初始化中用到的capture_fu_controls和capture_volume_control结构的定义如下: 接下来开始分析Device端接收到Host的Audio Control命令的完整处理流程。 按照UAC规范的定义,Audio Control命令的处理逻辑与UVC的Video Control的处理类似,都是通过在端点0上发送的控制传输类型来进行实现的。 那么在f_uac1.c中,每次Device端收到端点0上发过来的控制传输类型的数据后,自动调用的回调函数是f_audio_setup: 所以对这个f_audio_setup函数执行流程的分析就是整个audio control命令处理的起点所在。 - 首先对控制传输结构数据中包含的BRequestType字段进行解析,针对它的方向(IN还是OUT)、目的类型(端点还是接口)分为四类调用不同的函数进行处理; - 因此audio control命令都是发给audio control interface的,所以对于Set类型的audio control,我们只需要关注audio_set_intf_req;Get类型只需要关注audio_get_intf_req的实现即可。 - audio_set_intf_req和audio_get_intf_req执行完成后,返回这个控制传输后面的data stage要传输的字节数。如果大于0的话,需要再调用usb_ep_queue进行data stage的收发处理。 - 例如对于上面的volume control的例子,GET类型的命令处理上,在这个阶段会把当前的volume value返回给Host;SET类型的命令处理上,则在这个阶段接收Host新设置的volume value数据。 Audio Control Get类型命令的处理接口audio_get_intf_req: - 注意:对于get类audio command的处理,实际上在找到对应的audio command并调用其con->get函数,得到它的当前设置值,并返回以后,整个流程实际上就结束了。这个函数返回后,在f_audio_setup中会把返回的当前设置值通过usb_ep_queue发回给Host。因此在这里注册的这个f_audio_complete回调实际上没有实际作用,根本没有执行有意义的处理流程。f_audio_complete实际上主要应用在Set类型audio control的处理上,可以参考后面对Set类型命令的处理流程。 Get类型命令的实际处理入口实际上就是上面在轮询中找到的con->get函数,也就是generic_get_cmd: - 所以说这个函数实际上只是返回这个audio control中包含的当前设置值而已。那么这个当前设置值在哪儿初始化呢?答案是在前面的control_selector_init中就对所有的audio control的参数初始值进行了初始化。并且在此后收到Set类型命令以后,也会对这个con->data进行赋值,确保其中包含的参数是最新的。 至此,整个Get类型的audio control命令的处理流程就完整的分析完了。需要注意的一点是:所有audio control的当前运行参数都是在内核中进行维护和管理的,因此get类型的命令,直接从内核中就可以得到这些参数并直接返回,不需要到应用层去获取这些数据。
- 24 Mar, 2022
基于Linux UAC gadget驱动实现Audio Control处理-Kernel层SET
基于对SigmaStar Webcam方案源代码的学习,通过三篇文章的篇幅,整体总结了SigmaStar提供的在基于Linux kernel的UAC gadget设备中增加audio control控制功能的完整工作流程: 1. 应用层 2. 内核层1-Audio Control Get类命令的处理流程 3. 内核层2-Audio Control Set类命令的处理流程 以下为内核层SET类命令的完整总结。 延续前面的Get类命令的分析。下面来继续分析Audio Control Set类型命令的处理接口audio_set_intf_req: - 整体的工作逻辑与Get类的处理基本类似。最大的不同就是通过轮询两个队列,找到对应的audio control以后,只是把这个audio control结构体赋值给uac1->set_con,并没有做具体的set操作。这个set操作需要在req->complete上注册的回调函数f_audio_complete中执行。 - 放到f_audio_complete中执行的原因是,set类型的命令需要收到data stage发过来的设置参数,而audio_set_intf_req执行的时候这个data stage还没发过来。所以只能在这里先记下来,然后等整个控制传输结束,这个时候会自动回调req->complete上注册的f_audio_complete函数。因此在f_audio_complete中处理实际的set命令比较合适。 因此Set类型命令的实际处理入口函数就是f_audio_complete: - 可以看到,上面最终执行的set功能仍然是audio control command自己的set函数。这个set函数在对应的audio control结构体的定义中已经设置好。例如可以查看前面的capture_volume_control变量定义的时候给set赋值的处理函数是generic_set_cmd。 generic_set_cmd的分析: - 前面的f_audio_alloc函数中定义了一个schedule work,对应的执行函数是mixer_cmd_work。因此这里的schedule_work(&uac1->cmd_work)实际上就是给kernel的调度器提交了一个执行mixer_cmd_work的执行任务。 通过调度器执行的mixer_cmd_work函数来负责audio control功能在应用层的生效处理,其执行分析如下: g_audio_notify利用linux声卡驱动的ALSA架构来实现与应用层之间的通信。在u_audio.c文件中定义: - 需要注意,上面的g_audio_notify的实现非常简略。实际上只提供了一个volume调整上传应用层的机制,如果需要增加其他audio control的控制,就要在以上代码的基础上增加对应的处理逻辑,可以通过id把不同的命令区分开来,告诉应用层究竟需要对哪个audio control进行控制。 那么问题来了,上面的g_audio->uac->volume_ctl究竟是什么?为什么snd_ctl_notify函数可以向应用层发出volume control变化的通知? 这是因为UAC的实现借助了Linux的ALSA网卡驱动框架。ALSA网卡驱动框架提供了一个kcontrol的机制来实现在应用层和kernel层进行音频参数控制功能。 每个音频参数的设置都对应一个kcontrol结构体,也就是所谓的struct snd_kcontrol,把这个结构体定义并填充好以后,注册到内容中。应用层就可以访问到kernel中注册的所有kcontrol的列表,需要控制哪个参数,就通过对应的kcontrol控制的接口来进行控制即可。 那么对于volume control这个kcontrol而言,其定义及其在kernel中的注册流程如下: - 所以如果还要增加新的audio control的控制类型,就需要像上面的volume control一样,先定义清楚这个新的audio control对应的snd_kcontrol_new结构体,然后调用snd_ctl_add把这个kcontrol注册到内核中。这样后续就可以在g_audio_notify中通过snd_ctl_notify函数向应用层发出audio control command了。
- 23 Mar, 2022
基于Linux UAC gadget驱动实现Audio Control处理-应用层
基于对SigmaStar Webcam方案源代码的学习,通过三篇文章的篇幅,整体总结了SigmaStar提供的在基于Linux kernel的UAC gadget设备中增加audio control控制功能的完整工作流程: 1. 应用层 2. 内核层1-Audio Control Get类命令的处理流程 3. 内核层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函数的解析: mixer_subscribe_events mixer_subscribe_events的实现比较简单,实际上就是通过ioctl code SNDRV_CTL_IOCTL_SUBSCRIBE_EVENTS开启或者关闭对内核ALSA control事件的订阅; Audio Control消息处理循环结构 - 通过在事件监听和处理循环中,通过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函数中读取其内核中的设置值,并在应用层设置功能。