Partager via


Envoyer des transferts isochrones USB à partir d’une application de bureau WinUSB

À compter de Windows 8.1, l’ensemble des fonctions WinUSB dispose d’API qui permettent à une application de bureau de transférer des données vers et depuis des points de terminaison isochronaux d’un périphérique USB. Pour une telle application, le Winusb.sys fourni par Microsoft doit être le pilote de périphérique.

Cet article fournit les informations suivantes :

  • Brève vue d’ensemble des transferts isochroneuses.
  • Calcul de la mémoire tampon de transfert en fonction des valeurs d’intervalle de point de terminaison.
  • Envoi de transferts qui lisent et écrivent des données isochroneuses à l’aide des fonctions WinUSB.

API importantes

À compter de Windows 8.1, l’ensemble des fonctions WinUSB dispose d’API qui permettent à une application de bureau de transférer des données vers et depuis des points de terminaison isochronaux d’un périphérique USB. Pour une telle application, le Winusb.sys fourni par Microsoft doit être le pilote de périphérique.

Un périphérique USB peut prendre en charge des points de terminaison isochroques pour transférer des données dépendantes du temps à un rythme régulier, par exemple avec le streaming audio/vidéo. La livraison n’est pas garantie. Une bonne connexion ne doit pas supprimer de paquets, il n’est pas normal ou prévu de perdre des paquets, mais le protocole isochroneuse est tolérant à ces pertes.

Le contrôleur hôte envoie ou reçoit des données pendant des périodes réservées sur le bus, sont appelées intervalles de bus. L’unité d’intervalle de bus dépend de la vitesse du bus. Pour la vitesse maximale, il s’agit d’images de 1 milliseconde, pour les images haute vitesse et SuperSpeed, il s’agit de microframes de 250 microsecondes.

Le contrôleur hôte interroge l’appareil à intervalles réguliers. Pour les opérations de lecture, lorsque le point de terminaison est prêt à envoyer des données, l’appareil répond en envoyant des données dans l’intervalle de bus. Pour écrire sur l’appareil, le contrôleur hôte envoie des données.

Quantité de données que l’application peut envoyer dans un intervalle de service

Le terme paquet isochronous dans cette rubrique fait référence à la quantité de données transférées dans un intervalle de service. Cette valeur est calculée par la pile de pilotes USB et l’application peut obtenir la valeur lors de l’interrogation des attributs de canal.

La taille d’un paquet isochrone détermine la taille de la mémoire tampon de transfert que l’application alloue. La mémoire tampon doit se terminer à une limite de trame. La taille totale du transfert dépend de la quantité de données que l’application souhaite envoyer ou recevoir. Une fois le transfert lancé par l’application, l’hôte envoie un paquet à la mémoire tampon de transfert afin que, dans chaque intervalle, l’hôte puisse envoyer ou recevoir le nombre maximal d’octets autorisés par intervalle.

Pour un transfert de données, tous les intervalles de bus ne sont pas utilisés. Dans cette rubrique, les intervalles de bus utilisés sont appelés intervalles de service.

Comment calculer la trame dans laquelle les données sont transmises

L’application peut choisir de spécifier le cadre de l’une des deux manières suivantes :

  • Automatiquement. Dans ce mode, l’application indique à la pile de pilotes USB d’envoyer le transfert dans le cadre approprié suivant. L’application doit également spécifier si la mémoire tampon est un flux continu afin que la pile de pilotes puisse calculer le frame de démarrage.
  • Spécification de l’image de début postérieure à l’image actuelle. L’application doit prendre en compte la latence entre le moment où l’application démarre le transfert et le moment où la pile de pilotes USB le traite.

Discussion d’exemple de code

Les exemples de cette rubrique illustrent l’utilisation de ces fonctions WinUSB :

Dans cette rubrique, nous allons lire et écrire 30 millisecondes de données en trois transferts vers un appareil haute vitesse. Le canal est capable de transférer 1 024 octets dans chaque intervalle de service. Étant donné que l’intervalle d’interrogation est de 1, les données sont transférées dans chaque microframe d’une trame. Au total, 30 images contiennent 30*8*1024 octets.

