Condividi tramite


Inviare trasferimenti USB isochronous da un'app desktop WinUSB

A partire da Windows 8.1, il set di funzioni WinUSB include API che consentono a un'applicazione desktop di trasferire dati da e verso endpoint isochronous di un dispositivo USB. Per tale applicazione, il Winusb.sys fornito da Microsoft deve essere il driver del dispositivo.

Questo articolo contiene le informazioni seguenti:

  • Breve panoramica dei trasferimenti isochronous.
  • Trasferire il calcolo del buffer in base ai valori dell'intervallo dell'endpoint.
  • Invio di trasferimenti che in lettura e scrittura di dati isochronous tramite Funzioni WinUSB.

API importanti

A partire da Windows 8.1, il set di funzioni WinUSB include API che consentono a un'applicazione desktop di trasferire i dati da e verso endpoint isocroni di un dispositivo USB. Per tale applicazione, il Winusb.sys fornito da Microsoft deve essere il driver del dispositivo.

Un dispositivo USB può supportare endpoint isochronous per trasferire dati dipendenti dal tempo a una velocità costante, ad esempio con streaming audio/video. Non esiste una consegna garantita. Una buona connessione non deve eliminare pacchetti, non è normale o previsto perdere pacchetti, ma il protocollo isochronos è tollerante di tali perdite.

Il controller host invia o riceve dati durante periodi di tempo riservati sul bus, vengono chiamati intervalli di bus. L'unità di intervallo del bus dipende dalla velocità del bus. Per la velocità completa, si tratta di fotogrammi a 1 millisecondi, per velocità elevata e SuperSpeed, si tratta di microframe da 250 microsecondi.

Il controller host esegue il polling del dispositivo a intervalli regolari. Per le operazioni di lettura, quando l'endpoint è pronto per inviare dati, il dispositivo risponde inviando dati nell'intervallo del bus. Per scrivere nel dispositivo, il controller host invia i dati.

Quanti dati possono inviare l'app in un intervallo di servizio

Il termine pacchetto isochronous in questo argomento fa riferimento alla quantità di dati trasferiti in un intervallo di servizio. Tale valore viene calcolato dallo stack di driver USB e l'app può ottenere il valore durante la query degli attributi della pipe.

Le dimensioni di un pacchetto isochronous determinano le dimensioni del buffer di trasferimento allocato dall'app. Il buffer deve terminare a un limite di frame. La dimensione totale del trasferimento dipende dalla quantità di dati che l'app vuole inviare o ricevere. Dopo aver avviato il trasferimento dall'app, l'host pacchettizza il buffer di trasferimento in modo che in ogni intervallo l'host possa inviare o ricevere i byte massimi consentiti per intervallo.

Per un trasferimento dati, non vengono usati tutti gli intervalli di bus. In questo argomento gli intervalli del bus usati sono denominati intervalli di servizio.

Come calcolare il frame in cui vengono trasmessi i dati

L'app può scegliere di specificare il frame in uno dei due modi seguenti:

  • Automaticamente. In questa modalità l'app indica allo stack di driver USB di inviare il trasferimento nel frame appropriato successivo. L'app deve anche specificare se il buffer è un flusso continuo in modo che lo stack di driver possa calcolare il frame iniziale.
  • Specifica del frame di avvio successivo al frame corrente. L'app deve prendere in considerazione la latenza tra il momento in cui l'app avvia il trasferimento e quando lo stack di driver USB lo elabora.

Discussione di esempio di codice

Gli esempi di questo argomento illustrano l'uso di queste funzioni WinUSB:

In questo argomento verranno letti e scritti 30 millisecondi di dati in tre trasferimenti a un dispositivo ad alta velocità. La pipe è in grado di trasferire 1024 byte in ogni intervallo di servizio. Poiché l'intervallo di polling è 1, i dati vengono trasferiti in ogni microframe di un frame. Il totale di 30 fotogrammi conterrà 30*8*1024 byte.

Le chiamate di funzione per l'invio di trasferimenti di lettura e scrittura sono simili. L'app alloca un buffer di trasferimento abbastanza grande per contenere tutti e tre i trasferimenti. L'app registra il buffer per una determinata pipe chiamando WinUsb_RegisterIsochBuffer. La chiamata restituisce un handle di registrazione usato per inviare il trasferimento. Il buffer viene riutilizzato per i trasferimenti successivi e l'offset nel buffer viene modificato per inviare o ricevere il set successivo di dati.

Tutti i trasferimenti nell'esempio vengono inviati in modo asincrono. Per questo motivo, l'app alloca una matrice di struttura OVERLAPPED con tre elementi, uno per ogni trasferimento. L'app fornisce eventi in modo che possa ricevere una notifica al termine del trasferimento e recuperare i risultati dell'operazione. Per questo motivo, in ogni struttura OVERLAPPED nella matrice, l'app alloca un evento e imposta l'handle nel membro hEvent .

