Senden isochroner USB-Übertragungen aus einer WinUSB-Desktop-App

Ab Windows 8.1 verfügen die WinUSB-Funktionen über APIs, die es einer Desktopanwendung ermöglichen, Daten an und von isochronen Endpunkten eines USB-Geräts zu übertragen. Für eine solche Anwendung muss der von Microsoft bereitgestellte Winusb.sys der Gerätetreiber sein.

Dieser Artikel bietet die folgenden Informationen:

  • Kurze Übersicht über isochrone Übertragungen.
  • Transferpufferberechnung basierend auf Endpunktintervallwerten.
  • Senden von Übertragungen, die isochrone Daten mit WinUSB Functions lesen und schreiben.

Wichtige APIs

Ab Windows 8.1 verfügen die WinUSB-Funktionen über APIs, mit denen eine Desktopanwendung Daten zu und von isochronen Endpunkten eines USB-Geräts übertragen kann. Für eine solche Anwendung muss der von Microsoft bereitgestellte Winusb.sys der Gerätetreiber sein.

Ein USB-Gerät kann isochrone Endpunkte unterstützen, um zeitabhängige Daten mit einer konstanten Rate zu übertragen, z. B. beim Audio-/Videostreaming. Es gibt keine garantierte Lieferung. Eine gute Verbindung sollte keine Pakete löschen, es ist nicht normal oder erwartet, dass Pakete verloren gehen, aber das isochrone Protokoll ist tolerant gegenüber solchen Verlusten.

Der Hostcontroller sendet oder empfängt Daten während reservierter Zeiträume auf dem Bus, die als Busintervalle bezeichnet werden. Die Einheit des Busintervalls hängt von der Busgeschwindigkeit ab. Für volle Geschwindigkeit sind es 1-Millisekunden-Frames, für Hochgeschwindigkeits- und SuperSpeed-Mikroframes 250 Mikrosekunden.

Der Hostcontroller ruft das Gerät in regelmäßigen Abständen ab. Bei Lesevorgängen reagiert das Gerät, wenn der Endpunkt zum Senden von Daten bereit ist, indem es Daten im Busintervall sendet. Zum Schreiben auf das Gerät sendet der Hostcontroller Daten.

Wie viele Daten kann die App in einem Dienstintervall senden?

Der Begriff isochrones Paket in diesem Thema bezieht sich auf die Datenmenge, die in einem Dienstintervall übertragen wird. Dieser Wert wird vom USB-Treiberstapel berechnet, und die App kann den Wert beim Abfragen von Pipeattributen abrufen.

Die Größe eines isochronen Pakets bestimmt die Größe des Übertragungspuffers, den die App zuordnet. Der Puffer muss an einer Framegrenze enden. Die Gesamtgröße der Übertragung hängt davon ab, wie viele Daten die App senden oder empfangen möchte. Nachdem die Übertragung von der App initiiert wurde, packt der Host den Übertragungspuffer, sodass der Host in jedem Intervall die maximal zulässigen Bytes pro Intervall senden oder empfangen kann.

Für eine Datenübertragung werden nicht alle Busintervalle verwendet. In diesem Thema werden verwendete Busintervalle als Dienstintervalle bezeichnet.

Berechnen des Frames, in dem Daten übertragen werden

Die App kann den Frame auf zwei Arten angeben:

  • Automatisch. In diesem Modus weist die App den USB-Treiberstapel an, die Übertragung im nächsten geeigneten Frame zu senden. Die App muss auch angeben, ob es sich bei dem Puffer um einen fortlaufenden Datenstrom handelt, damit der Treiberstapel den Startframe berechnen kann.
  • Geben Sie den Startframe an, der sich später als der aktuelle Frame befindet. Die App sollte die Latenz zwischen dem Zeitpunkt berücksichtigen, zu dem die App die Übertragung startet und wann der USB-Treiberstapel sie verarbeitet.

Codebeispieldisk

Die Beispiele in diesem Thema veranschaulichen die Verwendung dieser WinUSB-Funktionen:

In diesem Thema lesen und schreiben wir 30 Millisekunden Daten in drei Übertragungen an ein Hochgeschwindigkeitsgerät. Die Pipe kann 1024 Bytes in jedem Dienstintervall übertragen. Da das Abrufintervall 1 ist, werden Daten in jedem Mikroframe eines Frames übertragen. Insgesamt werden 30 Frames 30 * 8 * 1024 Bytes enthalten.

Die Funktionsaufrufe zum Senden von Lese- und Schreibübertragungen sind ähnlich. Die App weist einen Übertragungspuffer zu, der groß genug ist, um alle drei Übertragungen aufzunehmen. Die App registriert den Puffer für eine bestimmte Pipe, indem WinUsb_RegisterIsochBuffer aufgerufen wird. Der Aufruf gibt ein Registrierungshandle zurück, das zum Senden der Übertragung verwendet wird. Der Puffer wird für nachfolgende Übertragungen wiederverwendet, und der Offset im Puffer wird angepasst, um den nächsten Datensatz zu senden oder zu empfangen.

Alle Übertragungen im Beispiel werden asynchron gesendet. Dazu weist die App ein Array der ÜBERLAPPENDEN Struktur mit drei Elementen zu, eines für jede Übertragung. Die App stellt Ereignisse bereit, sodass sie benachrichtigt werden kann, wenn die Übertragungen abgeschlossen sind und die Ergebnisse des Vorgangs abrufen können. Hierzu weist die App in jeder OVERLAPPED-Struktur im Array ein Ereignis zu und legt das Handle im hEvent-Element fest.

Diese Abbildung zeigt drei Leseübertragungen mithilfe der WinUsb_ReadIsochPipeAsap-Funktion . Der Aufruf gibt den Offset und die Länge jeder Übertragung an. Der ContinueStream-Parameterwert ist FALSE, um einen neuen Stream anzugeben. Danach fordert die App an, dass nachfolgende Übertragungen unmittelbar nach dem letzten Frame der vorherigen Anforderung geplant werden, um ein kontinuierliches Streaming von Daten zu ermöglichen. Die Anzahl isochroner Pakete wird als Pakete pro Frame * Anzahl der Frames berechnet; 8*10. Für diesen Aufruf muss sich die App keine Gedanken über die Berechnung der Startframenummer machen.

winusb-Funktion für isochrone Leseübertragung.

Diese Abbildung zeigt drei Schreibübertragungen mithilfe der WinUsb_WriteIsochPipe-Funktion . Der Aufruf gibt den Offset und die Länge jeder Übertragung an. In diesem Fall muss die App die Framenummer berechnen, in der der Hostcontroller mit dem Senden von Daten beginnen kann. Bei der Ausgabe empfängt die Funktion die Framenummer des Frames, die dem letzten Frame folgt, der in der vorherigen Übertragung verwendet wurde. Um den aktuellen Frame abzurufen, ruft die App WinUsb_GetCurrentFrameNumber auf. An diesem Punkt muss die App sicherstellen, dass der Startframe der nächsten Übertragung später als der aktuelle Frame ist, damit der USB-Treiberstapel keine verspäteten Pakete abgibt. Dazu ruft die App WinUsb_GetAdjustedFrameNumber auf, um eine realistische aktuelle Framenummer zu erhalten (dies ist später als die empfangene aktuelle Framenummer). Um auf der sicheren Seite zu sein, fügt die App fünf weitere Frames hinzu und sendet dann die Übertragung.

winusb-Funktion für die isochrone Schreibübertragung.

Nach Abschluss jeder Übertragung ruft die App die Ergebnisse der Übertragung ab, indem sie WinUsb_GetOverlappedResult aufruft. Der bWait-Parameter ist auf TRUE festgelegt, sodass der Aufruf erst zurückgegeben wird, wenn der Vorgang abgeschlossen ist. Bei Lese- und Schreibübertragungen ist der lpNumberOfBytesTransferred-Parameter immer 0. Bei einer Schreibübertragung geht die App davon aus, dass alle Bytes übertragen wurden, wenn der Vorgang erfolgreich abgeschlossen wurde. Bei einer Leseübertragung enthält das Length-Element jedes isochronen Pakets (USBD_ISO_PACKET_DESCRIPTOR) die Anzahl der in diesem Paket übertragenen Bytes pro Intervall. Um die Gesamtlänge abzurufen, fügt die App alle Length-Werte hinzu.

