兼容某些厂商ASIO驱动的USB Audio2.0开发

若做声卡相关开发,不得不提到的就是ASIO驱动,ASIO驱动实现了什么呢?低延迟,针对声卡的专业性,低延迟是个很重要的指标,低延时是如何应用实现的呢?

1.PC端需要有绕过WDM驱动框架的驱动模块(如:ASIO驱动方案),使得音频数据流发送到尽量底层的内核模块中。

2.设备端(MCU端),实现USB Audio 2.0 或 3.0设备开发,减少设备端缓冲区大小,实现PC专业驱动的匹配。

这里笔者主要讲讲第二点:USB Audio 2.0开发,缓冲区处理策略,兼容厂商ASIO驱动。

笔者利用NXP RT1052成功实现了如:PreSonus AudioBox iOne 声卡驱动 、 XMOS 某方案驱动完美适配的案例。

废话这么多,接下来进入正题,代码解析了。

开发USB设备,第一步就是实现其描述符,如下USB Audio 2.0描述符(这里是直接扒PreSonus的描述符,毕竟是要用他们的ASIO驱动嘛,当然描述符得基本一样才能实现驱动匹配啦)

//设备描述符
12 01 00 02  00 00 00 40  4f 19 03 04  21 01 01 02     
03 01 

//配置描述符
09 02 48 01 06 01 04 80 fa  //配置描述符 :描述符总长度,接口数量,
08 0b 00 03 01 00 20 00     //IAD 将音频设备捆绑一起:从0开始,一共3个接口:AUDIO_FUNCTION
09 04 00 00 00 01 01 20 10  //ctrl,功能协议版本 2.0 0x20
09 24 01 00 02 08 4b 00 01  //AC Header(描述主要功能): 08:IO/BOX,AC的总长度:004b = 9+8+17+12+17+12   01:主机可读控件
08 24 0a 05 03 07 00 00     //AC clock: 05 bClockID  03:时钟类型  07://Clock Type:D1~0 Clock Frequent (0x03 host programmable)  
//D3~2 Clock Validity(0x01 read only)  D7~4:Reserved 
11 24 02 01 03 06 00 05 02 00 00 00 00 20 00 00 00  //Iput terminal
0c 24 03 02 01 01 00 01 05 00 00 00  //output terminal
11 24 02 03 01 01 00 05 02 00 00 00 00 30 00 00 00  //Iput terminal
0c 24 03 04 01 03 00 03 05 00 00 00  //output terminal

//Audio Stream
09 04 01 00 00 01 02 20 11 
09 04 01 01 01 01 02 20 11 
10 24 01 02 00 01 01 00 00 00 02 00 00 00 00 20 
06 24 02 01 04 18 
07 05 81 05 68 00 01 
08 25 01 00 00 00 00 00
 
09 04 02 00 00 01 02 20 12 
09 04 02 01 02 01 02 20 12 
10 24 01 03 00 01 01 00 00 00 02 00 00 00 00 30 
06 24 02 01 04 18  
07 05 01 05 68 00 01 
08 25 01 00 00 00 00 00 
07 05 82 11 04 00 04
 
//MIDI Stream  
09 04 03 00 00 01 01 00 10 
09 24 01 00 01 09 00 01 04

09 04 04 00 02 01 03 00 00 
07 24 01 00 01 41 00 
06 24 02 01 01 00 
06 24 02 02 02 00  
09 24 03 01 03 01 02 01 00 
09 24 03 02 04 01 01 01 00 
09 05 02 02 00 02 00 00 00 
05 25 01 01 01  
09 05 83 02 00 02 00 00 00 
05 25 01 01 03

//厂商接口实现 
09 04 05 00 00 fe 01 02 50 
09 21 0d d0 07 00 04 10 01

第二步:实现USB请求

实现USB正常枚举,那么主机的请求是必不可少的。USB的请求主要分为两大类:标准请求类请求

 兼容某些厂商ASIO驱动的USB Audio2.0开发_第1张图片

标准请求:初始化接口和端点,基本等字符串描述符获取等

