65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Arduino Due 上获取 USB 网络摄像头视频流 - 第七部分:完成枚举过程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (10投票s)

2015年7月10日

CPOL

9分钟阅读

viewsIcon

23521

downloadIcon

370

视频流之前的最后一篇文章

引言

第6部分在这里

到目前为止,我已掌握完成USB设备枚举所需的所有信息。对于所有USB设备,枚举过程包括设置设备地址、配置和接口备用设置。视频类设备包括一个额外的步骤,称为协商,在此过程中会进行几次控制传输:设置和获取视频探测控制,以及在设置备用设置之前提交控制。

本文还介绍了带和不带数据阶段的OUT控制传输。

设置地址

在此之前,所有传输都是IN传输,意味着从设备到主机。另一方面,设置地址传输是OUT传输:主机将地址发送到设备,设备使用它。此传输很有趣,因为它没有数据阶段,SETUP事务(SETUP阶段中唯一的事务)本身就包含地址号。

可以看出,没有数据阶段,因为SETUP阶段的DATA0包包含了Set Address传输所需的所有信息。设备在主机IN包之后必须在HANDSHAKE阶段以空DATA包响应。

启动SETUP事务的SETUP包并具有进入DATA0包的数据的函数如下所示

void USBD_SetAddressBegin()
{
    usb_setup_req_t ControlRequest;
    
    ControlRequest.bmRequestType = USB_REQ_DIR_OUT | USB_REQ_TYPE_STANDARD | USB_REQ_RECIP_DEVICE;
    ControlRequest.bRequest = USB_REQ_SET_ADDRESS;
    ControlRequest.wValue = DeviceAddress;
    ControlRequest.wIndex = 0;
    ControlRequest.wLength = 0;
    
    HCD_SendCtrlSetupPacket(0, USB_REQ_DIR_OUT, ControlRequest, NULL, 0, USBD_SetAddressEnd);
}

DeviceAddress定义为0x05(您可以指定1到127之间的任何数字)。wIndexwLength必须为零,因为没有数据阶段,HCD_SendCtrlSetupPacket函数调用中的参数ptrBuffer为NULL,TransferBufferSize为零。

此类请求在[2, p.256]中有描述。

此函数在显示完整配置后立即调用(请参阅上一篇文章)

void USBD_GetConfigurationDescriptorEnd(uint16_t ByteReceived)
{
    uint16_t TotalSize;
    
    if(4 == ByteReceived)
    {
        TotalSize = (Buffer[3] << 8);
        TotalSize |= Buffer[2];
        
        PrintStr("Configuration Total size is ");
        PrintDEC(TotalSize);
        PrintStr(".\r\n");
        
        PrintStr("\r\nGETTIN FULL CONFIGURATION.\r\n");
        USBD_GetConfigurationDescriptorBegin(TotalSize);
    }
    else
    {
        PrintStr("Full configuration has been obtained.\r\n");
        USBD_ParseFullConfigurationDescriptor(Buffer, ByteReceived);
        
        PrintStr("Setting device address.\r\n");
        USBD_SetAddressBegin();
    }
}

由于之前没有进行过OUT传输,中断处理程序将无法正确处理它,因此必须进行更改。

//...

//Start DATA stage    
if(hcd_ControlStructure.ControlRequestStage == USB_CTRL_REQ_STAGE_SETUP)
{
    if(hcd_ControlStructure.Direction == USB_REQ_DIR_IN)    //Now need to send IN packet
    {
        //Change to IN phase
        hcd_ControlStructure.ControlRequestStage = USB_CTRL_REQ_STAGE_DATA_IN;
        HCD_SendCtrlInPacket();
        return;
    }
    if(hcd_ControlStructure.Direction == USB_REQ_DIR_OUT)
    {
        if(hcd_ControlStructure.ByteCount == 0)             //Empty data-stage
        {
            hcd_ControlStructure.ControlRequestStage = USB_CTRL_REQ_STAGE_ZLP_IN;
            HCD_SendCtrlInPacket();
            return;
        }
    }
}

//...

发送SETUP包后,“SETUP已发送中断”将我们带到HCD_HandleControlPipeInterrupt函数中的这段代码。粗体是我为处理无数据阶段的OUT传输而添加的内容。如果传输是OUT,它会检查要发送的字节数量,如果为零(在本例中为真),它会指定下一个阶段是HANDSHAKE,并且必须发生空IN事务。HCD_SendCtrlInPacket函数发送IN包,这反过来会启动HANDSHAKE阶段,此函数只发送IN令牌,并在第5篇文章中进行了描述。

在发送HANDSAHE阶段的IN令牌后,设备会响应一个空的DATA包,中断处理程序必须识别该条件。

//...

//Received IN Data interrupt
if(UOTGHS->UOTGHS_HSTPIPISR[0] & UOTGHS_HSTPIPISR_RXINI)    
{
    UOTGHS->UOTGHS_HSTPIPICR[0] = UOTGHS_HSTPIPICR_RXINIC;        //Ack interrupt    
       
    //Data stage: IN token has been send and DATA0/1 came in    
    if(hcd_ControlStructure.ControlRequestStage == USB_CTRL_REQ_STAGE_DATA_IN)
    {
        HCD_GetCtrlInDataPacket();                                //Getting data
        return;
    }
    if(hcd_ControlStructure.ControlRequestStage == USB_CTRL_REQ_STAGE_ZLP_IN)
    {
        (*hcd_ControlStructure.TransferEnd)(0);                   //Notifies about completion
        return;
    }        
    return;
}

//...   

如果处理程序从设备接收到IN包,并且阶段是ZLP_IN(零长度包IN),则表示HANDSHAKE阶段已完成,可以通知调用者传输完成。TransferEnd函数指针在USBD_SetAddressBegin中指定为USBD_SetAddressEnd函数。

void USBD_SetAddressBegin()
{
    usb_setup_req_t ControlRequest;
    
    ControlRequest.bmRequestType = USB_REQ_DIR_OUT | USB_REQ_TYPE_STANDARD | USB_REQ_RECIP_DEVICE;
    ControlRequest.bRequest = USB_REQ_SET_ADDRESS;
    ControlRequest.wValue = DeviceAddress;
    ControlRequest.wIndex = 0;
    ControlRequest.wLength = 0;
    
    HCD_SendCtrlSetupPacket(0, USB_REQ_DIR_OUT, ControlRequest, NULL, 0, USBD_SetAddressEnd);
}

USBD_SetAddressEnd打印关于地址设置完成的消息,并启动下一个配置步骤,即设置配置(参见Delay function部分)。

延迟函数

设备地址设置后,必须给设备2毫秒的恢复间隔[2, p.246]。在此间隔结束时,设备必须开始接受指向新分配地址的后续请求。

我将创建一个处理这种情况的函数

void HCD_StartDelayed(void (*TransferStart)(void), uint16_t Pause)
{
    hcd_ControlStructure.TransferStart = TransferStart;
    hcd_ControlStructure.Pause = Pause;
}

因此,下一个枚举操作(指定配置)将在地址设置结束后的2毫秒内开始。

void USBD_SetAddressEnd(uint16_t ByteReceived)
{
    PrintStr("Device address has been set.\r\n");
    PrintStr("Setting device configuration.\r\n");
    HCD_StartDelayed(USBD_SetConfigurationBegin, 2);
}

注意:由于我使用Print函数,因此此2毫秒的暂停不是必需的,因为通过RS232发送会产生比2毫秒更大的延迟。

设置配置

USB设备具有一个或多个配置,其中一个可以同时处于活动状态。我的设备只有一个配置(请参阅前面关于描述符的文章)。

要指定配置,其编号应作为参数传递。我从配置描述符中获取此编号(请参阅上一篇文章)。

所以参数是1。设置配置的传输与设置地址的传输类似——它们都是OUT传输,没有数据阶段,因此不需要更改USB中断处理程序。启动传输的函数

void USBD_SetConfigurationBegin()
{
    usb_setup_req_t ControlRequest;
    
    ControlRequest.bmRequestType = USB_REQ_DIR_OUT | USB_REQ_TYPE_STANDARD | USB_REQ_RECIP_DEVICE;
    ControlRequest.bRequest = USB_REQ_SET_CONFIGURATION;
    ControlRequest.wValue = DeviceConfiguration;
    ControlRequest.wIndex = 0;
    ControlRequest.wLength = 0;
    
    if(HCD_InitiatePipeZero(DeviceAddress))
    {
        HCD_SendCtrlSetupPacket(DeviceAddress, USB_REQ_DIR_OUT, ControlRequest, 
                                                                NULL, 0, USBD_SetConfigurationEnd);
    }
}

