Отправка USB изохронных передач из настольного приложения WinUSB

Начиная с Windows 8.1 набор функций WinUSB имеет API,которые позволяют классическому приложению передавать данные в и из изохронных конечных точек USB-устройства. Для такого приложения драйвером устройства должна быть предоставленная корпорацией Майкрософт Winusb.sys.

Эта статья содержит следующие сведения.

  • Краткий обзор изохронной передачи.
  • Вычисление буфера передачи на основе значений интервала конечной точки.
  • Отправка передачи, которые считывают и записывают изохронные данные с помощью функций WinUSB.

Важные API

Начиная с Windows 8.1 набор функций WinUSB имеет API,которые позволяют классическому приложению передавать данные в и из изохронных конечных точек USB-устройства. Для такого приложения драйвером устройства от корпорации Майкрософт должен быть Winusb.sys.

USB-устройство может поддерживать изохронные конечные точки для передачи данных, зависимых от времени, с устойчивой скоростью, например с потоковой передачой звука и видео. Нет гарантированной доставки. Хорошее подключение не должно терять пакеты, это ненормально и не ожидается, что пакеты будут теряться, но протокол isochronous терпим к таким потерям.

Контроллер узла отправляет или получает данные в течение зарезервированных периодов времени на шине, называются интервалами шины. Единица измерения интервала шины зависит от ее скорости. Для полной скорости это 1-миллисекундные кадры, для высокой скорости и SuperSpeed — это 250-микросекундные микрофреймы.

Контроллер узла опрашивает устройство через регулярные интервалы. Для операций чтения, когда конечная точка готова к отправке данных, устройство реагирует, отправляя данные в интервале шины. Для записи на устройство контроллер узла отправляет данные.

Объем данных, отправляемых приложением через один интервал службы

Термин изохронный пакет в этом разделе относится к объему данных, передаваемых в один интервал службы. Это значение вычисляется стеком USB-драйвера, и приложение может получить значение при запросе атрибутов канала.

Размер изохронного пакета определяет размер буфера передачи, который выделяет приложение. Буфер должен заканчиваться границой кадра. Общий размер передачи зависит от объема данных, которые приложение хочет отправить или получить. После запуска передачи приложением узел пакетизирует буфер передачи таким образом, чтобы в каждом интервале узел может отправлять или получать максимальные байты, разрешенные для каждого интервала.

Для передачи данных используются не все интервалы шины. В этом разделе интервалы движения автобусов, используемые, называются интервалами обслуживания.

Как вычислить кадр, в котором передаются данные

Приложение может указать кадр одним из двух способов:

  • благодаря автоматизации! В этом режиме приложение указывает стеку USB-драйверов отправить передачу в следующем соответствующем кадре. Приложение также должно указать, является ли буфер непрерывным потоком, чтобы стек драйверов смог вычислить начальный кадр.
  • Указание начального кадра, который позже текущего кадра. Приложение должно учитывать задержку между временем, когда приложение запускает передачу и когда стек USB-драйверов обрабатывает его.

Обсуждение примера кода

Примеры, приведенные в этом разделе, демонстрируют использование этих функций WinUSB:

В этом разделе мы будем считывать и записывать 30 миллисекунд данных в трех сеансах передачи на высокоскоростное устройство. Канал может передавать 1024 байта в каждом интервале обслуживания. Так как интервал опроса равен 1, данные передаются в каждом микрофрейме кадра. Всего 30 кадров будут содержать 30*8*1024 байта.

Вызовы функции для операций чтения и записи аналогичны. Приложение выделяет буфер передачи достаточно большой, чтобы сохранить все три передачи. Приложение регистрирует буфер для определенного канала, вызывая WinUsb_RegisterIsochBuffer. Вызов возвращает дескриптор регистрации, который используется для отправки передачи. Буфер повторно используется для последующей передачи, а смещение в буфере корректируется для отправки или получения следующего набора данных.

Все передачи в примере отправляются асинхронно. Для этого приложение выделяет массив структуры OVERLAPPED с тремя элементами, по одному для каждой передачи. Приложение предоставляет события, чтобы оно могло получать уведомления после завершения передачи и извлекать результаты операции. Для этого в каждой структуре OVERLAPPED в массиве приложение выделяет событие и задает дескриптор в элементе hEvent .