Questa immagine mostra tre trasferimenti di lettura usando la funzione WinUsb_ReadIsochPipeAsap . La chiamata specifica offset e lunghezza di ogni trasferimento. Il valore del parametro ContinueStream è FALSE per indicare un nuovo flusso. Successivamente, l'app richiede che i trasferimenti successivi vengano pianificati immediatamente dopo l'ultimo frame della richiesta precedente per consentire lo streaming continuo dei dati. Il numero di pacchetti isocroni viene calcolato come pacchetti per fotogramma * numero di fotogrammi; 8*10. Per questa chiamata, l'app non deve preoccuparsi del calcolo del numero di frame iniziale.

funzione winusb per il trasferimento di lettura isochronous.

Questa immagine mostra tre trasferimenti di scrittura usando la funzione WinUsb_WriteIsochPipe . La chiamata specifica offset e lunghezza di ogni trasferimento. In questo caso, l'app deve calcolare il numero di frame in cui il controller host può avviare l'invio di dati. In output, la funzione riceve il numero di frame del frame che segue l'ultimo frame usato nel trasferimento precedente. Per ottenere il frame corrente, l'app chiama WinUsb_GetCurrentFrameNumber. A questo punto, l'app deve assicurarsi che il frame di inizio del trasferimento successivo sia successivo al frame corrente, in modo che lo stack di driver USB non rilasci i pacchetti in ritardo. A tale scopo, l'app chiama WinUsb_GetAdjustedFrameNumber per ottenere un numero di frame corrente realistico (questo è più tardi del numero di frame corrente ricevuto). Per essere sul lato sicuro, l'app aggiunge cinque fotogrammi e quindi invia il trasferimento.

funzione winusb per il trasferimento di scrittura isochronous.

Al termine di ogni trasferimento, l'app ottiene i risultati del trasferimento chiamando WinUsb_GetOverlappedResult. Il parametro bWait è impostato su TRUE in modo che la chiamata non venga restituita fino al completamento dell'operazione. Per i trasferimenti di lettura e scrittura, il parametro lpNumberOfBytesTransferred è sempre 0. Per un trasferimento di scrittura, l'app presuppone che se l'operazione è stata completata correttamente, tutti i byte sono stati trasferiti. Per un trasferimento in lettura, il membro Length di ogni pacchetto isochrono (USBD_ISO_PACKET_DESCRIPTOR), contiene il numero di byte trasferiti in tale pacchetto, per intervallo. Per ottenere la lunghezza totale, l'app aggiunge tutti i valori Length .

Al termine, l'app rilascia gli handle del buffer isochrono chiamando WinUsb_UnregisterIsochBuffer.

Prima di iniziare

Assicurarsi che,

  • Il driver del dispositivo è il driver fornito da Microsoft: WinUSB (Winusb.sys). Tale driver è incluso nella cartella \Windows\System32\. Per altre informazioni, vedere Installazione di WinUSB (Winusb.sys).

  • In precedenza è stato ottenuto un handle di interfaccia WinUSB nel dispositivo chiamando WinUsb_Initialize. Tutte le operazioni vengono eseguite usando tale handle. Leggere Come accedere a un dispositivo USB usando funzioni WinUSB.

  • L'impostazione dell'interfaccia attiva include endpoint isochronous. In caso contrario, non è possibile accedere alle pipe per gli endpoint di destinazione.

Passaggio 1: Trovare la pipe isochronous nell'impostazione attiva

  1. Ottenere l'interfaccia USB con gli endpoint isocroni chiamando WinUsb_QueryInterfaceSettings.
  2. Enumerare le pipe dell'impostazione dell'interfaccia che definisce gli endpoint.
  3. Per ogni endpoint ottenere le proprietà della pipe associate in una struttura WINUSB_PIPE_INFORMATION_EX chiamando WinUsb_QueryPipeEx. Struttura WINUSB_PIPE_INFORMATION_EX recuperata che contiene informazioni sulla pipe isocrona. La struttura contiene informazioni sulla pipe, sul tipo, sull'ID e così via.
  4. Controllare i membri della struttura per determinare se si tratta della pipe che deve essere utilizzata per i trasferimenti. In caso affermativo, archiviare il valore PipeId . Nel codice del modello aggiungere membri alla struttura DEVICE_DATA, definita in Device.h.

In questo esempio viene illustrato come determinare se l'impostazione attiva dispone di endpoint isocroni e di ottenere informazioni su di esse. In questo esempio il dispositivo è un dispositivo SuperMUTT. Il dispositivo ha due endpoint isocroni nell'interfaccia predefinita, ovvero l'impostazione alternativa 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;
}

