Skip to content

Windows 11 support via the in-box WinUSB driver (driver installer, robust streaming, CI) - #702

Open
kebasaa wants to merge 10 commits into
OpenKinect:masterfrom
kebasaa:windows11-winusb
Open

kebasaa wants to merge 10 commits into
OpenKinect:masterfrom
kebasaa:windows11-winusb

Conversation

@kebasaa

@kebasaa kebasaa commented Nov 6, 2026

Copy link
Copy Markdown

Summary

The Kinect for Windows SDK's camera driver (kinectcamera.sys) no longer loads on Windows 11 with Memory integrity (core isolation) enabled, so the Kinect v1 has no working driver there. This PR makes libfreenect the replacement:

  • Driver: every Kinect function libfreenect uses is bound to Microsoft's in-box, signed winusb.sys, and everything else runs in user mode through libusb.
  • Installer: a small installer does the binding in one command.
  • Robustness: several bugs that kept libfreenect from working reliably on Windows are fixed. Some of them affect all platforms.
  • CI: the project gets CI on Windows, Linux and macOS.

Tested on Windows 11 with Memory integrity on, with an Xbox 360 Kinect (model 1414). Results are under "Testing" below.

Changes, one commit each

  1. Wrappers and examples build without pthreads-win32 (Can't build libfreenect on Windows 10/11 "LNK1120: 8 unresolved externals" #655, Libfreenect Solution Build Error-Windows #647, Can't build libfreenect.sln on Windows #561). A header-only pthread subset over native Win32 threads is used when pthreads-win32 isn't installed. FindThreads.cmake still prefers the real library.
  2. MSVC fixes.
    • The static libraries no longer overwrite the DLLs' import libraries (freenect.lib, freenect_sync.lib). That also fixes LNK1104 in parallel builds.
    • Findlibusb-1.0.cmake now finds libusb-1.0.lib, as shipped by vcpkg and the official binaries (Libfreenect Solution Build Error-Windows #647).
    • regtest writes its images in binary mode; text mode corrupted them on Windows.
  3. WinUSB isochronous streaming (libusb ≥ 1.0.26).
    • fnusb_start_iso() reports failure instead of streaming silently black.
    • Packets whose own status isn't COMPLETED are no longer parsed as data (all platforms).
    • fnusb_stop_iso() can't wait forever.
    • Windows keeps 64 ms of transfers queued.
    • The libusb version is checked at init.
    • UsbDk is available as an opt-in (LIBFREENECT_USBDK=1).
  4. Audio streaming is refused on Windows unless LIBFREENECT_WINDOWS_AUDIO_UNSAFE=1 is set. In testing, stalled audio isochronous transfers were never completed by the kernel, which left the process unkillable and hung Windows shutdown. Camera, motor, LED and accelerometer are unaffected.
  5. send_cmd resynchronises the camera command channel (all platforms).
    • The problem: a single unread reply left every later command reading the previous command's reply ("Bad cmd 03 != 16") until the camera was reset.
    • Stale and unrelated replies are now discarded.
    • Waiting for a reply times out after 2 s of wall-clock time.
  6. freenect-driver-install.
    • It generates an INF that only references winusb.inf, builds and signs its catalog with a one-off local certificate whose private key is destroyed immediately, trusts that certificate, and installs the package.
    • --list, --uninstall and --package-only are included.
    • It uses only documented Windows APIs and adds no new dependency.
    • The mic-array interfaces stay with usbaudio.sys.
  7. Audio firmware. It's now found in the usual Windows locations, and the new freenect-fwloader uploads it to every connected Kinect. This also fixes two leaks in upload_firmware().
  8. Examples exit cleanly on Ctrl+C and on closing the terminal: SIGINT/SIGTERM/SIGHUP, and on Windows SetConsoleCtrlHandler. camtest also accepts a run time in seconds and prints frame rates.
  9. Removes platform/windows/inf (libusb0 packages) and the unused libusbemu layer. This commit is separate in case you'd rather keep them.
  10. GitHub Actions CI and a vcpkg manifest (Provide binary files for windows #622, Add vcpkg installation instructions #660).
    • Jobs: Windows x64 and ARM64 (MSVC with vcpkg), Ubuntu 24.04 on x86_64 and arm64, and macOS on Apple Silicon.
    • Smoke tests that need no Kinect.
    • The Windows binaries are uploaded as artifacts.

Every commit builds on its own with MSVC.

Testing

Hardware: Windows 11, Memory integrity on, Xbox 360 Kinect (1414) with its power adapter, libusb 1.0.30, MSVC 2026.

Test Result
freenect-driver-install --install / --list Motor, camera and audio on WinUSB. On the 1414, the camera and audio enumerate only once the motor has a driver, and then pick up the staged package by themselves
--uninstall, then --install again without replugging Devices, packages and certificates removed; reinstall works without hanging
freenect-camtest 20 Video 29.2–29.4 fps with 0% frames missing; depth 27.4–28.4 fps (see "Known limitations")
freenect-tiltdemo Tilt, LED and accelerometer work; stops cleanly on Ctrl+C
freenect-regtest RGB, raw, registered and mm depth images are correct, and registered depth lines up with RGB
freenect-fwloader Firmware uploads and launches; a second run finds it running
Ctrl+C and window close All console tools exit cleanly

The send_cmd recovery was also tested against a simulated camera command channel: a stale reply, a channel one reply out of step, a late unrelated reply, and a camera that never answers.

CI is green on all jobs in the fork.

Known limitations

Notes for reviewers

  • Certificate trust: --install adds a locally generated certificate to the machine's Trusted Root and Trusted Publishers stores, as Zadig and libwdi do. Windows requires the driver package to be signed by a trusted publisher. The private key is destroyed right after the catalog is signed, so that certificate can't sign anything else, and --uninstall removes it.
  • Behaviour changes on other platforms:
    • commit 3: failed packets are skipped and a failed stream start returns an error;
    • commit 5: send_cmd resynchronises;
    • commit 8: the examples' exit handling.

c_sync, the C++ wrapper and the threaded examples needed
pthreads-win32 on Windows, and configuring failed without it
(OpenKinect#655, OpenKinect#647, OpenKinect#561).

Add platform/windows/pthread/pthread.h, a header-only subset of POSIX
threads built on native SRW locks, condition variables and
_beginthreadex. It covers exactly the calls the tree uses: create,
join, mutex init/lock/trylock/unlock/destroy, and cond
init/wait/signal/broadcast/destroy, including the static initialisers.
It needs no import library.

FindThreads.cmake still prefers a real pthreads-win32 when one is
installed. Otherwise it points THREADS_PTHREADS_INCLUDE_DIR at the
subset and leaves CMAKE_THREAD_LIBS_INIT empty, so no CMakeLists or
source file has to change.
- With MSVC, the static freenectstatic and freenect_sync_static
  libraries were written to freenect.lib and freenect_sync.lib, which
  are also the import libraries of freenect.dll and freenect_sync.dll.
  Whichever linked last won, and parallel builds failed with LNK1104.
  On MSVC the static libraries are now freenect_static.lib and
  freenect_sync_static.lib; other platforms keep their names.
- Findlibusb-1.0.cmake also accepts the library name libusb-1.0, which
  vcpkg and the official Windows binaries ship (libusb-1.0.lib). Before,
  CMake could not find libusb on Windows without -DLIBUSB_1_LIBRARY
  (OpenKinect#647).
- regtest opened its image files with "w", which is text mode on
  Windows, so every 0x0A byte became 0x0D 0x0A and all four images were
  corrupt. It now uses "wb".
With WinUSB, Microsoft's in-box driver, libfreenect streams from user
mode on Windows 11 with Memory integrity enabled, where the Kinect for
Windows SDK's kernel driver is blocked. This needs libusb 1.0.26 or
newer: WinUSB isochronous support arrived in 1.0.23, and 1.0.24 and
1.0.25 had a Windows isochronous regression.

- fnusb_start_iso() returned 0 even when no transfer could be
  submitted, as happens with a libusb0 driver, so streams stayed
  silently black. It now frees everything and returns the libusb error
  when nothing is in flight, warns when only some transfers start, and
  explains LIBUSB_ERROR_NOT_SUPPORTED (wrong driver, or Windows older
  than 8.1) and LIBUSB_ERROR_ACCESS.
- iso_callback() passed every packet to the stream parser without
  checking the packet's own status. Packets that failed or were never
  filled were parsed as data, causing "Invalid magic" errors and
  resyncs that also discarded good frames. They are now passed on as
  empty, so the parser counts them as lost through the sequence
  numbers. This applies to all platforms.
- fnusb_stop_iso() skips transfers that were never allocated, and waits
  at most 3 s (in 100 ms steps) for cancellations instead of forever.
  Transfers still outstanding after that are leaked rather than freed
  while libusb may still own them.
- Windows keeps 16 transfers of 32 packets in flight (64 ms per stream,
  up from 32 ms). PKTS_PER_XFER % 8 == 0, which WinUSB requires, and
  PKTS_PER_XFER * NUM_XFERS <= 1000 are now compile-time checks.
- freenect_init() logs the libusb version and warns on Windows below
  1.0.26.
- LIBFREENECT_USBDK=1 opts in to libusb's UsbDk backend on Windows.

Hardware test on Windows 11, Xbox 360 Kinect (1414), libusb 1.0.30:
camtest streams depth and video; with the failed-packet change, video
went from 7.4% to 0% of frames missing.
In a hardware test on Windows 11 (WinUSB, Xbox 1414 with audios.bin),
the audio isochronous streams reported LIBUSB_TRANSFER_STALL. The
outstanding transfers were never completed by the kernel, so the
process could not be terminated, and Windows hung during restart
because shutdown waits for that I/O. The machine had to be hard-reset.

Until the audio path is understood on Windows, freenect_start_audio()
returns -1 there with an explanation, unless
LIBFREENECT_WINDOWS_AUDIO_UNSAFE=1 is set. The check runs before
anything is allocated or submitted. Camera, motor, LED, accelerometer
and the firmware upload are unaffected, and stopped cleanly in the same
tests.

Also: if the audio OUT stream fails to start, stop the IN stream that
was already started. micview checks the result of
freenect_start_audio() and exits instead of running without audio.
In a hardware test on Windows 11 (Xbox 1414), the camera failed to
initialise with "send_cmd: Bad cmd 03 != 16" and kept failing on every
run. send_cmd() took the first reply the camera returned and failed
hard if its cmd or tag did not match; the camera's actual reply was
left unread and returned for the next command, so from then on every
command read the previous command's reply.

- Before sending, discard replies that are already waiting (up to 8),
  with a warning.
- After sending, skip replies whose cmd or tag does not match and keep
  waiting for the matching one (at most 8, then fail).
- Give up after 2 s of wall-clock time (GetTickCount64 or
  CLOCK_MONOTONIC) instead of polling forever. Counting polls does not
  work: on Windows each 1 ms sleep lasts about 15 ms.
- A failing IN transfer returns its own error code instead of the OUT
  transfer's result.

Tested against a simulated camera channel (the real flags.c with
fnusb_control stubbed): a stale cmd 0003 reply before cmd 0016, a
channel that stays one reply out of step, a late unrelated reply, and a
camera that never answers, which times out after 2000 ms. On the
Kinect, camtest started reliably in repeated runs afterwards.
On Windows 11 with Memory integrity enabled, the Kinect for Windows SDK
camera driver is blocked, and Zadig is a manual, per-device step.
freenect-driver-install binds every Kinect v1 function libfreenect
drives to Microsoft's in-box WinUSB driver in one command. It uses only
documented Windows APIs and adds no third-party dependency.

--install (Administrator):
- Writes %ProgramData%\libfreenect\driver\libfreenect-kinect.inf, which
  references only the in-box winusb.inf (Include/Needs WINUSB.NT), so no
  binaries or co-installers are shipped.
- Builds a catalog for it with the CryptCAT API. The member is hashed
  with SHA-1, because the driver store looks package files up by their
  SHA-1 hash and rejects a SHA-256-only catalog with
  ERROR_FILE_HASH_NOT_IN_CATALOG. The catalog signature is SHA-256.
- Creates a self-signed code-signing certificate, signs the catalog with
  it, and deletes the private key immediately, so the certificate that
  is trusted can never sign anything else.
- Adds the certificate to the local machine Root and TrustedPublisher
  stores, stages the package with SetupCopyOEMInf, and installs it on
  every present Kinect function (UpdateDriverForPlugAndPlayDevices,
  non-interactive). Functions that appear later, such as the camera and
  audio of the Xbox 1414 once the motor has a driver, pick it up from
  the driver store automatically.

--uninstall removes the Kinect device nodes, then every package
(recognised by its catalog name) and certificate the tool added, and
rescans, so no device is left bound to a deleted package. --list shows
the Kinect functions, their drivers and every device on the Kinect's
hub, with problem codes, and needs no elevation. --package-only DIR
builds and signs a package without changing the system.

Bound: camera 02AE/02BF, motor 02B0/02AF, audio controller 02AD/02BE,
and only interfaces 0 and 1 of 02BB/02C3, so the microphone-array
interfaces keep usbaudio.sys. CMake option BUILD_DRIVER_INSTALLER, on
by default on Windows. The README's Windows section now describes the
tool, libusb >= 1.0.26, and WinUSB instead of libusbK via Zadig.

Hardware test on Windows 11 with Memory integrity on, Xbox 1414:
--install, --list, --uninstall and reinstalling all work, and the
camera, motor and audio functions end up on WinUSB.
upload_firmware() searched only POSIX locations. On Windows it now also
searches %LOCALAPPDATA%\libfreenect, the directory of the executable,
and %ProgramData%\libfreenect; the existing locations keep their order.
The search is a plain list of directories, which also fixes two leaks
(the firmware buffer was never freed, and the FILE was not closed for
an empty file). A missing file returns -ENOENT and says where to put
it.

freenect-fwloader uploads the firmware to every connected Kinect whose
audio controller is still in its bootloader, using only the public API
(opening a device with FREENECT_DEVICE_AUDIO uploads and waits for
re-enumeration). --firmware FILE uses a specific image.

Hardware test on Windows 11, Xbox 1414: the firmware uploads and
launches; a second run finds it already running.
tiltdemo looped forever with no handler, so Ctrl+C killed it in the
middle of USB transfers. wavrecord caught only SIGINT and never stopped
audio or closed the device. On Windows, signal() never sees the console
window being closed.

examples/console_exit.h gives the console examples one shutdown path:
- POSIX: SIGINT, SIGTERM, SIGHUP and SIGQUIT request exit.
- Windows: SetConsoleCtrlHandler for Ctrl+C, Ctrl+Break, window close,
  logoff and shutdown; for the last three, the handler holds the
  process for up to 4 s until main() has stopped the streams and closed
  the device.
- fn_sleep_ms() sleeps in 100 ms steps and returns early on exit.

camtest and wavrecord poll with freenect_process_events_timeout()
(100 ms). wavrecord stops audio and closes the device before
freenect_shutdown(). tiltdemo calls freenect_sync_stop() on exit.
camtest accepts an optional run time in seconds ("freenect-camtest 20")
and then prints the depth and video frame rates.

Hardware test on Windows 11, Xbox 1414: camtest and tiltdemo stop on
Ctrl+C and when their window is closed.
platform/windows/inf ships libusb0 (libusb-win32) driver packages from
2010. libusb0 cannot do the isochronous transfers that video and depth
need with current libusb, and its catalogs are not accepted on current
Windows. freenect-driver-install replaces them.

platform/windows/libusb10emu is a libusb-1.0 emulation on top of
libusb-win32 that no CMakeLists.txt refers to.
platform/windows/README.TXT described it and is replaced by
platform/windows/README.md.
The workflow builds the library, the C sync and C++ wrappers and all
examples, including the OpenGL viewers, on:
- Windows x64 and ARM64 with MSVC, with libusb and freeglut from vcpkg;
- Ubuntu 24.04 on x86_64 and arm64;
- macOS on Apple Silicon.

Smoke tests that need no Kinect: freenect-fwloader must report that no
Kinect is present. On Windows x64, freenect-driver-install --list must
work, and --package-only must produce a catalog signed by the package
certificate with its key destroyed. The Windows jobs upload their
binaries as artifacts (OpenKinect#622).

vcpkg.json declares libusb, and freeglut for the examples behind the
"examples" feature, so a Windows build needs only the vcpkg toolchain
file (OpenKinect#660).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant