Compartilhar via


Enviar transferências isocronas USB de um aplicativo da área de trabalho do WinUSB

A partir de Windows 8.1, o conjunto de funções winusb tem APIs que permitem que um aplicativo da área de trabalho transfira dados de e para pontos de extremidade isócronos de um dispositivo USB. Para esse aplicativo, o Winusb.sys fornecido pela Microsoft deve ser o driver do dispositivo.

Este artigo fornece as seguintes informações:

  • Breve visão geral das transferências isocronas.
  • Cálculo de buffer de transferência com base em valores de intervalo de ponto de extremidade.
  • Enviando transferências que leem e gravam dados isócronos usando funções WinUSB.

APIs importantes

A partir de Windows 8.1, o conjunto de funções WinUSB tem APIs que permitem que um aplicativo da área de trabalho transfira dados de e para pontos de extremidade isócronos de um dispositivo USB. Para esse aplicativo, o Winusb.sys fornecido pela Microsoft deve ser o driver do dispositivo.

Um dispositivo USB pode dar suporte a pontos de extremidade isócronos para transferir dados dependentes de tempo a uma taxa constante, como com streaming de áudio/vídeo. Não há entrega garantida. Uma boa conexão não deve descartar nenhum pacote, não é normal ou espera-se que perca pacotes, mas o protocolo isócrono é tolerante a essas perdas.

O controlador host envia ou recebe dados durante períodos reservados de tempo no barramento, são chamados de intervalos de ônibus. A unidade do intervalo de ônibus depende da velocidade do ônibus. Para velocidade total, são quadros de 1 milissegundos, para alta velocidade e SuperSpeed, são microframes de 250 microssegundos.

O controlador host sonda o dispositivo em intervalos regulares. Para operações de leitura, quando o ponto de extremidade está pronto para enviar dados, o dispositivo responde enviando dados no intervalo de barramento. Para gravar no dispositivo, o controlador de host envia dados.

Quantos dados o aplicativo pode enviar em um intervalo de serviço

O termo pacote isócrono neste tópico refere-se à quantidade de dados transferidos em um intervalo de serviço. Esse valor é calculado pela pilha do driver USB e o aplicativo pode obter o valor ao consultar atributos de pipe.

O tamanho de um pacote isócrono determina o tamanho do buffer de transferência alocado pelo aplicativo. O buffer deve terminar em um limite de quadro. O tamanho total da transferência depende de quantos dados o aplicativo deseja enviar ou receber. Depois que a transferência é iniciada pelo aplicativo, o host encapsula o buffer de transferência para que, em cada intervalo, o host possa enviar ou receber o máximo de bytes permitidos por intervalo.

Para uma transferência de dados, nem todos os intervalos de barramento são usados. Neste tópico, os intervalos de barramento usados são chamados de intervalos de serviço.

Como calcular o quadro no qual os dados são transmitidos

O aplicativo pode optar por especificar o quadro de duas maneiras:

  • Automaticamente. Nesse modo, o aplicativo instrui a pilha de driver USB a enviar a transferência no próximo quadro apropriado. O aplicativo também deve especificar se o buffer é um fluxo contínuo para que a pilha de driver possa calcular o quadro inicial.
  • Especificando o quadro inicial que é posterior ao quadro atual. O aplicativo deve levar em consideração a latência entre o momento em que o aplicativo inicia a transferência e quando a pilha de driver USB o processa.

Discussão de exemplo de código

Os exemplos neste tópico demonstram o uso dessas funções winusb:

Neste tópico, leremos e gravaremos 30 milissegundos de dados em três transferências para um dispositivo de alta velocidade. O pipe é capaz de transferir 1024 bytes em cada intervalo de serviço. Como o intervalo de sondagem é 1, os dados são transferidos em cada microframe de um quadro. O total de 30 quadros terá 30*8*1024 bytes.

As chamadas de função para enviar transferências de leitura e gravação são semelhantes. O aplicativo aloca um buffer de transferência grande o suficiente para manter todas as três transferências. O aplicativo registra o buffer de um pipe específico chamando WinUsb_RegisterIsochBuffer. A chamada retorna um identificador de registro que é usado para enviar a transferência. O buffer é reutilizado para transferências subsequentes e o deslocamento no buffer é ajustado para enviar ou receber o próximo conjunto de dados.

Todas as transferências no exemplo são enviadas de forma assíncrona. Para isso, o aplicativo aloca uma matriz de estrutura OVERLAPPED com três elementos, um para cada transferência. O aplicativo fornece eventos para que ele possa ser notificado quando as transferências forem concluídas e recuperar os resultados da operação. Para isso, em cada estrutura OVERLAPPED na matriz, o aplicativo aloca um evento e define o identificador no membro hEvent .

Esta imagem mostra três transferências de leitura usando a função WinUsb_ReadIsochPipeAsap . A chamada especifica deslocamento e comprimento de cada transferência. O valor do parâmetro ContinueStream é FALSE para indicar um novo fluxo. Depois disso, o aplicativo solicita que as transferências subsequentes sejam agendadas imediatamente após o último quadro da solicitação anterior para permitir o streaming contínuo de dados. O número de pacotes isócronos é calculado como pacotes por quadro * número de quadros; 8*10. Para essa chamada, o aplicativo não precisa se preocupar em calcular o número do quadro inicial.

Função winusb para transferência de leitura isócrona.

Esta imagem mostra três transferências de gravação usando a função WinUsb_WriteIsochPipe . A chamada especifica deslocamento e comprimento de cada transferência. Nesse caso, o aplicativo deve calcular o número de quadro no qual o controlador de host pode começar a enviar dados. Na saída, a função recebe o número de quadro do quadro que segue o último quadro usado na transferência anterior. Para obter o quadro atual, o aplicativo chama WinUsb_GetCurrentFrameNumber. Neste ponto, o aplicativo deve garantir que o quadro inicial da próxima transferência seja posterior ao quadro atual, para que a pilha de driver USB não solte pacotes atrasados. Para fazer isso, o aplicativo chama WinUsb_GetAdjustedFrameNumber para obter um número de quadro atual realista (isso é mais tarde do que o número de quadro atual recebido). Para estar no lado seguro, o aplicativo adiciona mais cinco quadros e envia a transferência.

Função winusb para transferência de gravação isócrona.

Após a conclusão de cada transferência, o aplicativo obtém os resultados da transferência chamando WinUsb_GetOverlappedResult. O parâmetro bWait é definido como TRUE para que a chamada não retorne até que a operação seja concluída. Para transferências de leitura e gravação, o parâmetro lpNumberOfBytesTransferred é sempre 0. Para uma transferência de gravação, o aplicativo pressupõe que, se a operação for concluída com êxito, todos os bytes foram transferidos. Para uma transferência de leitura, o membro Length de cada pacote isócrono (USBD_ISO_PACKET_DESCRIPTOR), contém os bytes de número transferidos nesse pacote, por intervalo. Para obter o comprimento total, o aplicativo adiciona todos os valores length .

Quando concluído, o aplicativo libera os identificadores de buffer isócronos chamando WinUsb_UnregisterIsochBuffer.

Antes de começar

Certifique-se de que,

  • O driver do dispositivo é o driver fornecido pela Microsoft: WinUSB (Winusb.sys). Esse driver está incluído na pasta \Windows\System32\. Para obter mais informações, consulte Instalação do WinUSB (Winusb.sys).

  • Você já obteve um identificador de interface do WinUSB para o dispositivo chamando WinUsb_Initialize. Todas as operações são executadas usando esse identificador. Leia Como acessar um dispositivo USB usando funções WinUSB.

  • A configuração da interface ativa tem pontos de extremidade isócronos. Caso contrário, você não poderá acessar os pipes para os pontos de extremidade de destino.

Etapa 1: localizar o pipe isocrono na configuração ativa

  1. Obtenha a interface USB que tem os pontos de extremidade isócronos chamando WinUsb_QueryInterfaceSettings.
  2. Enumerar os pipes da configuração de interface que define os pontos de extremidade.
  3. Para cada ponto de extremidade, obtenha as propriedades de pipe associadas em uma estrutura WINUSB_PIPE_INFORMATION_EX chamando WinUsb_QueryPipeEx. A estrutura WINUSB_PIPE_INFORMATION_EX recuperada que contém informações sobre o pipe isocrono. A estrutura contém informações sobre o pipe, seu tipo, id e assim por diante.
  4. Verifique os membros da estrutura para determinar se é o pipe que deve ser usado para transferências. Se for, armazene o valor PipeId . No código do modelo, adicione membros à estrutura DEVICE_DATA, definida em Device.h.

Este exemplo mostra como determinar se a configuração ativa tem pontos de extremidade isocronos e obter informações sobre eles. Neste exemplo, o dispositivo é um dispositivo SuperMUTT. O dispositivo tem dois pontos de extremidade isócronos na interface padrão, configuração 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;
}