Les appels de fonction pour l’envoi de transferts de lecture et d’écriture sont similaires. L’application alloue une mémoire tampon de transfert suffisamment grande pour contenir les trois transferts. L’application inscrit la mémoire tampon pour un canal particulier en appelant WinUsb_RegisterIsochBuffer. L’appel retourne un handle d’inscription qui est utilisé pour envoyer le transfert. La mémoire tampon est réutilisée pour les transferts suivants et le décalage dans la mémoire tampon est ajusté pour envoyer ou recevoir le jeu de données suivant.

Tous les transferts de l’exemple sont envoyés de manière asynchrone. Pour cela, l’application alloue un tableau de structure CHEVAUCHÉE avec trois éléments, un pour chaque transfert. L’application fournit des événements afin qu’elle puisse être avertie lorsque les transferts sont terminés et récupérer les résultats de l’opération. Pour cela, dans chaque structure CHEVAUCHEMENT DANS le tableau, l’application alloue un événement et définit le handle dans le membre hEvent .

Cette image montre trois transferts de lecture à l’aide de la fonction WinUsb_ReadIsochPipeAsap . L’appel spécifie le décalage et la longueur de chaque transfert. La valeur du paramètre ContinueStream est FALSE pour indiquer un nouveau flux. Après cela, l’application demande que les transferts suivants soient planifiés immédiatement après la dernière trame de la demande précédente pour permettre la diffusion en continu des données. Le nombre de paquets isochrones est calculé en tant que paquets par image * nombre de trames ; 8*10. Pour cet appel, l’application n’a pas besoin de se soucier du calcul du numéro de trame de démarrage.

Fonction winusb pour le transfert de lecture isochronous.

Cette image montre trois transferts d’écriture à l’aide de la fonction WinUsb_WriteIsochPipe . L’appel spécifie le décalage et la longueur de chaque transfert. Dans ce cas, l’application doit calculer le numéro de trame dans lequel le contrôleur hôte peut commencer à envoyer des données. Lors de la sortie, la fonction reçoit le numéro d’image de l’image qui suit la dernière image utilisée dans le transfert précédent. Pour obtenir le frame actuel, l’application appelle WinUsb_GetCurrentFrameNumber. À ce stade, l’application doit s’assurer que l’image de début du transfert suivant est postérieure à l’image actuelle, afin que la pile du pilote USB ne supprime pas les paquets en retard. Pour ce faire, l’application appelle WinUsb_GetAdjustedFrameNumber afin d’obtenir un numéro d’image actuel réaliste (il est postérieur au numéro de trame actuel reçu). Pour être sûr, l’application ajoute cinq images supplémentaires, puis envoie le transfert.

Fonction winusb pour le transfert d’écriture isochroneuse.

Une fois chaque transfert terminé, l’application obtient les résultats du transfert en appelant WinUsb_GetOverlappedResult. Le paramètre bWait est défini sur TRUE afin que l’appel ne retourne pas tant que l’opération n’est pas terminée. Pour les transferts en lecture et en écriture, le paramètre lpNumberOfBytesTransferred est toujours égal à 0. Pour un transfert d’écriture, l’application part du principe que si l’opération s’est terminée avec succès, tous les octets ont été transférés. Pour un transfert en lecture, le membre Length de chaque paquet isochronous (USBD_ISO_PACKET_DESCRIPTOR), contient le nombre d’octets transférés dans ce paquet, par intervalle. Pour obtenir la longueur totale, l’application ajoute toutes les valeurs length .

Lorsque vous avez terminé, l’application libère les handles de mémoire tampon isochroneuse en appelant WinUsb_UnregisterIsochBuffer.

Avant de commencer

Assurez-vous que,

  • Le pilote de périphérique est le pilote fourni par Microsoft : WinUSB (Winusb.sys). Ce pilote est inclus dans le dossier \Windows\System32\. Pour plus d’informations, consultez Installation de WinUSB (Winusb.sys).

  • Vous avez précédemment obtenu un handle d’interface WinUSB pour l’appareil en appelant WinUsb_Initialize. Toutes les opérations sont effectuées à l’aide de ce handle. Consultez Comment accéder à un périphérique USB à l’aide de fonctions WinUSB.

  • Le paramètre d’interface active a des points de terminaison isochroques. Sinon, vous ne pouvez pas accéder aux canaux pour les points de terminaison cibles.