在我的情况下,DeviceConfiguration定义为1。请注意bRequest常量以及它与Set Address函数中使用的常量有何不同。地址已更改,必须重新初始化管道0。函数USBD_SetConfigurationEnd将在Set Configuration传输完成后调用。

void USBD_SetConfigurationEnd(uint16_t ByteReceived)
{
    PrintStr("Device configuration has been set.\r\n");
    PrintStr("Setting video probe control.\r\n");    
    USBD_SetVideoProbeControlBegin();
}

此函数打印一些消息并开始下一个枚举步骤。

流协商

在启动流之前,必须协商其参数,例如格式、帧、有效负载大小等。主机提出所需的值,设备确认这些值或降低其无法支持的值。如果指定的值不正确,设备将返回正确的值。

UVC规范有专门的图片来描述该过程

因此,首先主机提出值 - PROBE_CONTROL(SET_CUR),然后它从设备读取这些值 - PROBE_CONTROL(GET_CUR),最后它设置(提交)这些值 - COMMIT_CONTROL(SET_CUR)。

SET_CUR和GET_CUR只是常量,其使用方式与bRequest字段中的USB_REQ_SET_ADDRESS或USB_REQ_SET_CONFIGURATION相同。我在USB_Specification.h文件中定义了它们。

//Class-specific UVC requests
#define  USB_REQ_SET_CUR                    0x01
#define  USB_REQ_GET_CUR                    0x81

协商使用特殊结构[3, p.97]

在C语言中

typedef struct
{
    uint16_t bmHint;
    uint8_t bFormatIndex;
    uint8_t bFrameIndex;
    uint32_t dwFrameInterval;
    uint16_t wKeyFrameRate;
    uint16_t wPFrameRate;
    uint16_t wCompQuality;
    uint16_t wCompQualitySize;
    uint16_t wDelay;
    uint16_t Padding;
    uint32_t dwMaxVideoFrameSize;
    uint32_t dwMaxPayloadTransferSize;
    //uint32_t dwClockFrequency;
    //uint8_t bmFramingInfo;
    //uint8_t bPreferedVersion;
    //uint8_t bMinVersion;
    //uint8_t bMaxVersion;
} uvc_video_probe_and_commit_controls_t;

注释掉的字段在UVC规范1.0版本中不存在,它们从1.1版本开始出现。从wKeyFramRatewCompQualitySize的字段与我的摄像头不相关的压缩格式有关,因此这些字段将为零。

带数据阶段的OUT传输

协商过程中的第一个动作是将建议的值发送到设备。此传输包括将填充的uvc_video_probe_and_commit_controls_t结构发送到设备的数据阶段。图形上,这种传输如下所示

由于之前没有发生过,HCD中断处理程序应进行更改,以正确处理带数据阶段的OUT事务。首先,在“SETUP发送中断”部分

//...

if(hcd_ControlStructure.Direction == USB_REQ_DIR_OUT)
{
    if(hcd_ControlStructure.ByteCount == 0)             //Empty data-stage
    {
        hcd_ControlStructure.ControlRequestStage = USB_CTRL_REQ_STAGE_ZLP_IN;
        HCD_SendCtrlInPacket();
        return;
    }
    else                                                //Sending OUT-packet
    {
        hcd_ControlStructure.ControlRequestStage = USB_CTRL_REQ_STAGE_DATA_OUT;
        HCD_SendCtrlOutData();
    }
}

//...

当字节计数不为零时,意味着OUT传输有数据要发送,并且需要数据阶段。指定传输阶段,并调用函数HCD_SendCtrlOutData,该函数将提供数据并启动数据阶段。