Wenn sie fertig ist, gibt die App die isochronen Pufferhandles frei, indem sie WinUsb_UnregisterIsochBuffer aufruft.

Vorbereitung

Stellen Sie sicher, dass

  • Der Gerätetreiber ist der von Microsoft bereitgestellte Treiber WinUSB (Winusb.sys). Dieser Treiber ist im Ordner \Windows\System32\ enthalten. Weitere Informationen finden Sie unter Installation von WinUSB (Winusb.sys).

  • Sie haben zuvor ein WinUSB-Schnittstellenhandle für das Gerät erhalten, indem Sie WinUsb_Initialize aufrufen. Alle Vorgänge werden mit diesem Handle ausgeführt. Lesen Sie So greifen Sie auf ein USB-Gerät mithilfe von WinUSB-Funktionen zu.

  • Die Einstellung der aktiven Schnittstelle weist isochrone Endpunkte auf. Andernfalls können Sie nicht auf die Pipes für die Zielendpunkte zugreifen.

Schritt 1: Suchen der isochronen Pipe in der aktiven Einstellung

  1. Rufen Sie die USB-Schnittstelle mit den isochronen Endpunkten ab, indem Sie WinUsb_QueryInterfaceSettings aufrufen.
  2. Auflisten der Pipes der Schnittstelleneinstellung, die die Endpunkte definiert.
  3. Rufen Sie für jeden Endpunkt die zugeordneten Pipeeigenschaften in einer WINUSB_PIPE_INFORMATION_EX-Struktur ab, indem Sie WinUsb_QueryPipeEx aufrufen. Die abgerufene WINUSB_PIPE_INFORMATION_EX Struktur, die Informationen zur isochronen Pipe enthält. Die -Struktur enthält Informationen über die Pipe, ihren Typ, die ID usw.
  4. Überprüfen Sie die Strukturmember, um zu ermitteln, ob es sich um die Pipe handelt, die für Übertragungen verwendet werden muss. Wenn dies der Grund ist, speichern Sie den PipeId-Wert . Fügen Sie im Vorlagencode member zur DEVICE_DATA-Struktur hinzu, die in Device.h definiert ist.

In diesem Beispiel wird gezeigt, wie Sie ermitteln, ob die aktive Einstellung über isochrone Endpunkte verfügt, und wie Sie Informationen darüber erhalten. In diesem Beispiel ist das Gerät ein SuperMUTT-Gerät. Das Gerät verfügt über zwei isochrone Endpunkte in der Standardschnittstelle, alternative Einstellung 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;
}

Das SuperMUTT-Gerät definiert seine isochronen Endpunkte in der Standardschnittstelle bei Einstellung 1. Der vorangehende Code ruft die PipeId-Werte ab und speichert sie in der DEVICE_DATA-Struktur.

Schritt 2: Abrufen von Intervallinformationen zur isochronen Pipe

Als Nächstes erhalten Sie weitere Informationen zu der Pipe, die Sie im Aufruf von WinUsb_QueryPipeEx erhalten haben.

  • Übertragungsgröße

    1. Rufen Sie aus der abgerufenen WINUSB_PIPE_INFORMATION_EX-Struktur die Werte MaximumBytesPerInterval und Interval ab.

    2. Berechnen Sie abhängig von der Menge der isochronen Daten, die Sie senden oder empfangen möchten, die Übertragungsgröße. Betrachten Sie beispielsweise diese Berechnung:

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

      Im Beispiel wird die Übertragungsgröße für 10 Millisekunden isochroner Daten berechnet.

  • Anzahl isochroner PaketeBetrachten Sie beispielsweise diese Berechnung:

    Um die Gesamtzahl der isochronen Pakete zu berechnen, die für die gesamte Übertragung erforderlich sind. Diese Informationen sind für Leseübertragungen erforderlich und werden als >IsochInTransferSize / pipe.MaximumBytesPerInterval;berechnet.

Dieses Beispiel zeigt das Hinzufügen von Code zu Schritt 1 und ruft die Intervallwerte für die isochronen Pipes ab.


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

...