Étape 1 : Rechercher le canal isochron dans le paramètre actif

  1. Obtenez l’interface USB qui a les points de terminaison isochronieux en appelant WinUsb_QueryInterfaceSettings.
  2. Énumérez les canaux du paramètre d’interface qui définit les points de terminaison.
  3. Pour chaque point de terminaison, obtenez les propriétés de canal associées dans une structure de WINUSB_PIPE_INFORMATION_EX en appelant WinUsb_QueryPipeEx. La structure WINUSB_PIPE_INFORMATION_EX récupérée qui contient des informations sur le canal isochronieux. La structure contient des informations sur le canal, son type, son id, etc.
  4. Vérifiez les membres de la structure pour déterminer s’il s’agit du canal qui doit être utilisé pour les transferts. Si c’est le cas, stockez la valeur PipeId . Dans le code du modèle, ajoutez des membres à la structure DEVICE_DATA, définie dans Device.h.

Cet exemple montre comment déterminer si le paramètre actif a des points de terminaison isochroneux et obtenir des informations à leur sujet. Dans cet exemple, l’appareil est un appareil SuperMUTT. L’appareil a deux points de terminaison isochrones dans l’interface par défaut, autre paramètre 1.


typedef struct _DEVICE_DATA {

    BOOL                    HandlesOpen;
    WINUSB_INTERFACE_HANDLE WinusbHandle;
    HANDLE                  DeviceHandle;
    TCHAR                   DevicePath[MAX_PATH];
    UCHAR                   IsochOutPipe;
    UCHAR                   IsochInPipe;

} DEVICE_DATA, *PDEVICE_DATA;

HRESULT
       GetIsochPipes(
       _Inout_ PDEVICE_DATA DeviceData
       )
{
       BOOL result;
       USB_INTERFACE_DESCRIPTOR usbInterface;
       WINUSB_PIPE_INFORMATION_EX pipe;
       HRESULT hr = S_OK;
       UCHAR i;

       result = WinUsb_QueryInterfaceSettings(DeviceData->WinusbHandle,
              0,
              &usbInterface);

       if (result == FALSE)
       {
              hr = HRESULT_FROM_WIN32(GetLastError());
              printf(_T("WinUsb_QueryInterfaceSettings failed to get USB interface.\n"));
              CloseHandle(DeviceData->DeviceHandle);
              return hr;
       }

       for (i = 0; i < usbInterface.bNumEndpoints; i++)
       {
              result = WinUsb_QueryPipeEx(
                     DeviceData->WinusbHandle,
                     1,
                     (UCHAR) i,
                     &pipe);

              if (result == FALSE)
              {
                     hr = HRESULT_FROM_WIN32(GetLastError());
                     printf(_T("WinUsb_QueryPipeEx failed to get USB pipe.\n"));
                     CloseHandle(DeviceData->DeviceHandle);
                     return hr;
              }

              if ((pipe.PipeType == UsbdPipeTypeIsochronous) && (!(pipe.PipeId == 0x80)))
              {
                     DeviceData->IsochOutPipe = pipe.PipeId;
              }
              else if (pipe.PipeType == UsbdPipeTypeIsochronous)
              {
                     DeviceData->IsochInPipe = pipe.PipeId;
              }
       }

       return hr;
}

L’appareil SuperMUTT définit ses points de terminaison isochronieux dans l’interface par défaut, au paramètre 1. Le code précédent obtient les valeurs PipeId et les stocke dans la structure DEVICE_DATA.

Étape 2 : Obtenir des informations d’intervalle sur le canal isochronieux