O dispositivo SuperMUTT define seus pontos de extremidade isócronos na interface padrão, na configuração 1. O código anterior obtém os valores PipeId e os armazena na estrutura DEVICE_DATA.

Etapa 2: Obter informações de intervalo sobre o pipe isocrono

Em seguida, obtenha mais informações sobre o pipe obtido na chamada para WinUsb_QueryPipeEx.

  • Tamanho da transferência

    1. Na estrutura de WINUSB_PIPE_INFORMATION_EX recuperada, obtenha os valores MaximumBytesPerInterval e Interval .

    2. Dependendo da quantidade de dados isócronos que você deseja enviar ou receber, calcule o tamanho da transferência. Por exemplo, considere este cálculo:

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

      No exemplo, o tamanho da transferência é calculado para 10 milissegundos de dados isocronos.

  • Número de pacotes isócronosPor exemplo, considere este cálculo:

    Para calcular o número total de pacotes isócronos necessários para manter toda a transferência. Essas informações são necessárias para transferências de leitura e calculadas como, >IsochInTransferSize / pipe.MaximumBytesPerInterval;.

Este exemplo mostra o exemplo adicionar código à etapa 1 e obtém os valores de intervalo para os pipes isocronos.


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

...

No código anterior, o aplicativo obtém Interval e MaximumBytesPerInterval de WINUSB_PIPE_INFORMATION_EX para calcular o tamanho da transferência e o número de pacotes isócronos necessários para a transferência de leitura. Para ambos os pontos de extremidade isócronos, Interval é 1. Esse valor indica que todos os microframes do quadro carregam dados. Com base nisso, para enviar 10 milissegundos de dados, você precisa de 10 quadros, o tamanho total da transferência é de 10*1024*8 bytes e 80 pacotes isócronos, cada um com 1024 bytes de comprimento.

Etapa 3: Enviar uma transferência de gravação para enviar dados para um ponto de extremidade OUT isócrono

Este procedimento resume as etapas para gravar dados em um ponto de extremidade isócrono.

  1. Aloque um buffer que contenha os dados a serem enviados.
  2. Se você estiver enviando os dados de forma assíncrona, aloque e inicialize uma estrutura OVERLAPPED que contenha um identificador para um objeto de evento alocado pelo chamador. A estrutura deve ser inicializada como zero, caso contrário, a chamada falhará.
  3. Registre o buffer chamando WinUsb_RegisterIsochBuffer.
  4. Inicie a transferência chamando WinUsb_WriteIsochPipeAsap. Se você quiser especificar manualmente o quadro no qual os dados serão transferidos, chame WinUsb_WriteIsochPipe .
  5. Obtenha resultados da transferência chamando WinUsb_GetOverlappedResult.
  6. Quando terminar, solte o identificador de buffer chamando WinUsb_UnregisterIsochBuffer, o identificador de evento sobreposto e o buffer de transferência.

Aqui está um exemplo que mostra como enviar uma transferência de gravação.

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

Etapa 4: Enviar uma transferência de leitura para receber dados de um ponto de extremidade IN isócrono

Este procedimento resume as etapas para ler dados de um ponto de extremidade isócrono.

  1. Aloque um buffer de transferência que receberá dados no final da transferência. O tamanho do buffer deve ser baseado no tamanho da transferência calculado na etapa 1. O buffer de transferência deve terminar em um limite de quadro.
  2. Se você estiver enviando os dados de forma assíncrona, aloque uma estrutura OVERLAPPED que contenha um identificador para um objeto de evento alocado pelo chamador. A estrutura deve ser inicializada como zero, caso contrário, a chamada falhará.
  3. Registre o buffer chamando WinUsb_RegisterIsochBuffer.
  4. Com base no número de pacotes isócronos calculados na etapa 2, aloque uma matriz de pacotes isócronos (USBD_ISO_PACKET_DESCRIPTOR).
  5. Inicie a transferência chamando WinUsb_ReadIsochPipeAsap. Se você quiser especificar manualmente o quadro inicial no qual os dados serão transferidos, chame WinUsb_ReadIsochPipe .
  6. Obtenha resultados da transferência chamando WinUsb_GetOverlappedResult.
  7. Quando terminar, solte o identificador de buffer chamando WinUsb_UnregisterIsochBuffer, o identificador de evento sobreposto, a matriz de pacotes isócronos e o buffer de transferência.

Aqui está um exemplo que mostra como enviar uma transferência de leitura chamando 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;
}