Alright, so I have extracted the data and reading it in a hex editor, for a 5 second capture. I see that there is 3,422 0xFFD8
JPEG Start of Image (SOI) and 3,820 0xFFD9
JPEG End of Image (EOI).
There is a difference of 402 0xFFD8
JPEG SOI missing, is this normal for MJPEG streams of 1080P @ 60FPS.
What are some clues I can tell which is the start of new JPEG frame and end of it? Are UVC headers included in the capture that is placed in the buffer which I've extracted?
Anyhow I will leave my code here for others may find it helpful to get their WinUSB working:
/*
1 sec = 1,000 ms
camera is 60 FPS.
1,000ms/60FPS = 16.666 ms / camera frame input stream.
*/
#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <Winusb.h>
#include <Usb.h>
#include <cfgmgr32.h>
#include <stdio.h> // For printf
#include <iostream>
#include <string>
#include <initguid.h>
#define ISOCH_DATA_SIZE_MS 1 // How long to capture? (Do not use this as any midflight errors will throw off the entire previous data stream and need to start over streaming again).
#define ISOCH_TRANSFER_COUNT 5000 // How many instances of requested capture time to be repeated.
DEFINE_GUID(GUID_DEVINTERFACE_USBApplication1, 0xD3CACD43, 0xD2AF, 0x4589, 0x8A, 0x20, 0x7B, 0x1B, 0xB2, 0x08, 0xC4, 0x75); // Device Interface GUID.
typedef struct _DEVICE_DATA {
BOOL HandlesOpen;
WINUSB_INTERFACE_HANDLE WinusbHandle;
WINUSB_INTERFACE_HANDLE AssociatedInterfaceHandle;
HANDLE DeviceHandle;
TCHAR DevicePath[MAX_PATH];
UCHAR IsochInPipe;
ULONG IsochInTransferSize;
ULONG IsochInPacketCount;
} 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;
printf("Initiating: `WinUsb_GetAssociatedInterface`\n");
result = WinUsb_GetAssociatedInterface(DeviceData->WinusbHandle, 0, &DeviceData->AssociatedInterfaceHandle);
if (result == FALSE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
std::cerr << "WinUsb_GetAssociatedInterface failed to get handle for interface 0." << std::endl;
CloseHandle(DeviceData->WinusbHandle);
return hr;
}
else if (result)
{
printf("`WinUsb_GetAssociatedInterface` has been Initiated.\n\n");
}
printf("Initiating: `WinUsb_QueryInterfaceSettings`\n");
result = WinUsb_QueryInterfaceSettings(DeviceData->AssociatedInterfaceHandle, 1, &usbInterface);
if (result == FALSE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
std::cerr << "WinUsb_QueryInterfaceSettings failed to get USB interface for Alternate Setting 1." << std::endl;
CloseHandle(DeviceData->AssociatedInterfaceHandle);
return hr;
}
else if (result)
{
printf("`WinUsb_QueryInterfaceSettings` has been Initiated.\n\n");
}
printf("Initiating: `WinUsb_SetCurrentAlternateSetting`\n");
// Select the alternate setting for the video stream interface
result = WinUsb_SetCurrentAlternateSetting(DeviceData->AssociatedInterfaceHandle, 1);
if (result == FALSE)
{
printf("`WinUsb_SetCurrentAlternateSetting`: Failed to set alternate setting.\n");
//WinUsb_Free(&DeviceData->AssociatedInterfaceHandle);
CloseHandle(&DeviceData->AssociatedInterfaceHandle);
return hr;
}
else if (result)
{
printf("`WinUsb_SetCurrentAlternateSetting` has been Initiated.\n\n");
}
for (i = 0; i < usbInterface.bNumEndpoints; i++) {
result = WinUsb_QueryPipeEx(
DeviceData->AssociatedInterfaceHandle,
1,
(UCHAR)i,
&pipe);
if (result == FALSE) {
hr = HRESULT_FROM_WIN32(GetLastError());
printf("WinUsb_QueryPipeEx failed to get USB pipe.\n");
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
if (pipe.PipeType == UsbdPipeTypeIsochronous)
{
// Check if this is an IN endpoint (bit 7 set)
if ((pipe.PipeId & 0x80) != 0)
{
DeviceData->IsochInPipe = pipe.PipeId;
wprintf(L"Isochronous `IN` Pipe ID: [ %d ]\n", DeviceData->IsochInPipe);
}
else
{
wprintf(L"Isochronous OUT pipe found (ID: %d), skipping.\n", pipe.PipeId);
return hr;
}
if (pipe.MaximumBytesPerInterval == 0 || (pipe.Interval == 0)) {
hr = E_INVALIDARG;
wprintf(L"Isoch Out: MaximumBytesPerInterval or Interval value is 0.\n");
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
else
{
std::wcout << "Isochronous `IN` MaximumBytesPerInterval Size: " << pipe.MaximumBytesPerInterval << std::endl;
// 1ms = 1 frame.
// (ISOCH_DATA_SIZE_MS = per ms, pipe.MaximumBytesPerInterval = 3072 bytes per microframe, x 8 microframes) = total size of data in terms of total size of Frames.
// Calculating, how large of data in terms of total amount of time (frames) requested:
DeviceData->IsochInTransferSize = ISOCH_DATA_SIZE_MS * pipe.MaximumBytesPerInterval * (8 / pipe.Interval);
std::wcout << "Isochronous `IN` Pipe Total Transfer Size (with requested capture time): " << DeviceData->IsochInTransferSize << std::endl;
// 1 frame = 8 microframes or (8 x 125 microseconds).
// Calculating how many microframes in total size of data transfer of time (frames) requested:
DeviceData->IsochInPacketCount = DeviceData->IsochInTransferSize / pipe.MaximumBytesPerInterval;
std::wcout << "Isochronous `IN` Pipe Total Packet Count (Total Microframes): " << DeviceData->IsochInPacketCount << std::endl;
std::wcout << "Isochronous Requested Capture Time: " << ISOCH_DATA_SIZE_MS << "ms." << std::endl;
std::wcout << "Isochronous Requested repeated amount: " << ISOCH_TRANSFER_COUNT << std::endl;
}
}
}
return hr;
}
VOID SendIsochInTransfer(_Inout_ PDEVICE_DATA DeviceData)
{
DWORD lastError = 0;
LPOVERLAPPED overlapped;
overlapped = NULL;
PUCHAR readBuffer = NULL;
BOOL result = FALSE;
ULONG numBytes;
ULONG i;
ULONG j;
//ULONG length = DeviceData->IsochInTransferSize;
WINUSB_ISOCH_BUFFER_HANDLE isochReadBufferHandle;
PUSBD_ISO_PACKET_DESCRIPTOR isochPackets;
BOOL ContinueStream = FALSE;
ULONG totalTransferSize;
printf("\nPerforming to Read transfer:\n");
// Calculate total transfer size of how many instances should the requested capture time should be repeated:
totalTransferSize = DeviceData->IsochInTransferSize * ISOCH_TRANSFER_COUNT;
printf("Total Transfer Size WITH requested repeat amount: %lu\n\n", totalTransferSize);
// Allocate memory for the read buffer
readBuffer = new UCHAR[totalTransferSize];
if (readBuffer == NULL)
{
wprintf(L"Unable to allocate memory.\n");
goto Error;
}
ZeroMemory(readBuffer, totalTransferSize);
// Allocate memory for the isoch packets
isochPackets = new USBD_ISO_PACKET_DESCRIPTOR[DeviceData->IsochInPacketCount * ISOCH_TRANSFER_COUNT];
ZeroMemory(isochPackets, DeviceData->IsochInPacketCount * ISOCH_TRANSFER_COUNT);
// Allocate memory for overlapped
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;
}
}
printf("Initiating: `WinUsb_RegisterIsochBuffer`\n");
// Register the isoch buffer
result = WinUsb_RegisterIsochBuffer(
DeviceData->AssociatedInterfaceHandle,
DeviceData->IsochInPipe,
readBuffer,
DeviceData->IsochInTransferSize * ISOCH_TRANSFER_COUNT,
&isochReadBufferHandle);
if (!result)
{
lastError = GetLastError();
wprintf(L"Isoch buffer registration failed. Error: %x\n", lastError);
goto Error;
}
else if (result)
{
printf("`WinUsb_SetCurrentAlternateSetting` has been Initiated.\n\n");
}
// Validate parameters before calling WinUsb_ReadIsochPipeAsap:
// Check if the isochReadBufferHandle is valid
if (isochReadBufferHandle == INVALID_HANDLE_VALUE) {
wprintf(L"Invalid isochReadBufferHandle.\n");
goto Error;
}
if (DeviceData->IsochInPacketCount == 0) {
wprintf(L"Invalid number of packets: %lu\n", DeviceData->IsochInPacketCount);
goto Error;
}
if (isochPackets == NULL) {
wprintf(L"Invalid isochPackets array.\n");
goto Error;
}
// Start reading from the isoch pipe
i = 0; // Reset the counter, each count is 1ms of data.
while (i < ISOCH_TRANSFER_COUNT) // While loop is to test the 'ContinueStream' is continuous without any midflight errors:
{
printf("Value of `ContinueStream` is: %d\n", ContinueStream);
printf("Initiating: `WinUsb_ReadIsochPipeAsap` and `i` is: %d\n\n", i);
// Perform the isochronous read, the parameters reads data based on the total capture time requested:
result = WinUsb_ReadIsochPipeAsap(
isochReadBufferHandle,
DeviceData->IsochInTransferSize * i, // offest
DeviceData->IsochInTransferSize,
ContinueStream,
DeviceData->IsochInPacketCount,
&isochPackets[i * DeviceData->IsochInPacketCount], &overlapped[i]);
lastError = GetLastError();
if (!result && lastError != ERROR_IO_PENDING) // If results fail AND there is no pending I/O, then there is an error.
{
printf("`WinUsb_ReadIsochPipeAsap` failed. Error: %x\n", lastError);
// Log detailed information about the parameters
printf("Parameters:\n");
printf("IsochReadBufferHandle: %p\n", isochReadBufferHandle);
printf("Offset: %lu\n", DeviceData->IsochInTransferSize * i);
printf("ContinueStream: %d\n", ContinueStream);
printf("IsochInPacketCount: %lu\n", DeviceData->IsochInPacketCount);
printf("IsochPackets: %p\n", &isochPackets[i * DeviceData->IsochInPacketCount]);
goto Error;
}
if (!result && lastError != ERROR_IO_PENDING && ContinueStream) // If the stream failed and the `ContinueStream` is true, need to start a new continue stream again since we need a continous stream with no errors.
{
ContinueStream = FALSE;
i = 0;
DWORD lastError = GetLastError();
printf("!result && lastError != ERROR_IO_PENDING && `ContinueStream`. Error: %x\n", lastError);
continue;
}
printf("`WinUsb_ReadIsochPipeAsap` has been Initiated. `i` is: %d\n", i);
printf("Value of `ContinueStream` is: %d\n\n", ContinueStream);
ContinueStream = TRUE;
i++;
if (i == ISOCH_TRANSFER_COUNT) // Note: `WinUsb_ReadIsochPipeAsap` uses 0 based-indexing while `ISOCH_TRANSFER_COUNT` is 1 based-indexing.
{
break;
}
}
i = 0; // Reset the counter, each count is 1ms of data.
// Wait for transfers to complete
for (i = 0; i < ISOCH_TRANSFER_COUNT; i++)
{
result = WinUsb_GetOverlappedResult(
DeviceData->AssociatedInterfaceHandle,
&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);
}
// Dump the data to a file
FILE* file = fopen("isoch_data.bin", "wb");
if (file != NULL)
{
fwrite(readBuffer, 1, totalTransferSize, file);
fclose(file);
printf("Data successfully written to isoch_data.bin\n");
}
else
{
printf("Failed to open file for writing.\n");
}
Error:
// Cleanup resources
if (isochReadBufferHandle != INVALID_HANDLE_VALUE && isochReadBufferHandle != NULL) {
result = WinUsb_UnregisterIsochBuffer(isochReadBufferHandle);
if (!result) {
DWORD lastError = GetLastError();
wprintf(L"Failed to unregister isoch read buffer. Error: %x\n", lastError);
}
}
if (readBuffer != NULL) {
delete[] readBuffer;
}
if (isochPackets != NULL) {
delete[] isochPackets;
}
return;
}
HRESULT OpenDevice(
_Out_ PDEVICE_DATA DeviceData,
_Out_opt_ PBOOL FailureDeviceNotFound);
VOID CloseDevice(
_Inout_ PDEVICE_DATA DeviceData);
HRESULT RetrieveDevicePath(
_Out_bytecap_(BufLen) LPTSTR DevicePath,
_In_ ULONG BufLen,
_Out_opt_ PBOOL FailureDeviceNotFound);
HRESULT OpenDevice(
_Out_ PDEVICE_DATA DeviceData,
_Out_opt_ PBOOL FailureDeviceNotFound)
{
HRESULT hr = S_OK;
BOOL bResult;
DeviceData->HandlesOpen = FALSE;
hr = RetrieveDevicePath(DeviceData->DevicePath,
sizeof(DeviceData->DevicePath),
FailureDeviceNotFound);
if (FAILED(hr)) {
return hr;
}
DeviceData->DeviceHandle = CreateFile(DeviceData->DevicePath,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (INVALID_HANDLE_VALUE == DeviceData->DeviceHandle) {
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
bResult = WinUsb_Initialize(DeviceData->DeviceHandle,
&DeviceData->WinusbHandle);
if (FALSE == bResult) {
hr = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
DeviceData->HandlesOpen = TRUE;
return hr;
}
VOID CloseDevice(
_Inout_ PDEVICE_DATA DeviceData)
{
if (FALSE == DeviceData->HandlesOpen) {
return;
}
WinUsb_Free(DeviceData->WinusbHandle);
CloseHandle(DeviceData->DeviceHandle);
DeviceData->HandlesOpen = FALSE;
return;
}
HRESULT RetrieveDevicePath(
_Out_bytecap_(BufLen) LPTSTR DevicePath,
_In_ ULONG BufLen,
_Out_opt_ PBOOL FailureDeviceNotFound)
{
CONFIGRET cr = CR_SUCCESS;
HRESULT hr = S_OK;
PTSTR DeviceInterfaceList = NULL;
ULONG DeviceInterfaceListLength = 0;
if (NULL != FailureDeviceNotFound) {
*FailureDeviceNotFound = FALSE;
}
do {
cr = CM_Get_Device_Interface_List_Size(&DeviceInterfaceListLength,
(LPGUID)&GUID_DEVINTERFACE_USBApplication1,
NULL,
CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
if (cr != CR_SUCCESS) {
hr = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(cr, ERROR_INVALID_DATA));
break;
}
DeviceInterfaceList = (PTSTR)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
DeviceInterfaceListLength * sizeof(TCHAR));
if (DeviceInterfaceList == NULL) {
hr = E_OUTOFMEMORY;
break;
}
cr = CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_USBApplication1,
NULL,
DeviceInterfaceList,
DeviceInterfaceListLength,
CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
if (cr != CR_SUCCESS) {
HeapFree(GetProcessHeap(), 0, DeviceInterfaceList);
if (cr != CR_BUFFER_SMALL) {
hr = HRESULT_FROM_WIN32(CM_MapCrToWin32Err(cr, ERROR_INVALID_DATA));
}
}
} while (cr == CR_BUFFER_SMALL);
if (FAILED(hr)) {
return hr;
}
if (*DeviceInterfaceList == TEXT('\0')) {
if (NULL != FailureDeviceNotFound) {
*FailureDeviceNotFound = TRUE;
}
hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
HeapFree(GetProcessHeap(), 0, DeviceInterfaceList);
return hr;
}
hr = StringCbCopy(DevicePath,
BufLen,
DeviceInterfaceList);
HeapFree(GetProcessHeap(), 0, DeviceInterfaceList);
return hr;
}
int __cdecl wmain(
int Argc,
wchar_t* Argv[]
)
{
DEVICE_DATA deviceData;
HRESULT hr;
USB_DEVICE_DESCRIPTOR deviceDesc;
BOOL bResult;
BOOL noDevice;
ULONG lengthReceived;
UNREFERENCED_PARAMETER(Argc);
UNREFERENCED_PARAMETER(Argv);
hr = OpenDevice(&deviceData, &noDevice);
if (FAILED(hr)) {
if (noDevice) {
wprintf(L"Device not connected or driver not installed\n");
}
else {
wprintf(L"Failed looking for device, HRESULT 0x%x\n", hr);
}
return 0;
}
bResult = WinUsb_GetDescriptor(deviceData.WinusbHandle,
USB_DEVICE_DESCRIPTOR_TYPE,
0,
0,
(PBYTE)&deviceDesc,
sizeof(deviceDesc),
&lengthReceived);
if (FALSE == bResult || lengthReceived != sizeof(deviceDesc)) {
wprintf(L"Error among LastError %d or lengthReceived %d\n",
FALSE == bResult ? GetLastError() : 0,
lengthReceived);
CloseDevice(&deviceData);
return 0;
}
printf("\nDevice found: VID_%04X&PID_%04X; bcdUsb %04X\n\n",
deviceDesc.idVendor,
deviceDesc.idProduct,
deviceDesc.bcdUSB);
hr = GetIsochPipes(&deviceData);
if (SUCCEEDED(hr)) {
SendIsochInTransfer(&deviceData);
}
else {
wprintf(L"GetIsochPipes failed with HRESULT: 0x%08X\n", hr);
}
CloseDevice(&deviceData);
return 0;
}