Il dispositivo SuperMUTT definisce i relativi endpoint isocroni nell'interfaccia predefinita, impostando 1. Il codice precedente ottiene i valori PipeId e li archivia nella struttura DEVICE_DATA.

Passaggio 2: Ottenere informazioni sull'intervallo sulla pipe isocrona

Ottenere quindi altre informazioni sulla pipe ottenuta nella chiamata a WinUsb_QueryPipeEx.

  • Dimensioni trasferimento

    1. Dalla struttura WINUSB_PIPE_INFORMATION_EX recuperata ottenere i valori MaximumBytesPerInterval e Interval .

    2. A seconda della quantità di dati isocroni che si desidera inviare o ricevere, calcolare le dimensioni del trasferimento. Si consideri ad esempio questo calcolo:

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

      Nell'esempio le dimensioni del trasferimento vengono calcolate per 10 millisecondi di dati isocroni.

  • Numero di pacchetti isocroniSi consideri ad esempio questo calcolo:

    Per calcolare il numero totale di pacchetti isocroni necessari per contenere l'intero trasferimento. Queste informazioni sono necessarie per i trasferimenti di lettura e calcolati come . >IsochInTransferSize / pipe.MaximumBytesPerInterval;

Questo esempio mostra l'aggiunta di codice al passaggio 1 e ottiene i valori di intervallo per le pipe isocrone.


#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;
       }
}

...

Nel codice precedente l'app ottiene Interval e MaximumBytesPerInterval da WINUSB_PIPE_INFORMATION_EX per calcolare le dimensioni del trasferimento e il numero di pacchetti isocroni necessari per il trasferimento in lettura. Per entrambi gli endpoint isocroni, Interval è 1. Questo valore indica che tutti i microframe del frame contengono dati. In base a questo, per inviare 10 millisecondi di dati, sono necessari 10 fotogrammi, la dimensione totale del trasferimento è 10*1024*8 byte e 80 pacchetti isocroni, ogni 1024 byte di lunghezza.

Passaggio 3: Inviare un trasferimento di scrittura per inviare dati a un endpoint OUT isocrono

Questa procedura riepiloga i passaggi per la scrittura di dati in un endpoint isocrono.

  1. Allocare un buffer contenente i dati da inviare.
  2. Se si inviano i dati in modo asincrono, allocare e inizializzare una struttura OVERLAPPED che contiene un handle a un oggetto evento allocato dal chiamante. La struttura deve essere inizializzata su zero. In caso contrario, la chiamata non riesce.
  3. Registrare il buffer chiamando WinUsb_RegisterIsochBuffer.
  4. Avviare il trasferimento chiamando WinUsb_WriteIsochPipeAsap. Se si vuole specificare manualmente il frame in cui verranno trasferiti i dati, chiamare invece WinUsb_WriteIsochPipe .
  5. Ottenere i risultati del trasferimento chiamando WinUsb_GetOverlappedResult.
  6. Al termine, rilasciare l'handle del buffer chiamando WinUsb_UnregisterIsochBuffer, l'handle dell'evento sovrapposto e il buffer di trasferimento.

Di seguito è riportato un esempio che illustra come inviare un trasferimento di scrittura.

#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;
}

Passaggio 4: Inviare un trasferimento in lettura per ricevere dati da un endpoint IN isocrono

Questa procedura riepiloga i passaggi per la lettura dei dati da un endpoint isocrono.

  1. Allocare un buffer di trasferimento che riceverà i dati alla fine del trasferimento. Le dimensioni del buffer devono essere basate sulla dimensione del trasferimento calcolata nel passaggio 1. Il buffer di trasferimento deve terminare in corrispondenza di un limite di frame.
  2. Se si inviano i dati in modo asincrono, allocare una struttura OVERLAPPED che contiene un handle a un oggetto evento allocato dal chiamante. La struttura deve essere inizializzata su zero. In caso contrario, la chiamata non riesce.
  3. Registrare il buffer chiamando WinUsb_RegisterIsochBuffer.
  4. In base al numero di pacchetti isocroni calcolati nel passaggio 2, allocare una matrice di pacchetti isocroni (USBD_ISO_PACKET_DESCRIPTOR).
  5. Avviare il trasferimento chiamando WinUsb_ReadIsochPipeAsap. Se si vuole specificare manualmente il frame di inizio in cui verranno trasferiti i dati, chiamare invece WinUsb_ReadIsochPipe .
  6. Ottenere i risultati del trasferimento chiamando WinUsb_GetOverlappedResult.
  7. Al termine, rilasciare l'handle del buffer chiamando WinUsb_UnregisterIsochBuffer, l'handle di evento sovrapposto, la matrice di pacchetti isocroni e il buffer di trasferimento.

Ecco un esempio che mostra come inviare un trasferimento in lettura chiamando WinUsb_ReadIsochPipeAsap e 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;
}