WinUSB 데스크톱 앱에서 USB 등시 전송 보내기

Windows 8.1 시작해서 WinUSB Functions 집합에는 데스크톱 애플리케이션이 USB 디바이스의 등시 엔드포인트에서 데이터를 전송할 수 있는 API가 있습니다. 이러한 애플리케이션의 경우 Microsoft에서 제공하는 Winusb.sys 디바이스 드라이버여야 합니다.

이 문서에서는 다음 정보를 제공합니다.

  • 등시 전송에 대한 간략한 개요입니다.
  • 엔드포인트 간격 값을 기반으로 버퍼 계산을 전송합니다.
  • WinUSB 함수를 사용하여 등시 데이터를 읽고 쓰는 전송 전송

중요 API

Windows 8.1 WinUSB Functions 집합에는 데스크톱 애플리케이션이 USB 디바이스의 등시 엔드포인트 간 데이터를 전송할 수 있는 API가 있습니다. 이러한 애플리케이션의 경우 Microsoft에서 제공하는 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 호출하여 실제 현재 프레임 번호를 가져옵니다(수신된 현재 프레임 번호보다 나음). 안전한 쪽에 있을 수 있도록 앱은 5개의 프레임을 더 추가한 다음 전송을 보냅니다.

등시 쓰기 전송에 대한 winusb 함수입니다.

각 전송이 완료되면 앱은 WinUsb_GetOverlappedResult 호출하여 전송 결과를 가져옵니다. 작업이 완료될 때까지 호출이 반환되지 않도록 bWait 매개 변수가 TRUE로 설정됩니다. 읽기 및 쓰기 전송의 경우 lpNumberOfBytesTransferred 매개 변수는 항상 0입니다. 쓰기 전송의 경우 앱은 작업이 성공적으로 완료되면 모든 바이트가 전송되었다고 가정합니다. 읽기 전송의 경우 각 등시 패킷(USBD_ISO_PACKET_DESCRIPTOR)의 Length 멤버에는 간격당 해당 패킷에서 전송된 바이트 수가 포함됩니다. 총 길이를 가져오기 위해 앱은 모든 Length 값을 추가합니다.

완료되면 앱은 WinUsb_UnregisterIsochBuffer 호출하여 등시 버퍼 핸들을 해제합니다.

시작하기 전에

다음을 확인합니다.

  • 디바이스 드라이버는 Microsoft에서 제공하는 드라이버인 WinUSB(Winusb.sys)입니다. 해당 드라이버는 \Windows\System32\ 폴더에 포함되어 있습니다. 자세한 내용은 WinUSB(Winusb.sys) 설치를 참조하세요.

  • 이전에 WinUsb_Initialize 호출하여 디바이스에 대한 WinUSB 인터페이스 핸들을 얻었습니다. 모든 작업은 해당 핸들을 사용하여 수행됩니다. WinUSB 함수를 사용하여 USB 디바이스에 액세스하는 방법을 참조하세요.

  • 활성 인터페이스 설정에는 등시 엔드포인트가 있습니다. 그렇지 않으면 대상 엔드포인트의 파이프에 액세스할 수 없습니다.

1단계: 활성 설정에서 등시 파이프 찾기

  1. WinUsb_QueryInterfaceSettings 호출하여 등시 엔드포인트가 있는 USB 인터페이스를 가져옵니다.
  2. 엔드포인트를 정의하는 인터페이스 설정의 파이프를 열거합니다.
  3. 각 엔드포인트에 대해 WinUsb_QueryPipeEx 호출하여WINUSB_PIPE_INFORMATION_EX 구조에서 연결된 파이프 속성을 가져옵니다. 등시 파이프에 대한 정보를 포함하는 검색된 WINUSB_PIPE_INFORMATION_EX 구조체입니다. 구조체에는 파이프, 해당 형식, ID 등에 대한 정보가 포함됩니다.
  4. 구조체 멤버를 확인하여 전송에 사용해야 하는 파이프인지 확인합니다. 이 경우 PipeId 값을 저장합니다. 템플릿 코드에서 Device.h에 정의된 DEVICE_DATA 구조에 멤버를 추가합니다.

이 예제에서는 활성 설정에 등시 엔드포인트가 있는지 여부를 확인하고 해당 엔드포인트에 대한 정보를 가져오는 방법을 보여 줍니다. 이 예제에서 디바이스는 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 구조체에서 MaximumBytesPerIntervalInterval 값을 가져옵니다.

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

...

앞의 코드에서 앱은 WINUSB_PIPE_INFORMATION_EXIntervalMaximumBytesPerInterval을 가져오며 읽기 전송에 필요한 전송 크기와 등시 패킷 수를 계산합니다. 두 등시 엔드포인트의 경우 간격 은 1입니다. 이 값은 프레임의 모든 마이크로프레임이 데이터를 전달한다는 것을 나타냅니다. 이를 기반으로 10밀리초의 데이터를 보내려면 10프레임을 사용해야 하며, 총 전송 크기는 10*1024*8바이트, 등시 패킷은 각각 1024바이트입니다.

3단계: 쓰기 전송을 보내서 등시 OUT 엔드포인트로 데이터 보내기

이 절차에서는 등시 엔드포인트에 데이터를 쓰는 단계를 요약합니다.

  1. 보낼 데이터가 포함된 버퍼를 할당합니다.
  2. 데이터를 비동기적으로 보내는 경우 호출자가 할당한 이벤트 개체에 대한 핸들이 포함된 OVERLAPPED 구조를 할당하고 초기화합니다. 구조체를 0으로 초기화해야 합니다. 그렇지 않으면 호출이 실패합니다.
  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 구조를 할당합니다. 구조체를 0으로 초기화해야 합니다. 그렇지 않으면 호출이 실패합니다.
  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;
}