Im vorherigen Code ruft die App Interval und MaximumBytesPerInterval von WINUSB_PIPE_INFORMATION_EX ab, um die Übertragungsgröße und die Anzahl der isochronen Pakete zu berechnen, die für die Leseübertragung erforderlich sind. Für beide isochrone Endpunkte ist Intervall 1. Dieser Wert gibt an, dass alle Microframes des Frames Daten enthalten. Basierend darauf benötigen Sie zum Senden von 10 Millisekunden Daten 10 Frames, die Gesamtübertragungsgröße beträgt 10 * 1024 * 8 Bytes und 80 isochrone Pakete mit jeweils 1024 Byte.

Schritt 3: Senden einer Schreibübertragung zum Senden von Daten an einen isochronen OUT-Endpunkt

In diesem Verfahren werden die Schritte zum Schreiben von Daten in einen isochronen Endpunkt zusammengefasst.

  1. Ordnen Sie einen Puffer zu, der die zu sendenden Daten enthält.
  2. Wenn Sie die Daten asynchron senden, ordnen Sie eine OVERLAPPED-Struktur , die ein Handle enthält, einem vom Aufrufer zugeordneten Ereignisobjekt zu, und initialisieren Sie sie. Die Struktur muss auf 0 (null) initialisiert werden, andernfalls schlägt der Aufruf fehl.
  3. Registrieren Sie den Puffer, indem Sie WinUsb_RegisterIsochBuffer aufrufen.
  4. Starten Sie die Übertragung, indem Sie WinUsb_WriteIsochPipeAsap aufrufen. Wenn Sie den Frame, in dem Daten übertragen werden, manuell angeben möchten, rufen Sie stattdessen WinUsb_WriteIsochPipe auf.
  5. Rufen Sie die Ergebnisse der Übertragung ab, indem Sie WinUsb_GetOverlappedResult aufrufen.
  6. Wenn Sie fertig sind, geben Sie das Pufferhandle frei, indem Sie WinUsb_UnregisterIsochBuffer, das überlappende Ereignishandle und den Übertragungspuffer aufrufen.

Hier ist ein Beispiel, das zeigt, wie eine Schreibübertragung gesendet wird.

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

Schritt 4: Senden einer Leseübertragung zum Empfangen von Daten von einem isochronen IN-Endpunkt

In diesem Verfahren werden die Schritte zum Lesen von Daten von einem isochronen Endpunkt zusammengefasst.

  1. Weisen Sie einen Übertragungspuffer zu, der am Ende der Übertragung Daten empfängt. Die Größe des Puffers muss auf der in Schritt 1 berechneten Übertragungsgröße basieren. Der Übertragungspuffer muss an einer Framegrenze enden.
  2. Wenn Sie die Daten asynchron senden, ordnen Sie eine OVERLAPPED-Struktur , die ein Handle enthält, einem vom Aufrufer zugeordneten Ereignisobjekt zu. Die Struktur muss auf 0 (null) initialisiert werden, andernfalls schlägt der Aufruf fehl.
  3. Registrieren Sie den Puffer, indem Sie WinUsb_RegisterIsochBuffer aufrufen.
  4. Ordnen Sie basierend auf der in Schritt 2 berechneten Anzahl isochroner Pakete ein Array von isochronen Paketen (USBD_ISO_PACKET_DESCRIPTOR) zu.
  5. Starten Sie die Übertragung, indem Sie WinUsb_ReadIsochPipeAsap aufrufen. Wenn Sie den Startframe, in dem Daten übertragen werden, manuell angeben möchten, rufen Sie stattdessen WinUsb_ReadIsochPipe auf.
  6. Rufen Sie die Ergebnisse der Übertragung ab, indem Sie WinUsb_GetOverlappedResult aufrufen.
  7. Wenn Sie fertig sind, geben Sie das Pufferhandle frei, indem Sie WinUsb_UnregisterIsochBuffer, das überlappende Ereignishandle, das Array isochroner Pakete und den Übertragungspuffer aufrufen.

Hier sehen Sie ein Beispiel, das zeigt, wie Sie eine Leseübertragung senden, indem Sie WinUsb_ReadIsochPipeAsap und WinUsb_ReadIsochPipe aufrufen.

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