usb_status_t USB_DeviceCallback(usb_device_handle handle, uint32_t event, void *param)
{
    usb_status_t error = kStatus_USB_Error;
    uint16_t *temp16 = (uint16_t *)param;
    uint8_t *temp8 = (uint8_t *)param;
	usb_device_get_string_descriptor_struct_t *pStringDescriptor;

    switch (event)
    {
        case kUSB_DeviceEventBusReset:
        {
            g_composite.attach = 0U;
            g_composite.currentConfiguration = 0U;
					      
            /* Get USB speed to configure the device, including max packet size and interval of the endpoints. */
//             if (kStatus_USB_Success == USB_DeviceClassGetSpeed(CONTROLLER_ID, &g_composite.speed))
//             {
//                 USB_DeviceSetSpeed(handle, g_composite.speed);
//             }
			
			 error = kStatus_USB_Success;
        }
        break;
        case kUSB_DeviceEventSetConfiguration:
            if (0U == (*temp8))
            {
                g_composite.attach = 0U;
                g_composite.currentConfiguration = 0U;
            }
            else if (USB_COMPOSITE_CONFIGURE_INDEX == (*temp8))
            {
                g_composite.attach = 1U;
                g_composite.currentConfiguration = *temp8;
                USB_DeviceAudioCompositeSetConfigure(g_composite.audioUnified.audioSpeakerHandle, *temp8);
                USB_DeviceAudioCompositeSetConfigure(g_composite.audioUnified.audioRecorderHandle, *temp8);
							  
				//USB_DeviceBulkSetConfigure(handle,*temp8);  
															
                error = kStatus_USB_Success;
            }
            else
            {
                error = kStatus_USB_InvalidRequest;
            }
            break;
        case kUSB_DeviceEventSetInterface:
            if (g_composite.attach)
            {
                uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
                uint8_t alternateSetting = (uint8_t)(*temp16 & 0x00FFU);
			
                if (USB_AUDIO_RECORDER_STREAM_INTERFACE_INDEX == interface)
                {
                    /*主机发起录音请求时 接口将会重新初始化*/
                    USB_DeviceAudioRecorderSetInterface(g_composite.audioUnified.audioRecorderHandle, interface,
                                                        alternateSetting);
                }
                else if (USB_AUDIO_SPEAKER_STREAM_INTERFACE_INDEX == interface)
                {
                    USB_DeviceAudioSpeakerSetInterface(g_composite.audioUnified.audioSpeakerHandle, interface,
                                                       alternateSetting);
                }
						
                error = kStatus_USB_Success;
            }
            break;
        case kUSB_DeviceEventGetConfiguration:
            if (param)
            {
                *temp8 = g_composite.currentConfiguration;
                error = kStatus_USB_Success;
            }
            break;
        case kUSB_DeviceEventGetInterface:
            if (param)
            {
                uint8_t interface = (uint8_t)((*temp16 & 0xFF00U) >> 0x08U);
                if (interface < USB_DEVICE_INTERFACE_COUNT)
                {
                    *temp16 = (*temp16 & 0xFF00U) | g_composite.currentInterfaceAlternateSetting[interface];
                    error = kStatus_USB_Success;
                }
                else
                {
                    error = kStatus_USB_InvalidRequest;
                }
            }
            break;
        case kUSB_DeviceEventGetDeviceDescriptor:
            if (param)
            {
                error = USB_DeviceGetDeviceDescriptor(handle, (usb_device_get_device_descriptor_struct_t *)param);
            }
            break;
        case kUSB_DeviceEventGetConfigurationDescriptor:
            if (param)
            {
                error = USB_DeviceGetConfigurationDescriptor(handle,
                                                             (usb_device_get_configuration_descriptor_struct_t *)param);
            }
            break;
        case kUSB_DeviceEventGetStringDescriptor:
			pStringDescriptor = (usb_device_get_string_descriptor_struct_t *)param;
		    
			if(pStringDescriptor->stringIndex == 0x0c) //若主机请求该序号,则使用的是驱ASIO
			{
                iShaveASIODrivers = true;
			}
			
            if (pStringDescriptor)
            {
                error = USB_DeviceGetStringDescriptor(handle, pStringDescriptor);
            }
            break;
						

        default:
            break;
    }

    return error;
}

类请求:实现其该类的具体类容。如Audio类:控制请求(Entity ID,Control Selector,and Channel Number)  Audio 流请求(如:Feedback,Speaker,Recoder)等等(具体可参考USB Audio 2.0 标准手册) 

