新闻  |   论坛  |   博客  |   在线研讨会
骁龙820A汽车与智能设备间进行USB音频分享方案介绍—USB虚拟双声卡
车载技术工程师 | 2017-12-21 14:22:14    阅读:1963   发布文章

上一篇《骁龙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设备才能正确的返回描述符信息。

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
419147953  2017-12-21 16:31:58 

您好,博主。看了您的三篇博客我受益匪浅,请问您可以提供联系方式吗 ,有一些问题想向您请教一下。

推荐文章
最近访客