На этом рисунке показаны три передачи чтения с помощью функции WinUsb_ReadIsochPipeAsap . Вызов задает смещение и длину каждой передачи. Значение параметра ContinueStream равно FALSE, чтобы указать новый поток. После этого приложение запрашивает последующие передачи сразу после последнего кадра предыдущего запроса, чтобы обеспечить непрерывную потоковую передачу данных. Число изохронных пакетов вычисляется как пакеты на кадр * количество кадров; 8*10. Для этого вызова приложению не нужно беспокоиться о вычислении номера начального кадра.

Функция winusb для изохронной передачи данных на чтение.

На этом рисунке показаны три передачи записи с помощью функции WinUsb_WriteIsochPipe . Вызов определяет смещение и длину каждого переноса данных. В этом случае приложение должно вычислить номер кадра, в котором контроллер узла может начать отправку данных. На выходе функция получает номер кадра, который следует за последним использованным кадром в предыдущей передаче. Чтобы получить текущий кадр, приложение вызывает WinUsb_GetCurrentFrameNumber. На этом этапе приложение должно убедиться, что начальный кадр следующей передачи позже текущего кадра, чтобы стек USB-драйверов не сбрасывал поздние пакеты. Для этого приложение вызывает WinUsb_GetAdjustedFrameNumber , чтобы получить реалистичный текущий номер кадра (это позже полученного текущего номера кадра). Чтобы быть на безопасной стороне, приложение добавляет еще пять кадров, а затем отправляет передачу.

функция winusb для передачи изохронной записи.

После завершения каждой передачи приложение получает результаты передачи путем вызова WinUsb_GetOverlappedResult. Параметр bWait имеет значение TRUE, чтобы вызов не возвращался до завершения операции. Для передачи операций чтения и записи параметр lpNumberOfBytesTransferred всегда равен 0. Для передачи записи приложение предполагает, что при успешном выполнении операции все байты были переданы. Для передачи чтения поле длины каждого изохронного пакета (USBD_ISO_PACKET_DESCRIPTOR) содержит количество байтов, переданных в этом пакете, в интервале. Чтобы получить общую длину, приложение добавляет все значения Length .

По завершении приложение освобождает маркеры изохронного буфера, вызывая WinUsb_UnregisterIsochBuffer.

Перед началом работы

Убедитесь, что

  • Драйвер устройства — это драйвер, предоставленный корпорацией Майкрософт: WinUSB (Winusb.sys). Этот драйвер включен в папку \Windows\System32\. Дополнительные сведения см. в статье о установке WinUSB (Winusb.sys).

  • Ранее вы получили дескриптор интерфейса WinUSB для устройства путем вызова WinUsb_Initialize. Все операции выполняются с помощью этого дескриптора. Узнайте , как получить доступ к USB-устройству с помощью функций WinUSB.

  • Параметр активного интерфейса имеет изохронные конечные точки. В противном случае невозможно получить доступ к каналам для целевых конечных точек.

Шаг 1: Найдите изохронный трубопровод в активной настройке

  1. Получите USB-интерфейс, имеющий изохронные конечные точки, вызвав WinUsb_QueryInterfaceSettings.
  2. Перечислите каналы параметра интерфейса, определяющие конечные точки.
  3. Для каждой конечной точки получают связанные свойства канала в структуре WINUSB_PIPE_INFORMATION_EX путем вызова WinUsb_QueryPipeEx. Полученная WINUSB_PIPE_INFORMATION_EX структура, содержащая сведения об инохронном канале. Структура содержит сведения о трубе, её типе, идентификаторе и т. д.
  4. Проверьте элементы структуры, чтобы определить, должен ли использоваться трубопровод для передачи. Если это так, сохраните значение PipeId . В коде шаблона добавьте элементы в структуру DEVICE_DATA, определенную в Device.h.

В этом примере показано, как определить, имеет ли активный параметр изохронные конечные точки и получить сведения о них. В этом примере устройство является устройством SuperMUTT. Устройство имеет две изохронные конечные точки в интерфейсе по умолчанию, альтернативный параметр 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;
}