Ensuite, obtenez plus d’informations sur le canal que vous avez obtenu lors de l’appel à WinUsb_QueryPipeEx.

  • Taille du transfert

    1. À partir de la structure WINUSB_PIPE_INFORMATION_EX récupérée, obtenez les valeurs MaximumBytesPerInterval et Interval .

    2. En fonction de la quantité de données isochrone que vous souhaitez envoyer ou recevoir, calculez la taille du transfert. Par exemple, considérez ce calcul :

      TransferSize = ISOCH_DATA_SIZE_MS * pipeInfoEx.MaximumBytesPerInterval * (8 / pipeInfoEx.Interval);

      Dans l’exemple, la taille du transfert est calculée pour 10 millisecondes de données isochrone.

  • Nombre de paquets isochronieuxPar exemple, considérez ce calcul :

    Pour calculer le nombre total de paquets isochroniques requis pour contenir l’intégralité du transfert. Ces informations sont requises pour les transferts en lecture et calculées comme , >IsochInTransferSize / pipe.MaximumBytesPerInterval;.

Cet exemple montre comment ajouter du code à l’exemple d’étape 1 et obtenir les valeurs d’intervalle des canaux isochronieux.


#define ISOCH_DATA_SIZE_MS   10

typedef struct _DEVICE_DATA {

    BOOL                    HandlesOpen;
    WINUSB_INTERFACE_HANDLE WinusbHandle;
    HANDLE                  DeviceHandle;
    TCHAR                   DevicePath[MAX_PATH];
                UCHAR                   IsochOutPipe;
                UCHAR                   IsochInPipe;
                ULONG                   IsochInTransferSize;
                ULONG                   IsochOutTransferSize;
                ULONG                   IsochInPacketCount;

} DEVICE_DATA, *PDEVICE_DATA;


...

if ((pipe.PipeType == UsbdPipeTypeIsochronous) && (!(pipe.PipeId == 0x80)))
{
       DeviceData->IsochOutPipe = pipe.PipeId;

       if ((pipe.MaximumBytesPerInterval == 0) || (pipe.Interval == 0))
       {
         hr = E_INVALIDARG;
             printf("Isoch Out: MaximumBytesPerInterval or Interval value is 0.\n");
             CloseHandle(DeviceData->DeviceHandle);
             return hr;
       }
       else
       {
             DeviceData->IsochOutTransferSize =
                 ISOCH_DATA_SIZE_MS *
                 pipe.MaximumBytesPerInterval *
                 (8 / pipe.Interval);
       }
}
else if (pipe.PipeType == UsbdPipeTypeIsochronous)
{
       DeviceData->IsochInPipe = pipe.PipeId;

       if (pipe.MaximumBytesPerInterval == 0 || (pipe.Interval == 0))
       {
         hr = E_INVALIDARG;
             printf("Isoch Out: MaximumBytesPerInterval or Interval value is 0.\n");
             CloseHandle(DeviceData->DeviceHandle);
             return hr;
       }
       else
       {
             DeviceData->IsochInTransferSize =
                 ISOCH_DATA_SIZE_MS *
                 pipe.MaximumBytesPerInterval *
                 (8 / pipe.Interval);

             DeviceData->IsochInPacketCount =
                  DeviceData->IsochInTransferSize / pipe.MaximumBytesPerInterval;
       }
}

...

Dans le code précédent, l’application obtient Interval et MaximumBytesPerInterval à partir de WINUSB_PIPE_INFORMATION_EX pour calculer la taille de transfert et le nombre de paquets isochroneux requis pour le transfert en lecture. Pour les deux points de terminaison isochronieux, l’intervalle est 1. Cette valeur indique que tous les microframes de la trame contiennent des données. Sur cette base, pour envoyer 10 millisecondes de données, vous avez besoin de 10 images, la taille totale du transfert est de 10*1024*8 octets et de 80 paquets isochrone, chacun de 1 024 octets.

Étape 3 : Envoyer un transfert d’écriture pour envoyer des données à un point de terminaison OUT isochronisé

