WinUSB デスクトップ アプリから USB 等時性転送を送信する

Windows 8.1 以降、WinUSB 関数のセットには、デスクトップ アプリケーションが USB デバイスの等時性エンドポイントとの間でデータを転送できるようにする API が含まれています。 このようなアプリケーションの場合、マイクロソフトが提供する Winusb.sys がデバイス ドライバーである必要があります。

この記事には以下の情報が含まれています。

  • 等時性転送の簡単な概要。
  • エンドポイント間隔の値に基づいてバッファー計算を転送します。
  • WinUSB 関数を使用して等時性データの読み取りと書き込みを行う転送の送信。

重要な API

Windows 8.1 以降、WinUSB 関数のセットには、デスクトップ アプリケーションが USB デバイスの等時性エンドポイントとの間でデータを転送できるようにする API が含まれています。 このようなアプリケーションの場合、マイクロソフトが提供する Winusb.sys がデバイス ドライバーである必要があります。

USB デバイスでは、等時性エンドポイントをサポートし、オーディオ/ビデオ ストリーミングなど、一定の速度で時間依存データを転送できます。 保証された配信はありません。 良好な接続ではパケットがドロップされることはありません。パケットが失われることは正常なことではなく、予想されることでもありませんが、等時性プロトコルはそのような損失を許容します。

ホスト コントローラーは、バスの予約期間中にデータを送受信します。これはバス間隔と呼ばれます。 バス間隔の単位は、バス速度によって異なります。 フル スピードの場合は 1 ミリ秒のフレーム、高速と SuperSpeed の場合は 250 マイクロ秒のマイクロフレームです。

ホスト コントローラーは、一定の間隔でデバイスをポーリングします。 読み取り操作の場合、エンドポイントがデータを送信する準備ができると、デバイスはバス間隔でデータを送信して応答します。 デバイスに書き込むには、ホスト コントローラーがデータを送信します。

アプリが 1 つのサービス間隔で送信できるデータの量

このトピックの「等時性パケット」という用語は、1 つのサービス間隔で転送されるデータの量を指します。 その値は USB ドライバー スタックによって計算され、アプリはパイプ属性をクエリするときに値を取得できます。

等時パケットのサイズによって、アプリが割り当てる転送バッファーのサイズが決まります。 バッファーはフレーム境界で終わる必要があります。 転送の合計サイズは、アプリが送受信するデータの量によって異なります。 アプリによって転送が開始された後、ホストは転送バッファをパケット化して、ホストが各間隔で許可される最大バイト数を送受信できるようにします。

データ転送では、すべてのバス間隔が使用されるわけではありません。 このトピックでは、使用されるバス間隔をサービス間隔と呼びます。

データが送信されるフレームを計算する方法

アプリでは、次の 2 つの方法のいずれかでフレームを指定できます。

  • 自動。 このモードでは、アプリは USB ドライバー スタックに次の適切なフレームで転送を送信するように指示します。 ドライバー スタックが開始フレームを計算できるように、アプリはバッファーが連続ストリームであるかどうかも指定する必要があります。
  • 現在のフレームより後の開始フレームを指定します。 アプリは、転送を開始してから USB ドライバー スタックが転送を処理するまでの遅延を考慮する必要があります。

コード例の説明

このトピックの例では、次の WinUSB 関数の使用方法を示します。

このトピックでは、高速デバイスへの 3 回の転送で 30 ミリ秒のデータを読み書きします。 パイプは、各サービス間隔で 1024 バイトを転送できます。 ポーリング間隔は 1 であるため、データはフレームのすべてのマイクロフレームで転送されます。 合計 30 フレームは 30*8*1024 バイトを伝送します。

読み取りと書き込みの転送を送信するための関数呼び出しも同様です。 アプリは、3 つの転送すべてを保持するのに十分な大きさの転送バッファーを割り当てます。 アプリは、WinUsb_RegisterIsochBuffer を呼び出すことによって、特定のパイプのバッファーを登録します。 この呼び出しは、転送の送信に使用される登録ハンドルを返します。 バッファーは後続の転送に再利用され、バッファー内のオフセットは次のデータ セットを送受信するように調整されます。

この例のすべての転送は非同期的に送信されます。 このために、アプリは、3 つの要素を持つ OVERLAPPED 構造体の配列を 1 回の転送ごとに 1 つ割り当てます。 アプリは、転送が完了したときに通知を受け取り、操作の結果を取得できるようにイベントを提供します。 このために、配列内の各 OVERLAPPED 構造体で、アプリはイベントを割り当て、hEvent メンバーのハンドルを設定します。

この図は、WinUsb_ReadIsochPipeAsap 関数を使用した 3 つの読み取り転送を示しています。 呼び出しでは、各転送のオフセットと長さを指定します。 ContinueStream パラメーターの値は、新しいストリームを示す FALSE です。 その後、アプリは、データの継続的なストリーミングを可能にするために、前の要求の最後のフレームの直後に後続の転送をスケジュールするよう要求します。 等時性パケットの数は、フレームあたりのパケット数 * フレーム数、8*10 として計算されます。 この呼び出しでは、アプリは開始フレーム番号の計算について心配する必要はありません。

winusb function for isochronous read transfer.

この図は、WinUsb_WriteIsochPipe 関数を使用した 3 つの書き込み転送を示しています。 呼び出しでは、各転送のオフセットと長さを指定します。 この場合、アプリはホスト コントローラーがデータの送信を開始できるフレーム番号を計算する必要があります。 出力時に、この関数は、前の転送で使用された最後のフレームに続くフレームのフレーム番号を受け取ります。 現在のフレームを取得するために、アプリは WinUsb_GetCurrentFrameNumber を呼び出します。 この時点で、USB ドライバー スタックが遅延パケットをドロップしないように、アプリは次の転送の開始フレームが現在のフレームよりも遅いことを確認する必要があります。 これを行うために、アプリは現実的な現在のフレーム番号を取得するために WinUsb_GetAdjustedFrameNumber を呼び出します (これは受信した現在のフレーム番号より後です)。 安全な側に置くために、アプリはさらに 5 つのフレームを追加し、転送を送信します。

winusb function for isochronous write transfer.

各転送が完了すると、アプリは WinUsb_GetOverlappedResultを呼び出して転送の結果を取得します。 操作が完了するまで呼び出しが返されないように、bWait パラメーターは TRUE に設定されます。 読み取りと書き込みの転送の場合、lpNumberOfBytesTransferred パラメーターは常に 0 です。 書き込み転送の場合、アプリは操作が正常に完了した場合、すべてのバイトが転送されたと想定します。 読み取り転送の場合、各等時性パケット (USBD_ISO_PACKET_DESCRIPTOR) の Length メンバーには、そのパケットで転送されたバイト数が間隔ごとに含まれます。 合計の長さを取得するために、アプリはすべての Length 値を追加します。

完了すると、アプリは WinUsb_UnregisterIsochBuffer を呼び出すことによって等時性バッファー ハンドルを解放します。

開始する前に

次のことを確認してください。

  • デバイス ドライバーは、マイクロソフトが提供するドライバー、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) に 2 つの等時性エンドポイントがあります。


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_EX から IntervalMaximumBytesPerInterval を取得します。 両方の等時性エンドポイントの場合、Interval は 1 です。 この値は、フレームのすべてのマイクロフレームがデータを伝送することを示します。 それに基づいて、10 ミリ秒のデータを送信するには 10 フレームが必要で、合計転送サイズは 10*1024*8 バイト、および各 1024 バイトの長さのアイソクロナス パケットが 80 個必要になります。

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