/*
********************************************************************************************************************
@ Brief  : Audio class callback and use function:

@ Param  : None

@ Return : None

@ Author : LYC

@  Date  : 2019 - 07 - 03
********************************************************************************************************************
*/
usb_status_t USB_DeviceAudioCompositeCallback(class_handle_t handle, uint32_t event, void *param)
{
    usb_status_t error = kStatus_USB_Error;
    usb_device_endpoint_callback_message_struct_t *ep_cb_param;
    ep_cb_param = (usb_device_endpoint_callback_message_struct_t *)param;
    uint32_t epPacketSize = ISO_IN_ENDP_PACKET_SIZE;

    switch (event)
    {
        case kUSB_DeviceAudioEventStreamSendResponse:
            /*01.判断音频流的有效性*/	
            if ((g_deviceAudioComposite->audioUnified.attach) && (ep_cb_param->length != (USB_UNINITIALIZED_VAL_32)))
            {				
                if (ep_cb_param->length == 4)							
                {
                    //非驱动模式下才会使用反馈端点				
															
                    error = USB_DeviceAudioSend(
                        g_deviceAudioComposite->audioUnified.audioSpeakerHandle, USB_AUDIO_SPEAKER_FEEDBACK_ENDPOINT,
                        audioFeedBackBuffer, 4);  //				
                }
                else
                {
                    /*启动麦克风功能*/
                    if (g_deviceAudioComposite->audioUnified.startRec == 0)
                    {
                        g_deviceAudioComposite->audioUnified.startRec = 1;
                    }

                    /*音频流信息达到缓存区一半,表示当前可以播放文件*/
                    if ((g_deviceAudioComposite->audioUnified.tdReadNumberRec >=
                         AUDIO_RECORDER_DATA_WHOLE_BUFFER_LENGTH * ISO_IN_ENDP_PACKET_SIZE / 2) &&
                        (g_deviceAudioComposite->audioUnified.startRecHalfFull == 0))
                    {
                        g_deviceAudioComposite->audioUnified.startRecHalfFull = 1;
                    }

                    /*缓冲区文件足够后,发送端点播放*/
                    if (g_deviceAudioComposite->audioUnified.startRecHalfFull)
                    {
                        USB_AUDIO_ENTER_CRITICAL();//根据缓冲区大小调整上传的Rec大小
                        epPacketSize = USB_RecorderDataMatch(USB_AudioRecorderBufferSpaceUsed());
                        USB_AUDIO_EXIT_CRITICAL();
                        
                        /*gets audioRecPacket from the audioRecDataBuff in every callback*/
                        USB_AudioRecorderGetBuffer(audioRecPacket, epPacketSize);  

                        error = USB_DeviceAudioSend(g_deviceAudioComposite->audioUnified.audioRecorderHandle,
                                                USB_AUDIO_RECORDER_STREAM_ENDPOINT, &audioRecPacket[0], epPacketSize);

                        g_deviceAudioComposite->audioUnified.usbSendTimes++;   /*记录上传次数*/
                    }
                    else  /*如果每达到缓冲区一半以上,那就只播放开头部分*/
                    {
                        error = USB_DeviceAudioSend(g_deviceAudioComposite->audioUnified.audioRecorderHandle,
                                                    USB_AUDIO_RECORDER_STREAM_ENDPOINT, &audioRecDataBuff[0],
                                                    ISO_IN_ENDP_PACKET_SIZE);
                    }
                }
            }
            break;
        case kUSB_DeviceAudioEventStreamRecvResponse:
            /*01.判断音频流的有效性*/
            if ((g_deviceAudioComposite->audioUnified.attach) && (ep_cb_param->length != (USB_UNINITIALIZED_VAL_32)))
            {			 
#if 0 
                g_SysTicCount = GET_CURRENT_COUNT();             
                g_IntervalCount = g_SysTickLastCount - g_SysTicCount;             
                g_SysTickLastCount = g_SysTicCount;
#endif					
				
                if (g_deviceAudioComposite->audioUnified.startPlay == 0)
                {
                    g_deviceAudioComposite->audioUnified.startPlay = 1;
                }
                
                /*02.收集音频流数据,准备下次播放*/  //ep_cb_param->length 为PC发给的有效数据大小 
                USB_AudioSpeakerPutBuffer(audioPlayPacket, ep_cb_param->length);
                g_deviceAudioComposite->audioUnified.usbRecvCount += ep_cb_param->length;
                g_deviceAudioComposite->audioUnified.usbRecvTimes++;

                /*如果当前音源数据达到缓冲区数量的一半,此时可以播放音乐了(相当于做了个延迟播放吧)*/
                if ((g_deviceAudioComposite->audioUnified.tdReadNumberPlay >=
                     AUDIO_SPEAKER_DATA_WHOLE_BUFFER_LENGTH * ISO_OUT_ENDP_PACKET_SIZE / 2) &&
                    (g_deviceAudioComposite->audioUnified.startPlayHalfFull == 0))
                {
                    g_deviceAudioComposite->audioUnified.startPlayHalfFull = 1;
                }                
                
                /*audioSendTimes >= usbRecvTimes && startPlayHalfFull == 1*/  //音频流将播放结束某一段

                USB_AUDIO_ENTER_CRITICAL();
                USB_AudioFeedbackDataUpdate();   //反馈机制:1.通过反馈端点  2.调整设备时钟
                USB_AUDIO_EXIT_CRITICAL();

                error = USB_DeviceAudioRecv(handle, USB_AUDIO_SPEAKER_STREAM_ENDPOINT, &audioPlayPacket[0],
                                           (ISO_OUT_ENDP_PACKET_SIZE));	  
            }
            break;

        default:
            if (param && (event > 0xFF))
            {
                error = USB_DeviceAudioRequest(handle, event, param);  //控制类相关请求,如:音量调节等
            }
            break;
    }

    return error;
}

 