Cette procédure résume les étapes d’écriture de données dans un point de terminaison isochronisé.

  1. Allouez une mémoire tampon qui contient les données à envoyer.
  2. Si vous envoyez les données de manière asynchrone, allouez et initialisez une structure CHEVAUCHEMENT QUI contient un handle à un objet d’événement alloué à l’appelant. La structure doit être initialisée à zéro, sinon l’appel échoue.
  3. Inscrivez la mémoire tampon en appelant WinUsb_RegisterIsochBuffer.
  4. Démarrez le transfert en appelant WinUsb_WriteIsochPipeAsap. Si vous souhaitez spécifier manuellement la trame dans laquelle les données seront transférées, appelez WinUsb_WriteIsochPipe à la place.
  5. Obtenez les résultats du transfert en appelant WinUsb_GetOverlappedResult.
  6. Lorsque vous avez terminé, relâchez le handle de mémoire tampon en appelant WinUsb_UnregisterIsochBuffer, le handle d’événement qui se chevauche et la mémoire tampon de transfert.

Voici un exemple qui montre comment envoyer un transfert d’écriture.

#define ISOCH_TRANSFER_COUNT   3

VOID
    SendIsochOutTransfer(
    _Inout_ PDEVICE_DATA DeviceData,
    _In_ BOOL AsapTransfer
    )
{
    PUCHAR writeBuffer;
    LPOVERLAPPED overlapped;
    ULONG numBytes;
    BOOL result;
    DWORD lastError;
    WINUSB_ISOCH_BUFFER_HANDLE isochWriteBufferHandle;
    ULONG frameNumber;
    ULONG startFrame;
    LARGE_INTEGER timeStamp;
    ULONG i;
    ULONG totalTransferSize;

    isochWriteBufferHandle = INVALID_HANDLE_VALUE;
    writeBuffer = NULL;
    overlapped = NULL;

    printf(_T("\n\nWrite transfer.\n"));

    totalTransferSize = DeviceData->IsochOutTransferSize * ISOCH_TRANSFER_COUNT;

    if (totalTransferSize % DeviceData->IsochOutBytesPerFrame != 0)
    {
        printf(_T("Transfer size must end at a frame boundary.\n"));
        goto Error;
    }

    writeBuffer = new UCHAR[totalTransferSize];

    if (writeBuffer == NULL)
    {
        printf(_T("Unable to allocate memory.\n"));
        goto Error;
    }

    ZeroMemory(writeBuffer, totalTransferSize);

    overlapped = new OVERLAPPED[ISOCH_TRANSFER_COUNT];
    if (overlapped == NULL)
    {
        printf("Unable to allocate memory.\n");
        goto Error;

    }

    ZeroMemory(overlapped, (sizeof(OVERLAPPED) * ISOCH_TRANSFER_COUNT));

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {
        overlapped[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        if (overlapped[i].hEvent == NULL)
        {
            printf("Unable to set event for overlapped operation.\n");
            goto Error;

        }
    }

    result = WinUsb_RegisterIsochBuffer(
        DeviceData->WinusbHandle,
        DeviceData->IsochOutPipe,
        writeBuffer,
        totalTransferSize,
        &isochWriteBufferHandle);

    if (!result)
    {
        printf(_T("Isoch buffer registration failed.\n"));
        goto Error;
    }

    result = WinUsb_GetCurrentFrameNumber(
                DeviceData->WinusbHandle,
                &frameNumber,
                &timeStamp);

    if (!result)
    {
        printf(_T("WinUsb_GetCurrentFrameNumber failed.\n"));
        goto Error;
    }

    startFrame = frameNumber + 5;

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {

        if (AsapTransfer)
        {
            result = WinUsb_WriteIsochPipeAsap(
                isochWriteBufferHandle,
                DeviceData->IsochOutTransferSize * i,
                DeviceData->IsochOutTransferSize,
                (i == 0) ? FALSE : TRUE,
                &overlapped[i]);

            printf(_T("Write transfer sent by using ASAP flag.\n"));
        }
        else
        {

            printf("Transfer starting at frame %d.\n", startFrame);

            result = WinUsb_WriteIsochPipe(
                isochWriteBufferHandle,
                i * DeviceData->IsochOutTransferSize,
                DeviceData->IsochOutTransferSize,
                &startFrame,
                &overlapped[i]);

            printf("Next transfer frame %d.\n", startFrame);

        }

        if (!result)
        {
            lastError = GetLastError();

            if (lastError != ERROR_IO_PENDING)
            {
                printf("Failed to send write transfer with error %x\n", lastError);
            }
        }
    }

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {
        result = WinUsb_GetOverlappedResult(
            DeviceData->WinusbHandle,
            &overlapped[i],
            &numBytes,
            TRUE);

        if (!result)
        {
            lastError = GetLastError();

            printf("Write transfer %d with error %x\n", i, lastError);
        }
        else
        {
            printf("Write transfer %d completed. \n", i);

        }
    }

Error:
    if (isochWriteBufferHandle != INVALID_HANDLE_VALUE)
    {
        result = WinUsb_UnregisterIsochBuffer(isochWriteBufferHandle);
        if (!result)
        {
            printf(_T("Failed to unregister isoch write buffer. \n"));
        }
    }

    if (writeBuffer != NULL)
    {
        delete [] writeBuffer;
    }

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {
        if (overlapped[i].hEvent != NULL)
        {
            CloseHandle(overlapped[i].hEvent);
        }

    }

    if (overlapped != NULL)
    {
        delete [] overlapped;
    }

    return;
}

Étape 4 : Envoyer un transfert de lecture pour recevoir des données à partir d’un point de terminaison IN isochronisé

Cette procédure récapitule les étapes de lecture des données à partir d’un point de terminaison isochronisé.

  1. Allouez une mémoire tampon de transfert qui recevra les données à la fin du transfert. La taille de la mémoire tampon doit être basée sur le calcul de la taille de transfert à l’étape 1. La mémoire tampon de transfert doit se terminer à une limite d’image.
  2. Si vous envoyez les données de manière asynchrone, allouez une structure CHEVAUCHEMENT QUI contient un handle à un objet d’événement alloué à l’appelant. La structure doit être initialisée à zéro, sinon l’appel échoue.
  3. Inscrivez la mémoire tampon en appelant WinUsb_RegisterIsochBuffer.
  4. En fonction du nombre de paquets isochronieux calculés à l’étape 2, allouez un tableau de paquets isochronieux (USBD_ISO_PACKET_DESCRIPTOR).
  5. Démarrez le transfert en appelant WinUsb_ReadIsochPipeAsap. Si vous souhaitez spécifier manuellement le cadre de début dans lequel les données seront transférées, appelez WinUsb_ReadIsochPipe à la place.
  6. Obtenez les résultats du transfert en appelant WinUsb_GetOverlappedResult.
  7. Lorsque vous avez terminé, relâchez le handle de mémoire tampon en appelant WinUsb_UnregisterIsochBuffer, le handle d’événement qui se chevauche, le tableau de paquets isochronaux et la mémoire tampon de transfert.

Voici un exemple qui montre comment envoyer un transfert de lecture en appelant WinUsb_ReadIsochPipeAsap et WinUsb_ReadIsochPipe.

#define ISOCH_TRANSFER_COUNT   3

VOID
    SendIsochInTransfer(
    _Inout_ PDEVICE_DATA DeviceData,
    _In_ BOOL AsapTransfer
    )
{
    PUCHAR readBuffer;
    LPOVERLAPPED overlapped;
    ULONG numBytes;
    BOOL result;
    DWORD lastError;
    WINUSB_ISOCH_BUFFER_HANDLE isochReadBufferHandle;
    PUSBD_ISO_PACKET_DESCRIPTOR isochPackets;
    ULONG i;
    ULONG j;

    ULONG frameNumber;
    ULONG startFrame;
    LARGE_INTEGER timeStamp;

    ULONG totalTransferSize;

    readBuffer = NULL;
    isochPackets = NULL;
    overlapped = NULL;
    isochReadBufferHandle = INVALID_HANDLE_VALUE;

    printf(_T("\n\nRead transfer.\n"));

    totalTransferSize = DeviceData->IsochOutTransferSize * ISOCH_TRANSFER_COUNT;

    if (totalTransferSize % DeviceData->IsochOutBytesPerFrame != 0)
    {
        printf(_T("Transfer size must end at a frame boundary.\n"));
        goto Error;
    }

    readBuffer = new UCHAR[totalTransferSize];

    if (readBuffer == NULL)
    {
        printf(_T("Unable to allocate memory.\n"));
        goto Error;
    }

    ZeroMemory(readBuffer, totalTransferSize);

    overlapped = new OVERLAPPED[ISOCH_TRANSFER_COUNT];
    ZeroMemory(overlapped, (sizeof(OVERLAPPED) * ISOCH_TRANSFER_COUNT));

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {
        overlapped[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        if (overlapped[i].hEvent == NULL)
        {
            printf("Unable to set event for overlapped operation.\n");
            goto Error;
        }
    }

    isochPackets = new USBD_ISO_PACKET_DESCRIPTOR[DeviceData->IsochInPacketCount * ISOCH_TRANSFER_COUNT];
    ZeroMemory(isochPackets, DeviceData->IsochInPacketCount * ISOCH_TRANSFER_COUNT);

    result = WinUsb_RegisterIsochBuffer(
        DeviceData->WinusbHandle,
        DeviceData->IsochInPipe,
        readBuffer,
        DeviceData->IsochInTransferSize * ISOCH_TRANSFER_COUNT,
        &isochReadBufferHandle);

    if (!result)
    {
        printf(_T("Isoch buffer registration failed.\n"));
        goto Error;
    }

    result = WinUsb_GetCurrentFrameNumber(
                DeviceData->WinusbHandle,
                &frameNumber,
                &timeStamp);

    if (!result)
    {
        printf(_T("WinUsb_GetCurrentFrameNumber failed.\n"));
        goto Error;
    }

    startFrame = frameNumber + 5;

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {
        if (AsapTransfer)
        {
            result = WinUsb_ReadIsochPipeAsap(
                isochReadBufferHandle,
                DeviceData->IsochInTransferSize * i,
                DeviceData->IsochInTransferSize,
                (i == 0) ? FALSE : TRUE,
                DeviceData->IsochInPacketCount,
                &isochPackets[i * DeviceData->IsochInPacketCount],
                &overlapped[i]);

            printf(_T("Read transfer sent by using ASAP flag.\n"));
        }
        else
        {
            printf("Transfer starting at frame %d.\n", startFrame);

            result = WinUsb_ReadIsochPipe(
                isochReadBufferHandle,
                DeviceData->IsochInTransferSize * i,
                DeviceData->IsochInTransferSize,
                &startFrame,
                DeviceData->IsochInPacketCount,
                &isochPackets[i * DeviceData->IsochInPacketCount],
                &overlapped[i]);

            printf("Next transfer frame %d.\n", startFrame);
        }

        if (!result)
        {
            lastError = GetLastError();

            if (lastError != ERROR_IO_PENDING)
            {
                printf("Failed to start a read operation with error %x\n", lastError);
            }
        }
    }

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {
        result = WinUsb_GetOverlappedResult(
            DeviceData->WinusbHandle,
            &overlapped[i],
            &numBytes,
            TRUE);

        if (!result)
        {
            lastError = GetLastError();

            printf("Failed to read with error %x\n", lastError);
        }
        else
        {
            numBytes = 0;
            for (j = 0; j < DeviceData->IsochInPacketCount; j++)
            {
                numBytes += isochPackets[j].Length;
            }

            printf("Requested %d bytes in %d packets per transfer.\n", DeviceData->IsochInTransferSize, DeviceData->IsochInPacketCount);
        }

        printf("Transfer %d completed. Read %d bytes. \n\n", i+1, numBytes);
    }

Error:
    if (isochReadBufferHandle != INVALID_HANDLE_VALUE)
    {
        result = WinUsb_UnregisterIsochBuffer(isochReadBufferHandle);
        if (!result)
        {
            printf(_T("Failed to unregister isoch read buffer. \n"));
        }
    }

    if (readBuffer != NULL)
    {
        delete [] readBuffer;
    }

    if (isochPackets != NULL)
    {
        delete [] isochPackets;
    }

    for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
    {
        if (overlapped[i].hEvent != NULL)
        {
            CloseHandle(overlapped[i].hEvent);
        }
    }

    if (overlapped != NULL)
    {
        delete [] overlapped;
    }
    return;
}