Xilinx社区
首页 > 技术专栏 > 骁龙820A汽车与智能设备间进行USB音频分享方案介绍—USB虚拟双声卡
骁龙820A汽车与智能设备间进行USB音频分享方案介绍—USB虚拟双声卡
来源:技术专家 时间:2017-12-25

上一篇《骁龙820A汽车与智能设备间进行USB音频分享方案介绍--UAC》对涉及到的技术关键细节作进一步的详细分析,这一篇是基于UAC的USB虚拟双声卡

前两篇文章已经介绍了UAC的基本原理,这次来说说USB虚拟双声卡基本思路。

USB虚拟双声卡的基本思路就是依据USB驱动中的复合描述符,根据驱动的配置,在驱动中用声音子系统创建出了两个声卡。

USB 驱动中的复合描述符

如 USB 规范所述,每个 USB 设备都会提供一组分层描述符来定义其功能。在顶层,每个设备具有一个或多个 USB 配置描述符,其中每一个都具有一个或多个接口描述符。配置是相互排斥的,因此,一次只能选择一种配置来进行操作。

USB复合设备 Compound Device内嵌Hub和多个Function,每个Function都相当于一个独立的USB外设,有自己的PID/VID,复合设备其实就是几个设备通过一个USB Hub形成的单一设备;

USB复合设备一般用Interface Association DescriptorIAD)实现,就是在要合并的接口前加上IAD描述符。IADInterface Association Descriptor,功能是把多个接口定义为一个类设备。

在配置中,接口和接口集合独立进行管理。

IAD描述符简介:

typedef struct _USBInterfaceAssociationDescriptor {
  BYTE  bLength:                  0x08        //描述符大小
    BYTE  bDescriptorType:          0x0B        //IAD描述符类型
    BYTE  bFirstInterface:          0x00        //起始接口
    BYTE  bInterfaceCount:          0x02        //接口数
    BYTE  bFunctionClass:           0x0E        //类型代码
    BYTE  bFunctionSubClass:        0x03        //子类型代码
    BYTE  bFunctionProtocol:        0x00        //协议代码
    BYTE  iFunction:                0x04        //描述字符串索引
}

bFirstInterface:  表示功能中第一个接口的编号

bInterfaceCount:表示接口集合中有多少个接口。IAD接口集合中的接口必须是连续的(接口编号列表中不能有空格),因此具有第一个接口号的计数足以指定集合中的所有接口。

在描述符级别,每个接口都通过 USB_INTERFACE_DESCRIPTOR 结构中 bInterfaceNumber 成员的唯一值来表示。

接口的函数通过 bInterfaceClassbInterfaceSubClass 以及同一结构的 bInterfaceProtocol 成员(连同随后的类特定描述符)来表示。

有关描述符的详细信息,请参阅 文章最后的USB 描述符。


驱动中用声音子系统接口创建出了两个声卡:

Create first PCM deviceCreate second PCM device.

以下为实现虚拟双声卡的关键代码实例:

static int snd_uac2_probe_second(struct platform_device *pdev)
{
    struct snd_uac2_chip_second *uac2 = pdev_to_uac2_second(pdev);
    struct snd_card *card;
    struct snd_pcm *pcm;
    int err;

    /* Choose any slot, with no id */
    err = snd_card_create(-1, NULL, THIS_MODULE, 0, &card);
    if (err < 0)
        return err;

    uac2->card = card;

    /*
     * Create first PCM device
     * Create a substream only for non-zero channel streams
     */
    err = snd_pcm_new(uac2->card, "UAC2 PCM SECOND", 0,
                   p_chmask_second ? 1 : 0, c_chmask_second ? 1 : 0, &pcm);
    if (err < 0)
        goto snd_fail;

    strcpy(pcm->name, "UAC2 PCM SECOND");
    pcm->private_data = uac2;

    uac2->pcm = pcm;

    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops_second);
    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops_second);

    strcpy(card->driver, "UAC2_Gadget_Second");
    strcpy(card->shortname, "UAC2_Gadget_Second");
    sprintf(card->longname, "UAC2_Gadget_Second %i", pdev->id);

    snd_card_set_dev(card, &pdev->dev);

    snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
        snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX_SECOND);

    err = snd_card_register(card);
    if (!err) {
        platform_set_drvdata(pdev, card);
        return 0;
    }

snd_fail:
    snd_card_free(card);

    uac2->pcm = NULL;
    uac2->card = NULL;

    return err;
}

static int snd_uac2_remove_second(struct platform_device *pdev)
{
    struct snd_card *card = platform_get_drvdata(pdev);

    if (card)
        return snd_card_free(card);

    return 0;
}

static int alsa_uac2_init_second(struct audio_dev_second *agdev)
{
    struct snd_uac2_chip_second *uac2 = &agdev->uac2;
    int err;

    uac2->pdrv.probe = snd_uac2_probe_second;
    uac2->pdrv.remove = snd_uac2_remove_second;
    uac2->pdrv.driver.name = uac2_name_second;

    uac2->pdev.id = 0;
    uac2->pdev.name = uac2_name_second;

    /* Register snd_uac2 driver */
    err = platform_driver_register(&uac2->pdrv);
    if (err)
        return err;

    /* Register snd_uac2 device */
    err = platform_device_register(&uac2->pdev);
    if (err)
        platform_driver_unregister(&uac2->pdrv);

    return err;
}