第四步:同步处理机制和缓冲区处理策略

关于同步机制处理分为两类:1.调整设备Audio时钟,如44.1k采样率调整为44.12K     2.利用Feedback端点,告诉主机当前设备实际采样率大小。

/*
********************************************************************************************************************
@ Brief  : USB Audio 同步

@ Param  : None

@ Return : NONE

@ Author : LYC

@  Date  : 2020 - 05 - 07
********************************************************************************************************************
*/
void USB_AudioFeedbackDataUpdate(void)
{
	int32_t spaceSize;   //记录缓冲区大小
	int32_t pidAudioData;
    if(g_deviceAudioComposite->audioUnified.curMute == 1)
    {
        /*如果为静音,不需要计算feedback,*/
        g_deviceAudioComposite->audioUnified.timesFeedbackCalculate = 0;
        return;  
    }
	
    if (g_deviceAudioComposite->audioUnified.speakerIntervalCount != AUDIO_CALCULATE_Ff_INTERVAL)
    {
        g_deviceAudioComposite->audioUnified.speakerIntervalCount++;
        return;
    }

    g_deviceAudioComposite->audioUnified.timesFeedbackCalculate++;  

    if(g_deviceAudioComposite->audioUnified.timesFeedbackCalculate >= AUDIO_CALCULATE_Ff)
    {
		spaceSize = USB_AudioSpeakerBufferSpaceUsed();		
	
#if  IS_USE_FEEDBACK_CTRL    
		if(iShaveASIODrivers == false)
        {
			pidAudioData = xUSB_AudioPid(AUDIO_KP,AUDIO_KI,AUDIO_KD,spaceSize);

			feedbackValue += pidAudioData; 

			AUDIO_UPDATE_FEEDBACK_DATA(audioFeedBackBuffer, feedbackValue);	
        }
		
#else    //调整设备时钟    	
        if(iShaveASIODrivers == false)
        {
            pidAudioData = xUSB_AudioPid(1.0,0.0,0.3,spaceSize);

            if((spaceSize > (EXP_SPEAKERBUE + 16))|| (spaceSize < (EXP_SPEAKERBUE - 16)))
            {
                usbaudioClock.numerator -= pidAudioData/20;
            }
                    
            CLOCK_InitAudioPll(&usbaudioClock);   //更新时钟
        }		
#endif

        g_deviceAudioComposite->audioUnified.timesFeedbackCalculate = 0; 	
    }
			
    g_deviceAudioComposite->audioUnified.speakerIntervalCount = 1;
	
	/*可用于判断USB 强制断开现象*/
    g_deviceAudioComposite->audioUnified.lastAudioSendCount = g_deviceAudioComposite->audioUnified.audioSendCount;
}

如上图代码所示,笔者用很小的缓冲区,利用PID调控达到设备端的低延迟。这里也许大家注意到 iShaveASIODrivers 变量的存在,这里是为了区分主机是否安装ASIO驱动设定的,因为在ASIO驱动模式下,同步机制并没有利用上述两种方法。

第五步:ASIO驱动的兼容

 这一步比较考验技术活了,笔者首先通过Bus Hound获取了该声卡的描述符,分析。发现一个特殊的地方,该设备的最后一个接口并无端点的实现,只是简短的描述接口基本特性(在第一步描述符末尾有标注)

兼容某些厂商ASIO驱动的USB Audio2.0开发_第2张图片

在无驱动情况下设备管理器显示为(无驱动模式下,声卡是同步模式是按照第四步的描述方法实现的,音频流开始前,之后设置一个接口):

                  兼容某些厂商ASIO驱动的USB Audio2.0开发_第3张图片    

安装厂商ASIO驱动后:将同时设置两个接口,Speaker和Recoder将同时启动

兼容某些厂商ASIO驱动的USB Audio2.0开发_第4张图片

最后实现该接口后,通过Cubase测试,完美兼容了ASIO驱动

兼容某些厂商ASIO驱动的USB Audio2.0开发_第5张图片

你可能感兴趣的:(USB,Audio,ASIO,usb,asio,嵌入式)