Compartir a través de


Envío de transferencias isócrónicas USB desde una aplicación de escritorio winUSB

A partir de Windows 8.1, el conjunto de funciones winUSB tiene API que permiten a una aplicación de escritorio transferir datos hacia y desde puntos de conexión isócronos de un dispositivo USB. Para esta aplicación, el Winusb.sys proporcionado por Microsoft debe ser el controlador del dispositivo.

Este artículo ofrece la siguiente información:

  • Breve introducción a las transferencias isócrónicas.
  • Transferir el cálculo del búfer en función de los valores del intervalo de punto de conexión.
  • Envío de transferencias que leen y escriben datos isócronos mediante funciones winUSB.

API importantes

A partir de Windows 8.1, el conjunto de funciones de WinUSB tiene API que permiten a una aplicación de escritorio transferir datos hacia y desde puntos de conexión isócronos de un dispositivo USB. Para esta aplicación, el Winusb.sys proporcionado por Microsoft debe ser el controlador del dispositivo.

Un dispositivo USB puede admitir puntos de conexión isócronos para transferir datos dependientes del tiempo a una velocidad estable, como con streaming de audio y vídeo. No hay entrega garantizada. Una buena conexión no debe quitar ningún paquete, no es normal o se espera que pierda paquetes, pero el protocolo isócrono es tolerante a tales pérdidas.

El controlador de host envía o recibe datos durante períodos reservados de tiempo en el bus, se denominan intervalos de bus. La unidad de intervalo de autobús depende de la velocidad del autobús. Para velocidad completa, es de 1 milisegundos, para alta velocidad y SuperSpeed, es de 250 microsegundos.

El controlador de host sondea el dispositivo a intervalos regulares. Para las operaciones de lectura, cuando el punto de conexión está listo para enviar datos, el dispositivo responde enviando datos en el intervalo de bus. Para escribir en el dispositivo, el controlador de host envía datos.

Cantidad de datos que puede enviar la aplicación en un intervalo de servicio

El término paquete isocrono de este tema hace referencia a la cantidad de datos que se transfieren en un intervalo de servicio. Ese valor se calcula mediante la pila de controladores USB y la aplicación puede obtener el valor mientras consulta los atributos de canalización.

El tamaño de un paquete isócrono determina el tamaño del búfer de transferencia que asigna la aplicación. El búfer debe terminar en un límite de marco. El tamaño total de la transferencia depende de la cantidad de datos que la aplicación quiere enviar o recibir. Una vez iniciada la transferencia por la aplicación, el host rellena el búfer de transferencia para que, en cada intervalo, el host pueda enviar o recibir los bytes máximos permitidos por intervalo.

Para una transferencia de datos, no se usan todos los intervalos de bus. En este tema, los intervalos de bus que se usan se denominan intervalos de servicio.

Cómo calcular el marco en el que se transmiten los datos

La aplicación puede elegir especificar el marco de una de estas dos maneras:

  • Automáticamente. En este modo, la aplicación indica a la pila del controlador USB que envíe la transferencia en el siguiente marco adecuado. La aplicación también debe especificar si el búfer es una secuencia continua para que la pila de controladores pueda calcular el marco de inicio.
  • Especificando el marco de inicio que es posterior al marco actual. La aplicación debe tener en cuenta la latencia entre el momento en que la aplicación inicia la transferencia y cuando la pila del controlador USB la procesa.

Explicación del ejemplo de código

En los ejemplos de este tema se muestra el uso de estas funciones de WinUSB:

En este tema, leeremos y escribiremos 30 milisegundos de datos en tres transferencias a un dispositivo de alta velocidad. La canalización es capaz de transferir 1024 bytes en cada intervalo de servicio. Dado que el intervalo de sondeo es 1, los datos se transfieren en cada microframe de un fotograma. El total de 30 fotogramas llevará 30*8*1024 bytes.

Las llamadas de función para enviar transferencias de lectura y escritura son similares. La aplicación asigna un búfer de transferencia lo suficientemente grande como para contener las tres transferencias. La aplicación registra el búfer de una canalización determinada llamando a WinUsb_RegisterIsochBuffer. La llamada devuelve un identificador de registro que se usa para enviar la transferencia. El búfer se reutiliza para las transferencias posteriores y el desplazamiento en el búfer se ajusta para enviar o recibir el siguiente conjunto de datos.

Todas las transferencias del ejemplo se envían de forma asincrónica. Para ello, la aplicación asigna una matriz de estructura SUPERPUESTA con tres elementos, uno para cada transferencia. La aplicación proporciona eventos para que pueda recibir notificaciones cuando se completen las transferencias y recuperen los resultados de la operación. Para ello, en cada estructura SUPERPUESTA de la matriz, la aplicación asigna un evento y establece el identificador en el miembro hEvent .

