1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
//! A cross-platform camera library for Rust, built with data-oriented design.
//!
//! `cameras` exposes plain data ([`Device`], [`Capabilities`], [`FormatDescriptor`],
//! [`StreamConfig`], [`Frame`]) and free functions that operate on that data. Every
//! public type has public fields. Format negotiation is explicit: you probe, you pick,
//! you open. Errors are typed. Platform dispatch happens at compile time via `cfg` and
//! the associated-type [`Backend`] trait; there are zero trait objects anywhere in the
//! library.
//!
//! # Platform support
//!
//! | Platform | USB / Built-in | RTSP (`rtsp` feature) |
//! |----------|---------------|-----------------------|
//! | macOS | AVFoundation via `objc2` | retina + VideoToolbox (H.264 / H.265 / MJPEG) |
//! | Windows | Media Foundation via `windows` | retina + Media Foundation (H.264 / H.265 / MJPEG) |
//! | Linux | V4L2 via `v4l` | not supported |
//!
//! # Quick Start
//!
//! ```no_run
//! use std::time::Duration;
//!
//! fn main() -> Result<(), cameras::Error> {
//! let devices = cameras::devices()?;
//! let device = devices.first().expect("no cameras");
//!
//! let capabilities = cameras::probe(device)?;
//! println!("{} formats available", capabilities.formats.len());
//!
//! let config = cameras::StreamConfig {
//! resolution: cameras::Resolution { width: 1280, height: 720 },
//! framerate: 30,
//! pixel_format: cameras::PixelFormat::Bgra8,
//! };
//!
//! let camera = cameras::open(device, config)?;
//!
//! for _ in 0..30 {
//! let frame = cameras::next_frame(&camera, Duration::from_secs(2))?;
//! let rgb = cameras::to_rgb8(&frame)?;
//! println!("{}x{} -> {} bytes rgb", frame.width, frame.height, rgb.len());
//! }
//!
//! Ok(())
//! }
//! ```
//!
//! Dropping the [`Camera`] stops the stream. Dropping the [`DeviceMonitor`] joins its
//! polling worker.
//!
//! # Higher-level primitives
//!
//! Two modules layer on top of the [`Camera`] / [`next_frame`] core. They are optional;
//! callers who want full control can stick with the core API.
//!
//! - [`source`]: a [`CameraSource`] enum that unifies USB and RTSP, plus
//! [`open_source`] which dispatches to [`open`] or [`open_rtsp`] automatically.
//! Useful for UIs and config files that want a single "where do frames come from"
//! value type.
//! - [`pump`]: a long-running background worker that pulls frames and hands them to a
//! caller-provided sink closure. Supports [`pump::set_active`] (pause / resume without
//! closing the camera), [`pump::capture_frame`] (single fresh frame on demand, works
//! while paused), and [`pump::stop_and_join`] (deterministic teardown). This is the
//! primitive higher-level integrations (for example, the `dioxus-cameras` hook) are
//! built on.
//! - `analysis` (feature-gated, see [`analysis`]): blur-variance sharpness metrics
//! and a small [`analysis::Ring`] for "take the sharpest frame of the last N"
//! capture flows. Scores are relative; calibrate thresholds per camera.
//!
//! # Design
//!
//! - **Data-oriented**: Types hold data. Functions operate on data. No `impl` blocks with
//! hidden accessors, no trait objects, no inheritance.
//! - **Explicit format negotiation**: [`probe`] returns every format a device supports.
//! You pick one and pass it to [`open`] via [`StreamConfig`]. If you want a fallback,
//! [`best_format`] picks the closest match.
//! - **Push-based delivery**: Each [`Camera`] owns a worker thread and a bounded crossbeam
//! channel. The consumer pulls frames with a timeout via [`next_frame`]. If the consumer
//! falls behind, old frames are dropped, not buffered.
//! - **Typed errors**: See [`Error`].
//! - **Pluggable pixel conversion**: [`to_rgb8`] / [`to_rgba8`] decode from BGRA, RGBA,
//! YUYV, NV12, and MJPEG (via `zune-jpeg`), honoring stride.
//! - **Hotplug**: [`monitor()`] returns a [`DeviceMonitor`] that emits
//! [`DeviceEvent::Added`] / [`DeviceEvent::Removed`] as cameras appear and disappear.
//! - **Unified opening**: [`open_source`] + [`CameraSource`] let you treat USB and RTSP
//! cameras uniformly in higher-level code.
//! - **Background pump with pause + capture**: [`pump::spawn`] runs the frame loop off
//! the calling thread, with [`pump::set_active`] for pause / resume and
//! [`pump::capture_frame`] for single-shot snapshots while paused.
//! - **Compile-time backend contract**: Platform backends are selected with `cfg`. Each is
//! a `Driver` struct that implements [`Backend`]. No `Box<dyn Backend>`; the compiler
//! verifies every platform implements the same surface.
pub type ActiveBackend = crateDriver;
pub type ActiveBackend = crateDriver;
pub type ActiveBackend = crateDriver;
pub type ActiveBackend = crateDriver;
pub use Backend;
pub use BackendControls;
pub use ;
pub use ;
pub use Error;
pub use ;
pub use ;
pub use Rect;
pub use ;
pub use ;
pub use open_rtsp;
use Duration;
/// Enumerate every video capture device the platform currently sees.
///
/// On macOS this triggers the system camera permission prompt on first call
/// if it hasn't been granted. On Linux this reads `/dev/video*`. On Windows
/// this queries Media Foundation via `MFEnumDeviceSources`.
/// Inspect a device's full set of supported formats without opening a stream.
///
/// Returns every native `(resolution, framerate_range, pixel_format)` tuple the
/// device reports. On macOS and Linux this is cheap metadata; on Windows it
/// instantiates a source reader briefly.
/// Open a camera with the given configuration and start streaming.
///
/// The returned [`Camera`] owns a worker thread that pushes frames into a
/// bounded crossbeam channel. Read them with [`next_frame`] or [`try_next_frame`].
/// Dropping the [`Camera`] stops the stream.
/// Start a hotplug monitor.
///
/// Returns a [`DeviceMonitor`] that emits [`DeviceEvent::Added`] / [`DeviceEvent::Removed`]
/// as cameras appear and disappear. Initial events are emitted for every device already
/// present when the monitor starts. Dropping the monitor joins its polling worker.
/// Pick the closest supported format to a requested `StreamConfig`.
///
/// Tries, in order:
/// 1. An exact match on `(pixel_format, resolution, framerate)`.
/// 2. Any format at the requested resolution.
/// 3. The format whose resolution has the smallest total width + height delta
/// from the request.
///
/// Returns `None` only if `capabilities.formats` is empty.
/// A reasonable default timeout for [`next_frame`] when you don't want to hand-pick one.
pub const DEFAULT_FRAME_TIMEOUT: Duration = from_millis;
/// Report which runtime controls the given device exposes and their native ranges.
///
/// Fields on the returned [`ControlCapabilities`] are `None` for controls the
/// platform / device does not expose. Ranges are in each platform's native
/// unit — do not assume a normalized scale.
/// Read the current value of every exposed control on `device`.
///
/// Fields are `None` for controls the device does not expose. Read-back of
/// `auto_exposure` collapses V4L2 priority modes into `Some(true)`.
/// Apply every [`Some`]-valued field in `controls` to `device`.
///
/// `None` fields are left at their current value. Returns the first platform
/// failure encountered; does not preflight against [`control_capabilities`].