void HCD_SendCtrlOutData(void)
{
    PrintStr("Sending DATA0/1 to device.\r\n");
    
    uint8_t ControlPipeSize = (UOTGHS->UOTGHS_HSTPIPCFG[0] & UOTGHS_HSTPIPCFG_PSIZE_Msk) 
                                                                   >> UOTGHS_HSTPIPCFG_PSIZE_Pos;
    uint8_t volatile *ptrSendBuffer = 
                                 (uint8_t *)&(((volatile uint8_t(*)[0x8000])UOTGHS_RAM_ADDR)[0]);
    
    if (hcd_ControlStructure.Index == hcd_ControlStructure.ByteCount)
    {
        //Everything has been sent, start HANDSHAKE stage by changing direction
        hcd_ControlStructure.ControlRequestStage = USB_CTRL_REQ_STAGE_ZLP_IN;
        HCD_SendCtrlInPacket();
        return;
    }
    
    //Moving data to pipe 0 FIFO buffer
    while ((hcd_ControlStructure.Index < hcd_ControlStructure.ByteCount) && ControlPipeSize)
    {
        *ptrSendBuffer++ = hcd_ControlStructure.ptrBuffer[hcd_ControlStructure.Index];
        hcd_ControlStructure.Index++;
        ControlPipeSize--;
    }
    
    //Start OUT transaction by sending OUT packet
    HCD_SentCtrlOutPacket();
}

首先,它获取管道最大传输大小,以避免指定超出设备处理能力的数据。然后它获取指向应填充数据的管道FIFO缓冲区的指针。然后它检查是否所有数据都已发送,如果已发送,则启动IN包以启动HANDSHAKE阶段。如果有数据要发送,它只需逐字节复制到FIFO缓冲区,并检查是否已达到数据末尾或最大管道大小。一旦复制了一部分数据,OUT事务就会通过发送OUT包启动(SAM3X自动处理OUT事务的数据包发送和从设备获取ACK)。

此函数之后,可能还有两个后续操作:

1. 如果有更多数据要发送(阶段仍为DATA_OUT),则启动下一个OUT事务。

2. 捕获传输结束(此处指定HANDSHAKE阶段ZLP_IN),并通知调用方。

这两个动作都在控制端点中断处理方法HCD_HandleControlPipeInterrupt中识别。一旦OUT事务完成,“OUT数据中断”触发,这会引导到函数中同名部分。

//...

//Received OUT Data interrupt
if(UOTGHS->UOTGHS_HSTPIPISR[0] & UOTGHS_HSTPIPISR_TXOUTI)
{
    UOTGHS->UOTGHS_HSTPIPICR[0] = UOTGHS_HSTPIPICR_TXOUTIC;    //Ack interrupt
      
    if(hcd_ControlStructure.ControlRequestStage == USB_CTRL_REQ_STAGE_DATA_OUT)
    {
        HCD_SendCtrlOutData();                                //Sending more data
        return;
    }
        
    //HANDSHAKE stage, ACK for empty OUT is received
    if(hcd_ControlStructure.ControlRequestStage == USB_CTRL_REQ_STAGE_ZLP_OUT)
    {
        //Notifies about transaction completion
        (*hcd_ControlStructure.TransferEnd)(hcd_ControlStructure.Index);    
    }
    return;
}

//...

如果阶段保持为DATA_OUT,则会反复调用HCD_SendCtrlOutData,直到没有数据要发送。

第二个动作在以下部分处理

//...

//Received IN Data interrupt
if(UOTGHS->UOTGHS_HSTPIPISR[0] & UOTGHS_HSTPIPISR_RXINI)    
{
    UOTGHS->UOTGHS_HSTPIPICR[0] = UOTGHS_HSTPIPICR_RXINIC;    //Ack interrupt    
        
    //Data stage: IN token has been send and DATA0/1 came in    
    if(hcd_ControlStructure.ControlRequestStage == USB_CTRL_REQ_STAGE_DATA_IN)
    {
        HCD_GetCtrlInData();                                  //Getting data
        return;
    }
    if(hcd_ControlStructure.ControlRequestStage == USB_CTRL_REQ_STAGE_ZLP_IN)
    {
        (*hcd_ControlStructure.TransferEnd)(0);               //Notifies about completion
        return;
    }        
    return;
}

//...

将调用TransferEnd函数指针中指定的函数指针,以通知整个传输完成。

向设备建议参数 - PROBE_CONTROL(SET_CUR)

此时,所有低级(HCD)函数都已完成,我们可以继续进行枚举过程。这是上一篇文章中的一张图,其中包含必要的数字:

请注意,格式号只有1,最低帧号为5——160x120像素。最低速度替代设置号为1——每次事务128字节。

启动传输的函数

