moq_video/lib.rs
1//! Native video capture, encoding, and publishing for Media over QUIC.
2//!
3//! Counterpart to [`moq-audio`](https://crates.io/crates/moq-audio) for video
4//! tracks, and shaped the same way: both split into `capture` / `encode` /
5//! `decode` role modules over a shared root [`Error`], with `render` as video's
6//! fourth. Sits on top of [`moq_mux`] (and the `hang` catalog) and adds the
7//! native pieces a desktop/CLI publisher needs.
8//!
9//! A raw picture is a [`Frame`] wherever it crosses the API: a timestamp and a
10//! [`Surface`] holding the pixels. Capture and [`decode`] produce them, [`encode`]
11//! consumes them and hands back the compressed [`encode::Encoded`], and [`Size`]
12//! names a resolution. Keyframes are the encoder's business: it inserts them per
13//! [`encode::Config::gop`], and [`encode::Encoder::keyframe`] is there for the
14//! rarer case where a caller needs one at a specific frame.
15//!
16//! - `capture` describes a frame source and grabs frames per platform:
17//! AVFoundation/ScreenCaptureKit on macOS, native V4L2 on Linux, native Media
18//! Foundation (camera), DXGI Desktop Duplication (screen), and GDI (window) on
19//! Windows, plus portal/PipeWire on Wayland and X11 capture on Linux. Use
20//! [`capture::open`] for an embeddable raw-frame stream or
21//! [`encode::publish_capture`] for turnkey publication. It requires the
22//! default-on `capture` feature.
23//! - [`encode`] encodes frames with a native backend and publishes them through
24//! the matching `moq_mux::codec` importer, which handles catalog registration
25//! and framing. The codec is chosen via [`encode::Codec`]: H.264 (openh264 /
26//! VideoToolbox / Media Foundation / NVENC / VAAPI) or H.265 (VideoToolbox /
27//! Media Foundation / NVENC). Two entry points:
28//! - `encode::publish_capture` captures a webcam and publishes it (turnkey).
29//! It encodes strictly on demand: the track and catalog are advertised up
30//! front (the camera opens once at startup so they can be exact), and the
31//! encoder runs only while a subscriber is watching. Requires the `capture`
32//! feature.
33//! - [`encode::Encoder`] encodes [`Frame`]s you supply (from capture, a
34//! decoder, or your own pixels via [`Surface::rgba`]) and
35//! [`encode::Producer`] publishes the results.
36//! - [`decode`] subscribes to an H.264, H.265, or AV1 track and decodes it to
37//! raw frames with a native backend (VideoToolbox on macOS, Media Foundation /
38//! DXVA on Windows, NVDEC on Linux, openh264 software fallback for H.264).
39//! [`decode::Consumer`] is the mirror of `moq_audio::decode::Consumer`. An
40//! NVDEC frame stays in CUDA memory and feeds [`encode::Encoder::encode`]
41//! zero-copy (the transcode path), scaled in hardware via
42//! [`decode::Config::resize`].
43//! - [`convert`] downloads any [`Surface`] to owned, tightly packed RGBA pixels
44//! for CPU image and UI toolkits, honoring native color metadata when present.
45//! - `render` draws a [`Frame`] on the GPU and hands back a `wgpu` texture to
46//! present, importing a GPU frame's surface directly where the platform
47//! allows and uploading I420 otherwise. Behind the non-default `render`
48//! feature, since it pulls in a graphics stack a publisher or relay does not
49//! need.
50//!
51//! ## API stability
52//!
53//! The public API is codec-agnostic: no public type, signature, or error
54//! variant names a backend (openh264 / VideoToolbox / NVENC / NVDEC) or a codec
55//! implementation. [`encode::Encoder`] takes a [`Frame`],
56//! [`decode::Consumer`] returns one (CPU I420 on demand, GPU-resident when
57//! hardware decoded), and [`capture::Stream`] returns a [`Surface`]. So swapping
58//! or bumping any backend crate is not a breaking change for consumers. Config
59//! structs are `#[non_exhaustive]`: build them via `default()`/`new()` and set
60//! fields, so new options stay additive.
61//!
62//! The one deliberate exception is [`Surface`], the enum behind every frame.
63//! Its variants name platform representations (`CVPixelBuffer`, Direct3D11,
64//! CUDA) so you can render or re-encode a frame yourself without a CPU round
65//! trip, which means a major bump of one of those platform crates is a breaking
66//! change here. It is `#[non_exhaustive]` and every variant has a universal
67//! fallback in [`Surface::into_i420`], so matching on it stays portable: take the
68//! fast path you recognize and let the `_` arm handle the rest.
69
70#[cfg(feature = "capture")]
71pub mod capture;
72pub mod convert;
73pub mod decode;
74pub mod encode;
75#[cfg(feature = "render")]
76pub mod render;
77pub mod resize;
78
79mod color;
80mod error;
81pub mod frame;
82mod size;
83// Only the threaded sinks use this, and both are compiled out on macOS, where
84// the codecs run inline (no COM apartment to confine). Ungated it is dead code
85// there, which `-D warnings` rejects.
86#[cfg(not(target_os = "macos"))]
87mod worker;
88
89#[cfg(target_os = "windows")]
90mod mf;
91
92pub use color::Color;
93pub use error::Error;
94#[cfg(all(target_os = "linux", feature = "dmabuf"))]
95pub use frame::{DmaBuf, DmaBufExport, DmaBufPlane, DrmFormat};
96pub use frame::{Frame, I420, Surface};
97pub use size::Size;
98
99/// The CoreFoundation bindings owning the handle [`Surface::into_pixel_buffer`]
100/// returns, re-exported alongside [`objc2_core_video`] for the same reason.
101#[cfg(target_os = "macos")]
102pub use objc2_core_foundation;
103/// The CoreVideo bindings [`Surface::into_pixel_buffer`] hands back,
104/// re-exported so you name the exact version this crate links rather than guessing
105/// at a matching one. A major bump here is a breaking change for this crate.
106#[cfg(target_os = "macos")]
107pub use objc2_core_video;
108/// The Direct3D11 bindings [`frame::d3d11::Texture`] hands back, re-exported for
109/// the same reason as the Apple ones above: name the exact version this crate
110/// links rather than guessing at a matching one, since a device and a texture
111/// from a different `windows` build are different types. A major bump here is a
112/// breaking change for this crate.
113#[cfg(target_os = "windows")]
114pub use windows;