在同一条USB线上启用第二块音频卡,一般情况下不需要再定义标志了

这里包括一些Playback 默认的频率,Playback 默认的位

捕捉(USB-OUT)默认立体声, 相关的频率   

定义宏,保持两个声卡的同步

驱动程序实现一个简单的UAC_2拓扑, 采集和回放采样率是独立的
由两个时钟源控制
注意:将采样率转换为我们的全速格式 ,否则这将会导致溢出,确定下一个数据包中的帧数

接下来我们不能驱使太多的bad xfers否则将可能导致ISOCH xfer失败。
注意:播放停止后清除缓冲区
往下就就是注册snd_uac2驱动程序
创建第一个PCM设备,仅为非零通道流创建子流
注意:必须为bFirstInterface设置正确的接口号,只有类特定的请求应该到达这里,初始化可配置的参数.

上述就是和USB虚拟双声卡相关的一些基础知识。

[ 附录 ]

USB描述符

USB 描述符信息存储在USB设备中,在枚举过程中,USB主机会向USB设备发送GetDescriptor请求,USB设备在收到这个请求之后,会将USB 描述符信息返回给USB主机,USB主机分析返回来的数据,判断出该设备是哪一种USB设备,建立相应的数据链接通道。那么USB描述符信息到底是一个什 么样的数据呢,USB协议中有详细描述。

通用的USB描述符信息包括设备描述符、配置描述符、接口描述符和端点描述符,具体不同的USB设备还包括其它类型的描述符,例如,USB鼠标、键盘还包括HID描述符和报告描述符,还有可能包括字符串描述符,USB协议中对描述符类型定义如下:


所有的描述符信息都是通过发送GetDescriptor请求得到的,但是USB设备也不知道你要获取的是哪种描述符,所以还需要在GetDescriptor请求中指定描述符的类型以及描述符的长度,这样USB设备才能正确的返回描述符信息。


more汽车电子市场动态
more技术专栏
more专家答疑
问:配置高通pmd9607的mpp管脚, 设置为模拟输入一直量不到电压,请教。
答:需要注意,MPP管脚并不是所有MPP管脚都可以配置的,请参考spec说明,你需要配置的pin有没有限制。通常模拟输入的话,还要配置ANA_IN_CTL等寄存器,需要外接模拟量,然后读取该pin的值的寄存器中HKADC值。
问:专家请指导: sensor厂家给的sensor相关资料(chromatix + lib)是支持前置摄像头的,“支持”主要是我这边验证过前置已经点亮。 后置摄像也用同sensor, 但用这套相关资料就点不亮了。 kernel层确认已经PROBE, 同时在/dev下有media0 media1,请协助该如何完成后置的点亮. p.s: 1. 该板后置如果使用其他sensor,可以点亮前后置摄像头; 2. 将后置摄像头拆卸, 可点亮前置, 不拆卸后置,同样可以点亮前置; 3. 如果前置后置一样, 前后置都点不亮, 但kernel层确认都probe, dev下有camera0,camera1,camera2; 4. 拆卸前置,仅后置, 依然无法点亮, kernel已经probe,dev下有camera0,camera1 针对前后置同sensor, 在sensor_init.c增加2sensor, 分别命名为: sensor, sensor_rear,同时在sensor_Libs目录下增加sensor,sensor_rear目录,主要是针对lib部分, 同时对lib中camera_id和position做对应修改(前:CAMERA_1,1,后: CAMERA_0,0) 写的太多, 一句话说明下问题: 前后置同sensor如何同时点亮前后置sensor, 是否需要修改lib中的参数,如何修改?还是其他问题?
答:从现象描述看,可以从以下几点排查。 1,sensor的配置,通常后置sensor 4lanes,而前置sensor 2lanes。换不同型号的sensor可以点亮,说明这些配置可能没有修改 导致一些问题。 2,重点看下,“拆卸前置,仅后置, 依然无法点亮,dev下有camera0,camera1,”,对比下前后置不同型号 枚举dev也是这样,看看HAL层dumpimage检查图像是否正常。 3,camera id枚举冲突,无法区分两颗相同的sensor,tuning参数调用是否正确。
问:请教专家,UE 链路层怎么样才能主动和NODE B断开。 设备和基站没有OTA log,也不太清楚是什么原因导致的连接断开。
答:可以通过AT命令CREG可以离线和在线网络。不知道是否是您需要的情况。 http://blog.chinaunix.net/uid-149881-id-2780145.html
问:有没有懂高通平台root 和解网络锁的高手?
答:eng和userdebug版本上通过adb root。你是否需要如下的方法么, https://zhidao.baidu.com/question/557640730.html
Copyright ©2000-2015 ELECTRONIC ENGINEERING & PRODUCT WORLD. All rights reserved.
京ICP备12027778号-2 北京市公安局备案:1101082052