void USBD_SetVideoProbeControlBegin(void)
{
    uvc_video_probe_and_commit_controls_t Parameters;
    Parameters.bmHint = 0x0001;
    Parameters.bFormatIndex = 1;                //format #1
    Parameters.bFrameIndex = 5;                 //160x120 frame size
    Parameters.dwFrameInterval = 333333;        //in 100ns units (from frame descriptor)
    Parameters.wKeyFrameRate = 0;               //unused
    Parameters.wPFrameRate = 0;                 //unused
    Parameters.wCompQuality = 0;                //unused
    Parameters.wCompQualitySize = 0;            //unused
    Parameters.wDelay = 0;                      //set by device
    Parameters.dwMaxVideoFrameSize = 0;         //set by device
    Parameters.dwMaxPayloadTransferSize = 0;    //set by device    
    
    USBD_CopyVideoProbeControl(Buffer, &Parameters);
    PrintVideoProbeAndCommitControls(Buffer);
    
    usb_setup_req_t ControlRequest;    
    ControlRequest.bmRequestType = USB_REQ_DIR_OUT | USB_REQ_TYPE_CLASS | USB_REQ_RECIP_INTERFACE;
    ControlRequest.bRequest = USB_REQ_SET_CUR;
    ControlRequest.wValue = 0x0100;            //Probe control selector (01)
    ControlRequest.wIndex = 1;                 //Video Streaming interface number is 1
    ControlRequest.wLength = 26;               //Length of the returned block (parameter block)
    
    HCD_SendCtrlSetupPacket(DeviceAddress, USB_REQ_DIR_OUT, ControlRequest, 
                               Buffer, ControlRequest.wLength, USBD_SetVideoProbeControlEnd);
}

注意PrintVideoProbeAndCommitControls函数,我在这里发送参数之前调用它,并且在从设备获取参数之后立即调用它。这是为了方便比较。wValue高字节中的01表示探测控制,02表示提交控制。

我使用函数USBD_CopyVideoProbeControlParameters复制到缓冲区,我将解释为什么。我花了一些时间才明白我不能简单地这样转换Parameters

(uint8_t*)&Parameters

将其传递给HCD_SendCtrlSetupPacket函数。原因是填充,成员在结构中对齐,有时会添加填充以填充到4字节。我将用图片说明这一点

因此,如您所见,该结构实际上占用了28(而不是26)字节,并且内部有填充(蓝色标记),因为在32位ARM处理器内存中,4字节字段dwMaxVideoFrameSize不能分成两半。这就是为什么我不能简单地转换和传递此结构的变量,因为它插入了那2个无用的字节。因此,我创建了特殊函数来执行此复制操作

void USBD_CopyVideoProbeControl(uint8_t *ptrBuffer, uint8_t *ptrParameters)
{
    for(uint8_t i = 0; i < 18; i++)
        ptrBuffer[i] = ptrParameters[i];
    
    for(uint8_t i = 18; i < 26; i++)
        ptrBuffer[i] = ptrParameters[i+2];
}

此传输完成后将调用函数USBD_SetVideoProbeControlEnd

void USBD_SetVideoProbeControlEnd(uint16_t ByteReceived)
{
    PrintStr("Proposed parameter values have been sent.");
    PrintStr("\r\nGETTING VIDEO PROBE CONTROL.\r\n");
    USBD_GetVideoProbeControlBegin();
}

它打印一些调试消息并启动下一个枚举步骤。

设备对建议参数的回答 - PROBE_CONTROL(GET_CUR)

以下函数启动从设备获取答案的传输

void USBD_GetVideoProbeControlBegin(void)
{
    usb_setup_req_t ControlRequest;
    
    ControlRequest.bmRequestType = USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | USB_REQ_RECIP_INTERFACE;
    ControlRequest.bRequest = USB_REQ_GET_CUR;
    ControlRequest.wValue = 0x0100;            //Probe control selector (01)
    ControlRequest.wIndex = 1;                 //Video Streaming interface number is 1
    ControlRequest.wLength = 26;               //Length of the returned block (parameter block)
    
    HCD_SendCtrlSetupPacket(DeviceAddress, USB_REQ_DIR_IN, ControlRequest, Buffer, 
                                           ControlRequest.wLength, USBD_GetVideoProbeControlEnd);
}

bRequest的值现在是GET_GUR,方向是IN。

USBD_GetVideoProbeControlEnd获取答案,打印它并启动下一个枚举步骤

void USBD_GetVideoProbeControlEnd(uint16_t ByteReceived)
{
    PrintStr("\r\nDEVICE ANSWER:\r\n");
    PrintVideoProbeAndCommitControls(Buffer);    
    
    PrintStr("COMMITTING VIDEO PROBE CONTROL.\r\n");    
    USBD_SetVideoCommitControlBegin();
}

