nokhwa: cross-platform webcam capture in Rust with one Camera struct
Cross Platform Rust Library for Powerful Webcam/Camera Capture
At a glance
- What is it?
- nokhwa is a Rust library that turns a camera index and a requested format into decoded frames on Linux, Windows, macOS and in the browser, hiding V4L2, MSMF and AVFoundation behind a single Camera API. It is a good fit for tools that need frames as pixels rather than as encoded blobs, and its feature flags let you keep the binary small by dropping backends you do not use.
- Who is it for?
- Adopt nokhwa when you are writing Rust code that needs real pixels from a camera on more than one operating system and want to skip the per-platform FFI work yourself. Do not adopt it when you need audio, when you must support every camera without a device index you can enumerate, or when you want a stable published version, since the crate manifest sits at 0.11.0 while the newest listed release is 0.10.11 and the repository default branch is senpai.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 161 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem of reading a camera from three different APIs
Every desktop platform exposes webcams through a different native interface. Linux goes through Video4Linux, Windows through the Media Foundation capture stack, macOS through AVFoundation. Writing that layer yourself means unsafe FFI, per-platform device enumeration, and a frame format negotiation step that differs in each case.
nokhwa exists to remove that work. Its description calls it a cross-platform Rust webcam capture library, and the Korean word it takes its name from means to record. The design promise is narrow and clear: pick a camera by index, ask for a format, receive frames.
The intended audience is Rust developers building anything that reads a camera: a recording tool, a vision pipeline, a streaming helper, a test harness. The repository also documents a boundary that helps you decide quickly. Most users will work only with the Camera struct, and anyone who needs lower-level access can drop down to the raw capture backends under nokhwa::backends::capture::*.
That two-level design is the core choice in the library. The high level handles device selection, format negotiation and decoding so that common code stays short, while the low level stays available for the cases where the default negotiation picks something you did not want. The trade-off is that the high level gives you a fixed vocabulary of formats, and anything outside it means writing backend-specific code anyway.
From Camera::new to a decoded image buffer
The pipeline has four steps: choose a camera index, describe the format you want, construct the Camera, then pull frames and decode them.
The camera index is expressed as CameraIndex::Index(0) for the first camera in the system. The format request uses RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate), which asks for the highest resolution the camera can offer among formats that can be decoded to RGB, with frame rate as the tiebreaker. Passing the target type to new is what lets the library filter out formats you could not use anyway.
Constructing the Camera returns a Result, so a machine with no camera or a busy device surfaces as an error rather than a panic. From there, camera.frame() yields a frame, frame.buffer() gives you the raw bytes and its length, and frame.decode_image::<RgbFormat>() converts the frame into an image buffer you can work with directly.
The distinction between buffer() and decode_image() is the one to internalise. The buffer is the encoded payload the camera produced, which for many cameras is compressed JPEG data. The decoded image is raw pixels. Since the README example prints the size of both, the two lengths are directly comparable, and the difference is the cost of decoding.
Decoding support is a separate concern from capture. The manifest enables decoding-yuv and decoding-mjpeg by default, and decoding-ffmpeg exists as an opt-in that pulls the nokhwa-decoders crate with the ffmpeg feature. The README also names a decoding feature that enables mozjpeg decoding and is enabled by default, and the manifest carries a comment that the uvc input feature is disabled until soundness is proven and mozjpeg moves to 0.9.x.
Adding nokhwa to a crate and taking one frame
The library is a normal Cargo dependency, and the features you enable decide which backends get compiled in. The snippet the front page gives pins the 0.10 line, enables the native input backends together with the WGPU output, and adds the image crate so you can export to JPEG or PNG.
[dependencies.nokhwa]
version = "0.10"
# Use the native input backends, enable WGPU integration
features = ["input-native", "output-wgpu"]
# add this to enable exporting to a JPEG/PNG
[dependencies.image]
version = "0.25"
features = ["default-formats"]Note that the dependency snippet asks for version 0.10 while the manifest at the repository root declares 0.11.0. If you follow the snippet literally you will get the older line, so decide deliberately which one your project wants rather than discovering the difference later.
The minimum code you need to see whether a camera works is short enough to paste into a binary or a test:
// first camera in system
let index = CameraIndex::Index(0);
// request the absolute highest resolution CameraFormat that can be decoded to RGB.
let requested = RequestedFormat::new::<RgbFormat>(RequestedFormatType::AbsoluteHighestFrameRate);
// make the camera
let mut camera = Camera::new(index, requested).unwrap();
// get a frame
let frame = camera.frame().unwrap();
println!("Captured Single Frame of {}", frame.buffer().len());
// decode into an ImageBuffer
let decoded = frame.decode_image::<RgbFormat>().unwrap();
println!("Decoded Frame of {}", decoded.len());Both unwrap calls are the places your code will actually fail on real hardware: once when the camera cannot be opened at the requested format, and once when a frame cannot be fetched or decoded. A command line application built on the library sits in the examples folder, with separate projects for capture, decoder_test, jscam, setting and threaded-capture.
The backend table is the honest part of the documentation
The support matrix distinguishes five backends across four capabilities: reading frames, querying the system device list, querying the capabilities of a specific device, and the platform each works on.
The native backends are complete. Video4Linux on Linux, MSMF on Windows and AVFoundation on macOS all report support for input, device query and per-device capability query. That symmetry is what makes the cross-platform promise hold, and it is why the default native feature bundles all three together under input-native.
OpenCV is the weaker one. It supports reading frames on Linux, Windows and macOS, but it is marked as not supported for both device query and device capability query, and the footnote warns it may be buggy. The same footnote records that it also supports IP cameras, which is its real reason to exist: no other listed backend reaches a network camera.
The web backend, reached through the input-wasm feature in the table and input-jscam in the manifest, is complete on the same three capabilities for the browser, and it works through the JSCamera struct built on browser APIs.
One detail matters when you pick a feature: the default feature includes nothing. Every backend is opt-in through a feature whose name starts with input-, so a crate that forgets to declare one compiles and then has no way to open a device.
Output features decide how frames leave the camera
The output-wgpu feature enables the API to copy a frame directly into a wgpu texture, which is the one that matters for graphics work. Instead of decoding to CPU memory and uploading, the frame path can terminate inside a GPU texture. The dependency snippet on the front page enables it alongside the native backends, which suggests it is a common pairing.
Two more output-side features shape the API. output-threaded enables the threaded and callback based camera, which changes the shape of your code from pulling frames on demand to receiving them on a background thread. output-async, declared in the manifest, pulls the core crate's async support and the async-trait dependency into the build, and input-jscam depends on it.
The manifest also carries a serialize feature that turns on serde for the core crate, so device and format descriptions can be round-tripped through JSON or any other serde format. That is useful when you want to persist a chosen device configuration between runs of a recording tool.
Decoding, output and input are separate axes in the feature system, and the front page makes the practical point that you can pick and choose to reduce bloat. A headless service that only needs JPEG frames on Linux should enable input-native and decoding-mjpeg and nothing else, avoiding the Windows and macOS binding crates entirely.
Where nokhwa is the wrong tool
Several limitations are visible in the project itself rather than in the prose.
The OpenCV backend does not support device enumeration or capability queries. That means an application relying on the high level Camera API cannot enumerate cameras or ask what a device supports when it runs on the OpenCV backend, even though the same application works on the native backends. Behaviour differences between backends are therefore baked into the design, and only the native path and the web path give you the full API.
IP camera support is likewise confined to the OpenCV backend, with the explicit warning that it may be buggy. If network cameras are a requirement rather than a nicety, the OpenCV path is the only one in the table that offers them.
The uvc input feature is commented out in the manifest with a note that it will be re-enabled once soundness has been proven and mozjpeg is updated to 0.9.x. Direct USB camera control is therefore not currently available through that path.
There is also a version policy to think about. The manifest states that nokhwa may build on older versions of rustc but that there is no guarantee except for the latest stable rust, and the crate uses edition 2024, which pulls in a recent toolchain baseline. And the repository default branch is named senpai rather than main or master, which is unusual for a published crate and worth knowing if you browse the source expecting main.
A workspace split by platform, and a licence you can choose between
The repository is a Cargo workspace rather than a single crate. The members are nokhwa-core, nokhwa-decoders, nokhwa-iter-extensions and three platform binding crates: nokhwa-bindings-linux, nokhwa-bindings-macos and nokhwa-bindings-windows. That split is the mechanism behind the feature system. Enabling input-native pulls in the three binding crates, while the manifest's per-platform features input-avfoundation, input-v4l and input-msmf let you enable exactly one of them.
Around that sits ordinary project infrastructure: a CHANGELOG.md, an UPDATE_CHECKLIST.md, a flake.nix and flake.lock for Nix based development, a publish.sh script for releases, and examples with separate subdirectories per use case.
On licensing, the manifest declares license = "MIT OR Apache-2.0", and the repository carries both LICENSE-APACHE and LICENSE-MIT files. That is the standard dual licence pattern, which lets you pick either terms. The README states that contributions are accepted under the Apache 2.0 licence unless otherwise specified, and asks contributors to run rustfmt, respect clippy lints, and limit the use of unsafe.
For maintenance, the repository is not archived, the last push was on 2026-05-15, and the listed releases are 0.10.11 on 2026-05-15, 0.10.10 on 2025-11-10 and 0.10.7 on 2024-12-05. The manifest version has already moved ahead of the newest tag at 0.11.0, which tells you development on the branch and the published crate are not in lockstep.
Editorial conclusion
Adopt nokhwa when you are writing Rust code that needs real pixels from a camera on more than one operating system and want to skip the per-platform FFI work yourself. Do not adopt it when you need audio, when you must support every camera without a device index you can enumerate, or when you want a stable published version, since the crate manifest sits at 0.11.0 while the newest listed release is 0.10.11 and the repository default branch is senpai. Before you start, add nokhwa to your Cargo.toml with the input backend for the platform you target, and confirm the decoding feature you need, because the default feature set enables only decoding-yuv and decoding-mjpeg.
Frequently asked questions
How do I add nokhwa to a Rust project?
Add it as a Cargo dependency with the backend features you need, for example version = "0.10" with features = ["input-native", "output-wgpu"]. The default feature set includes no backend, so a build without an input feature has no way to open a camera.
How do I capture a single frame with nokhwa?
Create a Camera with CameraIndex::Index(0) and a RequestedFormat, then call camera.frame(), read frame.buffer() for the raw bytes, or frame.decode_image::<RgbFormat>() to turn the frame into an image buffer.
Which platforms and backends does nokhwa support?
The native backends are Video4Linux on Linux, MSMF on Windows and AVFoundation on macOS, all grouped under the input-native feature. There is also an OpenCV backend for cross-platform use and IP cameras, which does not support device query or capability query, and a web backend for the browser through the JSCamera struct.
Can nokhwa copy frames into a wgpu texture?
Yes. The output-wgpu feature enables the API to copy a frame directly into a wgpu texture, which avoids a separate decode and upload step for graphics workloads. The output-threaded feature enables a callback based camera instead.
What licence does nokhwa use and how recent is it?
The manifest declares MIT OR Apache-2.0, with both LICENSE-MIT and LICENSE-APACHE in the repository, and contributions are accepted under Apache 2.0 by default. The last push was on 2026-05-15 and the newest listed release is 0.10.11 from that date, while the manifest version reads 0.11.0.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/l1npengtul-nokhwa)Was this useful?