En esta imagen se muestran tres transferencias de lectura mediante la función WinUsb_ReadIsochPipeAsap . La llamada especifica el desplazamiento y la longitud de cada transferencia. El valor del parámetro ContinueStream es FALSE para indicar una nueva secuencia. Después, las solicitudes de aplicación que las transferencias posteriores se programan inmediatamente después del último marco de la solicitud anterior para permitir el streaming continuo de datos. El número de paquetes isócronos se calcula como paquetes por fotograma * número de fotogramas; 8*10. Para esta llamada, la aplicación no debe preocuparse por calcular el número de fotograma inicial.

Función winusb para la transferencia de lectura isócrónica.

En esta imagen se muestran tres transferencias de escritura mediante la función WinUsb_WriteIsochPipe . La llamada especifica el desplazamiento y la longitud de cada transferencia. En este caso, la aplicación debe calcular el número de fotograma en el que el controlador de host puede empezar a enviar datos. En la salida, la función recibe el número de fotograma del marco que sigue al último fotograma usado en la transferencia anterior. Para obtener el marco actual, la aplicación llama a WinUsb_GetCurrentFrameNumber. En este momento, la aplicación debe asegurarse de que el marco de inicio de la siguiente transferencia es posterior al marco actual, de modo que la pila del controlador USB no quite paquetes retrasados. Para ello, la aplicación llama a WinUsb_GetAdjustedFrameNumber para obtener un número de fotograma actual realista (esto es posterior al número de fotograma actual recibido). Para estar en el lado seguro, la aplicación agrega cinco fotogramas más y, a continuación, envía la transferencia.

Función winusb para la transferencia de escritura isócrónica.

Una vez completada cada transferencia, la aplicación obtiene los resultados de la transferencia llamando a WinUsb_GetOverlappedResult. El parámetro bWait se establece en TRUE para que la llamada no devuelva hasta que se haya completado la operación. Para las transferencias de lectura y escritura, el parámetro lpNumberOfBytesTransferred siempre es 0. Para una transferencia de escritura, la aplicación supone que si la operación se completó correctamente, se transferiron todos los bytes. Para una transferencia de lectura, el miembro Length de cada paquete isócrono (USBD_ISO_PACKET_DESCRIPTOR), contiene el número de bytes transferidos en ese paquete, por intervalo. Para obtener la longitud total, la aplicación agrega todos los valores Length .

Cuando termine, la aplicación libera los identificadores de búfer isócrono llamando a WinUsb_UnregisterIsochBuffer.

Antes de empezar

Asegúrese de que,

  • El controlador de dispositivo es el controlador proporcionado por Microsoft: WinUSB (Winusb.sys). Ese controlador se incluye en la carpeta \Windows\System32\. Para obtener más información, consulte Instalación de WinUSB (Winusb.sys).

  • Anteriormente ha obtenido un identificador de interfaz winUSB para el dispositivo llamando a WinUsb_Initialize. Todas las operaciones se realizan mediante ese identificador. Lea Cómo acceder a un dispositivo USB mediante funciones winUSB.

  • La configuración de la interfaz activa tiene puntos de conexión isócronos. De lo contrario, no puede acceder a las canalizaciones de los puntos de conexión de destino.

Paso 1: Buscar la canalización isócrónica en la configuración activa

  1. Obtenga la interfaz USB que tiene los puntos de conexión isócronos llamando a WinUsb_QueryInterfaceSettings.
  2. Enumere las canalizaciones de la configuración de interfaz que define los puntos de conexión.
  3. Para cada punto de conexión, obtenga las propiedades de canalización asociadas en una estructura de WINUSB_PIPE_INFORMATION_EX llamando a WinUsb_QueryPipeEx. Estructura de WINUSB_PIPE_INFORMATION_EX recuperada que contiene información sobre la canalización isócrónica. La estructura contiene información sobre la canalización, su tipo, identificador, etc.
  4. Compruebe los miembros de la estructura para determinar si es la canalización que se debe usar para las transferencias. Si es así, almacene el valor PipeId . En el código de plantilla, agregue miembros a la estructura DEVICE_DATA, definida en Device.h.

En este ejemplo se muestra cómo determinar si la configuración activa tiene puntos de conexión isócronos y obtener información sobre ellos. En este ejemplo, el dispositivo es un dispositivo SuperMUTT. El dispositivo tiene dos puntos de conexión isócronos en la interfaz predeterminada, configuración 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;
}