Устройство SuperMUTT определяет его изохронные конечные точки в интерфейсе по умолчанию при задании 1. Приведенный выше код получает значения PipeId и сохраняет их в структуре DEVICE_DATA.

Шаг 2. Получение сведений об интервале изохронного канала

Затем получите дополнительные сведения о канале, полученном при вызове WinUsb_QueryPipeEx.

  • Размер передачи

    1. Из полученной WINUSB_PIPE_INFORMATION_EX структуры получите значения MaximumBytesPerInterval и Interval .

    2. В зависимости от объема изохронных данных, которые вы хотите отправить или получить, вычислите размер передачи. Например, рассмотрим этот расчет:

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

      В примере размер передачи вычисляется в 10 миллисекундах изохронных данных.

  • Число изохронных пакетовНапример, рассмотрим этот расчет:

    Чтобы вычислить общее количество изохронных пакетов, необходимых для хранения всей передачи. Эта информация необходима для чтения передачи и вычисления, как >IsochInTransferSize / pipe.MaximumBytesPerInterval;.

В этом примере показано, как добавить код к примеру на шаге 1 и получить значения интервалов для изохронных каналов.


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

...

В приведенном выше коде приложение получает Interval и MaximumBytesPerInterval из WINUSB_PIPE_INFORMATION_EX для вычисления размера передачи и количества изохронных пакетов, необходимых для операции чтения. Для обеих изохронных конечных точек интервал равен 1. Это значение указывает, что все микрофреймы кадра несут данные. На основе этого для отправки 10 миллисекунд данных требуется 10 кадров, общий размер передачи составляет 10*1024*8 байт и 80 изохронных пакетов, каждый 1024 байт длиной.

Шаг 3. Перенос данных в изохронную конечную точку OUT с использованием передачи записи

В этой процедуре приведены инструкции по написанию данных в изохронную конечную точку.

  1. Выделите буфер, содержащий данные для отправки.
  2. Если вы отправляете данные асинхронно, выделите и инициализируйте структуру OVERLAPPED, содержащую дескриптор на объект события, выделенный вызывающим. Структура должна быть инициализирована до нуля, в противном случае вызов завершается ошибкой.
  3. Зарегистрируйте буфер, вызвав WinUsb_RegisterIsochBuffer.
  4. Запустите передачу, вызвав WinUsb_WriteIsochPipeAsap. Если вы хотите вручную указать кадр, в котором будут передаваться данные, вызовите WinUsb_WriteIsochPipe вместо этого.
  5. Получение результатов передачи путем вызова WinUsb_GetOverlappedResult.
  6. По завершении отпустите дескриптор буфера, вызвав WinUsb_UnregisterIsochBuffer, перекрываемый дескриптор событий и буфер передачи.

Ниже приведен пример отправки передачи записи.

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

Шаг 4. Отправка передачи данных для получения данных из изохронной конечной точки IN.

В этой процедуре приведены инструкции по чтению данных из изохронной конечной точки.

  1. Выделите буфер передачи, который получит данные в конце передачи. Размер буфера должен зависеть от размера передачи, вычисляемого на шаге 1. Буфер передачи должен заканчиваться границой кадра.
  2. Если вы отправляете данные асинхронно, выделите структуру OVERLAPPED, содержащую дескриптор объекта события, выделенного вызывающим. Структура должна быть инициализирована до нуля, в противном случае вызов завершается ошибкой.
  3. Зарегистрируйте буфер, вызвав WinUsb_RegisterIsochBuffer.
  4. На основе числа изохронных пакетов, вычисляемых на шаге 2, выделите массив изохронных пакетов (USBD_ISO_PACKET_DESCRIPTOR).
  5. Запустите передачу, вызвав WinUsb_ReadIsochPipeAsap. Если вы хотите вручную указать начальный кадр, в котором будут передаваться данные, вызовите WinUsb_ReadIsochPipe .
  6. Получение результатов передачи путем вызова WinUsb_GetOverlappedResult.
  7. По завершении отпустите дескриптор буфера, вызвав WinUsb_UnregisterIsochBuffer, перекрываемый дескриптор событий, массив изохронных пакетов и буфер передачи.

Вот пример, показывающий, как отправить данные для чтения, вызвав функции WinUsb_ReadIsochPipeAsap и 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;
}