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

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

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

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

Важные API

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

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

Контроллер узла отправляет или получает данные в течение зарезервированных периодов времени на шине, которые называются интервалами шины. Единица интервала шины зависит от скорости шины. Для полной скорости это 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. Для передачи записи приложение предполагает, что если операция успешно завершена, все байты были переданы. Для передачи на чтение элемент Length каждого изохронного пакета (USBD_ISO_PACKET_DESCRIPTOR) содержит число байтов, передаваемых в этом пакете, за интервал. Чтобы получить общую длину, приложение добавляет все значения Длины .

По завершении приложение освобождает изохронные дескрипторы буфера, вызывая 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;
}