此和以前枚举步骤的程序输出

设备只更改了最后两个字段。帧大小可以很容易地检查,因为我已经知道YUY格式每像素有2字节,帧大小为160x120:160*120*2 = 38400字节。最大传输大小比我将要使用的要大得多。

设置流参数

一旦主机和设备协商并同意,就可以设置参数。

void USBD_SetVideoCommitControlBegin(void)
{
    usb_setup_req_t ControlRequest;
    
    ControlRequest.bmRequestType = USB_REQ_DIR_OUT | USB_REQ_TYPE_CLASS | USB_REQ_RECIP_INTERFACE;
    ControlRequest.bRequest = USB_REQ_SET_CUR;
    ControlRequest.wValue = 0x0200;            //Commit control selector (02)
    ControlRequest.wIndex = 1;                 //Video Streaming interface number is 1
    ControlRequest.wLength = 26;               //Length of the returned block (parameter block)
    
    HCD_SendCtrlSetupPacket(DeviceAddress, USB_REQ_DIR_OUT, ControlRequest, 
                                    Buffer, ControlRequest.wLength, USBD_SetVideoCommitControlEnd);
}

bRequest再次是SET_CUR,但wValue的高字节是0x02,表示COMMIT(而不是像以前的PROBE),Buffer仍然保留上一步的响应。

此传输完成后将调用USBD_SetVideoCommitControlEnd

void USBD_SetVideoCommitControlEnd(uint16_t ByteReceived)
{
    PrintStr("Video Commit Control has been set.\r\n");
    
    PrintStr("SELECTING ALTERNATE SETTING.\r\n");    
    USBD_SetInterfaceAlternativeSettingBegin();
}

此函数打印调试消息并启动下一个枚举步骤。

启动流

在上一篇文章中我们看到,流接口描述符下没有端点,但在流接口的备用设置描述符下有。所以,最后要做的是选择具有适当端点数据大小的备用设置,正如我之前所说,我将从数据大小为126字节的最低#1开始。

void USBD_SetInterfaceAlternativeSettingBegin(void)
{
    usb_setup_req_t ControlRequest;
    
    ControlRequest.bmRequestType = USB_REQ_DIR_OUT |USB_REQ_TYPE_STANDARD |USB_REQ_RECIP_INTERFACE;
    ControlRequest.bRequest = USB_REQ_SET_INTERFACE;
    ControlRequest.wValue = 1;                //Alternative setting #1: 128 bytes endpoint
    ControlRequest.wIndex = 1;                //Video Streaming interface number is #1
    ControlRequest.wLength = 0;               //No data
    
    HCD_SendCtrlSetupPacket(DeviceAddress, USB_REQ_DIR_OUT, ControlRequest, 
                                                  NULL, 0, USBD_SetInterfaceAlternateSettingEnd);
}

bRequest是SET_INTERFACE,因为此请求是针对接口的。wValue包含备用设置号,wIndex是接口号——0是视频控制,1是视频流。此传输中没有数据阶段(wLength为零)。

此传输完成后将调用USBD_SetInterfaceAlternateSettingEnd

void USBD_SetInterfaceAlternateSettingEnd(uint16_t ByteReceived)
{
    PrintStr("Alternate setting has been set.\r\n");
    
    PrintStr("PROCESSING THE STREAM.\r\n");
}

此处将启动流处理(在下一篇文章中)。

结论

我展示了如何枚举(初始化)USB网络摄像头。此处讨论的步骤适用于任何USB摄像头,并让您很好地理解要使用哪些命令(请求)、顺序以及如何应用从设备描述符中获取的参数。

在此阶段,流已准备就绪,摄像头正在生成视频,如果我开始向流端点发送IN数据包(在我的情况下为#2),我将获得带有视频数据的128字节DATAx数据包。视频格式(在我的情况下是未压缩的)规范有助于理解如何处理传入数据,因为并非所有数据都是视频数据,还有元数据有助于将视频帧的各个部分组合在一起。所有这些都将在下一篇文章中解释,我将尝试制作/上传一个视频来演示最终结果。我还将尝试以至少两种帧大小进行流式传输,因为Arduino Due的处理能力允许这样做。

源代码在此处。

第8部分在此处

© . All rights reserved.