El dispositivo SuperMUTT define sus puntos de conexión isócronos en la interfaz predeterminada, en el valor 1. El código anterior obtiene los valores de PipeId y los almacena en la estructura DEVICE_DATA.

Paso 2: Obtener información de intervalo sobre la canalización isócrónica

A continuación, obtenga más información sobre la canalización que obtuvo en la llamada a WinUsb_QueryPipeEx.

  • Tamaño de transferencia

    1. En la estructura de WINUSB_PIPE_INFORMATION_EX recuperada, obtenga los valores MaximumBytesPerInterval e Interval .

    2. En función de la cantidad de datos isócronos que desea enviar o recibir, calcule el tamaño de la transferencia. Por ejemplo, considere este cálculo:

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

      En el ejemplo, el tamaño de transferencia se calcula para 10 milisegundos de datos isócronos.

  • Número de paquetes isócronosPor ejemplo, considere este cálculo:

    Para calcular el número total de paquetes isócronos necesarios para contener toda la transferencia. Esta información es necesaria para las transferencias de lectura y se calculan como , >IsochInTransferSize / pipe.MaximumBytesPerInterval;.

En este ejemplo se muestra cómo agregar código al paso 1 y obtiene los valores de intervalo de las canalizaciones isócrónicas.


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

...

En el código anterior, la aplicación obtiene Interval y MaximumBytesPerInterval de WINUSB_PIPE_INFORMATION_EX para calcular el tamaño de transferencia y el número de paquetes isócronos necesarios para la transferencia de lectura. Para ambos puntos de conexión isócronos, Interval es 1. Ese valor indica que todos los microframes del marco llevan datos. En función de eso, para enviar 10 milisegundos de datos, necesita 10 fotogramas, el tamaño total de transferencia es de 10*1024*8 bytes y 80 paquetes isócronos, cada 1024 bytes de longitud.

Paso 3: Envío de una transferencia de escritura para enviar datos a un punto de conexión OUT isócrono

En este procedimiento se resumen los pasos para escribir datos en un punto de conexión isócrono.

  1. Asigne un búfer que contenga los datos que se van a enviar.
  2. Si va a enviar los datos de forma asincrónica, asigne e inicialice una estructura SUPERPUESTA que contenga un identificador a un objeto de evento asignado por el autor de la llamada. La estructura debe inicializarse en cero; de lo contrario, se produce un error en la llamada.
  3. Registre el búfer llamando a WinUsb_RegisterIsochBuffer.
  4. Inicie la transferencia llamando a WinUsb_WriteIsochPipeAsap. Si desea especificar manualmente el marco en el que se transferirán los datos, llame a WinUsb_WriteIsochPipe en su lugar.
  5. Obtenga los resultados de la transferencia llamando a WinUsb_GetOverlappedResult.
  6. Cuando termine, libere el identificador del búfer llamando a WinUsb_UnregisterIsochBuffer, el identificador de eventos superpuestos y el búfer de transferencia.

Este es un ejemplo que muestra cómo enviar una transferencia de escritura.

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

Paso 4: Envío de una transferencia de lectura para recibir datos de un punto de conexión IN isócrono

En este procedimiento se resumen los pasos para leer datos de un punto de conexión isócrono.

  1. Asigne un búfer de transferencia que recibirá datos al final de la transferencia. El tamaño del búfer debe basarse en el tamaño de transferencia calculado en el paso 1. El búfer de transferencia debe terminar en un límite de marco.
  2. Si va a enviar los datos de forma asincrónica, asigne una estructura SUPERPUESTA que contenga un identificador a un objeto de evento asignado por el autor de la llamada. La estructura debe inicializarse en cero; de lo contrario, se produce un error en la llamada.
  3. Registre el búfer llamando a WinUsb_RegisterIsochBuffer.
  4. En función del número de paquetes isócronos calculados en el paso 2, asigne una matriz de paquetes isócronos (USBD_ISO_PACKET_DESCRIPTOR).
  5. Inicie la transferencia llamando a WinUsb_ReadIsochPipeAsap. Si desea especificar manualmente el marco de inicio en el que se transferirán los datos, llame a WinUsb_ReadIsochPipe en su lugar.
  6. Obtenga los resultados de la transferencia llamando a WinUsb_GetOverlappedResult.
  7. Cuando termine, libere el identificador del búfer llamando a WinUsb_UnregisterIsochBuffer, el identificador de eventos superpuestos, la matriz de paquetes isócronos y el búfer de transferencia.

Este es un ejemplo que muestra cómo enviar una transferencia de lectura llamando a WinUsb_ReadIsochPipeAsap y 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;
}