ff_decode/lib.rs
1//! # ff-decode
2//!
3//! Video and audio decoding - the Rust way.
4//!
5//! This crate provides frame-by-frame video/audio decoding, efficient seeking,
6//! and thumbnail generation. It completely hides `FFmpeg` internals and provides
7//! a safe, ergonomic Rust API.
8//!
9//! ## Features
10//!
11//! - **Video Decoding**: Frame-by-frame decoding with Iterator pattern
12//! - **Audio Decoding**: Sample-level audio extraction
13//! - **Seeking**: Fast keyframe and exact seeking without file re-open
14//! - **Thumbnails**: Efficient thumbnail generation for timelines
15//! - **Hardware Acceleration**: Optional NVDEC, QSV, AMF, `VideoToolbox`, VAAPI support
16//! - **Frame Pooling**: Memory reuse for reduced allocation overhead
17//!
18//! ## Usage
19//!
20//! ### Video Decoding
21//!
22//! ```ignore
23//! use ff_decode::{VideoDecoder, SeekMode};
24//! use ff_format::PixelFormat;
25//! use std::time::Duration;
26//!
27//! // Open a video file and create decoder
28//! let mut decoder = VideoDecoder::open("video.mp4")?
29//! .output_format(PixelFormat::Rgba)
30//! .build()?;
31//!
32//! // Get basic info
33//! println!("Duration: {:?}", decoder.duration());
34//! println!("Resolution: {}x{}", decoder.width(), decoder.height());
35//!
36//! // Decode frames sequentially
37//! for result in &mut decoder {
38//! let frame = result?;
39//! println!("Frame at {:?}", frame.timestamp().as_duration());
40//! }
41//!
42//! // Seek to specific position
43//! decoder.seek(Duration::from_secs(30), SeekMode::Keyframe)?;
44//! ```
45//!
46//! ### Audio Decoding
47//!
48//! ```ignore
49//! use ff_decode::AudioDecoder;
50//! use ff_format::SampleFormat;
51//!
52//! let mut decoder = AudioDecoder::open("audio.mp3")?
53//! .output_format(SampleFormat::F32)
54//! .output_sample_rate(48000)
55//! .build()?;
56//!
57//! // Decode all audio samples
58//! for result in &mut decoder {
59//! let frame = result?;
60//! println!("Audio frame with {} samples", frame.samples());
61//! }
62//! ```
63//!
64//! ### Hardware Acceleration (Video)
65//!
66//! ```ignore
67//! use ff_decode::{VideoDecoder, HardwareAccel};
68//!
69//! let decoder = VideoDecoder::open("video.mp4")?
70//! .hardware_accel(HardwareAccel::Auto) // Auto-detect GPU
71//! .build()?;
72//! ```
73//!
74//! ### Frame Pooling (Video)
75//!
76//! ```ignore
77//! use ff_decode::{VideoDecoder, FramePool};
78//! use std::sync::Arc;
79//!
80//! // Use a frame pool for memory reuse
81//! let pool: Arc<dyn FramePool> = create_frame_pool(32);
82//! let decoder = VideoDecoder::open("video.mp4")?
83//! .frame_pool(pool)
84//! .build()?;
85//! ```
86//!
87//! ## Module Structure
88//!
89//! - [`audio`] - Audio decoder for extracting audio frames
90//! - [`video`] - Video decoder for extracting video frames
91//! - [`error`] - Error types for decoding operations
92//! - Frame pool types (`FramePool`, `PooledBuffer`, `VecPool`) are provided by `ff-common`
93//!
94//! ## Re-exports
95//!
96//! This crate re-exports commonly used types from `ff-format` for convenience.
97
98#![warn(missing_docs)]
99#![warn(clippy::all)]
100#![warn(clippy::pedantic)]
101
102// Module declarations
103pub mod audio;
104pub mod error;
105pub mod image;
106pub(crate) mod network;
107pub mod video;
108
109// Re-exports for convenience
110pub use audio::{AudioDecoder, AudioDecoderBuilder};
111pub use error::DecodeError;
112pub use ff_common::{FramePool, PooledBuffer};
113pub use ff_format::ContainerInfo;
114pub use image::{ImageDecoder, ImageDecoderBuilder};
115pub use video::{VideoDecoder, VideoDecoderBuilder};
116
117#[cfg(feature = "tokio")]
118pub use audio::AsyncAudioDecoder;
119#[cfg(feature = "tokio")]
120pub use image::AsyncImageDecoder;
121#[cfg(feature = "tokio")]
122pub use video::AsyncVideoDecoder;
123
124/// Seek mode for positioning the decoder.
125///
126/// This enum determines how seeking is performed when navigating
127/// through a media file.
128///
129/// # Performance Considerations
130///
131/// - [`Keyframe`](Self::Keyframe) is fastest but may land slightly before or after the target
132/// - [`Exact`](Self::Exact) is slower but guarantees landing on the exact frame
133/// - [`Backward`](Self::Backward) is useful for editing workflows where the previous keyframe is needed
134///
135/// # Examples
136///
137/// ```
138/// use ff_decode::SeekMode;
139///
140/// // Default is Keyframe mode
141/// let mode = SeekMode::default();
142/// assert_eq!(mode, SeekMode::Keyframe);
143///
144/// // Use exact mode for frame-accurate positioning
145/// let exact = SeekMode::Exact;
146/// assert_eq!(format!("{:?}", exact), "Exact");
147/// ```
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
149#[repr(u8)]
150pub enum SeekMode {
151 /// Seek to nearest keyframe (fast, may have small offset).
152 ///
153 /// This mode seeks to the closest keyframe to the target position.
154 /// It's the fastest option but the actual position may differ from
155 /// the requested position by up to the GOP (Group of Pictures) size.
156 #[default]
157 Keyframe = 0,
158
159 /// Seek to exact frame (slower but precise).
160 ///
161 /// This mode first seeks to the previous keyframe, then decodes
162 /// frames until reaching the exact target position. This guarantees
163 /// frame-accurate positioning but is slower, especially for long GOPs.
164 Exact = 1,
165
166 /// Seek to keyframe at or before the target position.
167 ///
168 /// Similar to [`Keyframe`](Self::Keyframe), but guarantees the resulting
169 /// position is at or before the target. Useful for editing workflows
170 /// where you need to start decoding before a specific point.
171 Backward = 2,
172}
173
174/// Hardware acceleration configuration.
175///
176/// This enum specifies which hardware acceleration method to use for
177/// video decoding. Hardware acceleration can significantly improve
178/// decoding performance, especially for high-resolution content.
179///
180/// # Platform Support
181///
182/// | Mode | Platform | GPU Required |
183/// |------|----------|--------------|
184/// | [`Nvdec`](Self::Nvdec) | Windows/Linux | NVIDIA |
185/// | [`Qsv`](Self::Qsv) | Windows/Linux | Intel |
186/// | [`Amf`](Self::Amf) | Windows/Linux | AMD |
187/// | [`VideoToolbox`](Self::VideoToolbox) | macOS/iOS | Any |
188/// | [`Vaapi`](Self::Vaapi) | Linux | Various |
189///
190/// # Fallback Behavior
191///
192/// When [`Auto`](Self::Auto) is used, the decoder will try available
193/// accelerators in order of preference and fall back to software
194/// decoding if none are available.
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
196pub enum HardwareAccel {
197 /// Automatically detect and use available hardware.
198 ///
199 /// The decoder will probe for available hardware accelerators
200 /// and use the best one available. Falls back to software decoding
201 /// if no hardware acceleration is available.
202 #[default]
203 Auto,
204
205 /// Disable hardware acceleration (CPU only).
206 ///
207 /// Forces software decoding using the CPU. This may be useful for
208 /// debugging, consistency, or when hardware acceleration causes issues.
209 None,
210
211 /// NVIDIA NVDEC.
212 ///
213 /// Uses NVIDIA's dedicated video decoding hardware. Supports most
214 /// common codecs including H.264, H.265, VP9, and AV1 (on newer GPUs).
215 /// Requires an NVIDIA GPU with NVDEC support.
216 Nvdec,
217
218 /// Intel Quick Sync Video.
219 ///
220 /// Uses Intel's integrated GPU video engine. Available on most
221 /// Intel CPUs with integrated graphics. Supports H.264, H.265,
222 /// VP9, and AV1 (on newer platforms).
223 Qsv,
224
225 /// AMD Advanced Media Framework.
226 ///
227 /// Uses AMD's dedicated video decoding hardware. Available on AMD
228 /// GPUs and APUs. Supports H.264, H.265, and VP9.
229 Amf,
230
231 /// Apple `VideoToolbox`.
232 ///
233 /// Uses Apple's hardware video decoding on macOS and iOS. Works with
234 /// both Intel and Apple Silicon Macs. Supports H.264, H.265, and `ProRes`.
235 VideoToolbox,
236
237 /// Video Acceleration API (Linux).
238 ///
239 /// A Linux-specific API that provides hardware-accelerated video
240 /// decoding across different GPU vendors. Widely supported on
241 /// Intel, AMD, and NVIDIA GPUs on Linux.
242 Vaapi,
243}
244
245impl HardwareAccel {
246 /// Returns `true` if this represents an enabled hardware accelerator.
247 ///
248 /// Returns `false` for [`None`](Self::None) and [`Auto`](Self::Auto).
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use ff_decode::HardwareAccel;
254 ///
255 /// assert!(!HardwareAccel::Auto.is_specific());
256 /// assert!(!HardwareAccel::None.is_specific());
257 /// assert!(HardwareAccel::Nvdec.is_specific());
258 /// assert!(HardwareAccel::Qsv.is_specific());
259 /// ```
260 #[must_use]
261 pub const fn is_specific(&self) -> bool {
262 !matches!(self, Self::Auto | Self::None)
263 }
264
265 /// Returns the name of the hardware accelerator.
266 ///
267 /// # Examples
268 ///
269 /// ```
270 /// use ff_decode::HardwareAccel;
271 ///
272 /// assert_eq!(HardwareAccel::Auto.name(), "auto");
273 /// assert_eq!(HardwareAccel::Nvdec.name(), "nvdec");
274 /// assert_eq!(HardwareAccel::VideoToolbox.name(), "videotoolbox");
275 /// ```
276 #[must_use]
277 pub const fn name(&self) -> &'static str {
278 match self {
279 Self::Auto => "auto",
280 Self::None => "none",
281 Self::Nvdec => "nvdec",
282 Self::Qsv => "qsv",
283 Self::Amf => "amf",
284 Self::VideoToolbox => "videotoolbox",
285 Self::Vaapi => "vaapi",
286 }
287 }
288}
289
290/// Prelude module for convenient imports.
291///
292/// This module re-exports all commonly used types:
293///
294/// ```ignore
295/// use ff_decode::prelude::*;
296/// ```
297pub mod prelude {
298 #[cfg(feature = "tokio")]
299 pub use crate::{AsyncAudioDecoder, AsyncImageDecoder, AsyncVideoDecoder};
300 pub use crate::{
301 AudioDecoder, AudioDecoderBuilder, DecodeError, FramePool, HardwareAccel, ImageDecoder,
302 ImageDecoderBuilder, PooledBuffer, SeekMode, VideoDecoder, VideoDecoderBuilder,
303 };
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309
310 #[test]
311 fn test_seek_mode_default() {
312 let mode = SeekMode::default();
313 assert_eq!(mode, SeekMode::Keyframe);
314 }
315
316 #[test]
317 fn test_hardware_accel_default() {
318 let accel = HardwareAccel::default();
319 assert_eq!(accel, HardwareAccel::Auto);
320 }
321
322 #[test]
323 fn test_hardware_accel_is_specific() {
324 assert!(!HardwareAccel::Auto.is_specific());
325 assert!(!HardwareAccel::None.is_specific());
326 assert!(HardwareAccel::Nvdec.is_specific());
327 assert!(HardwareAccel::Qsv.is_specific());
328 assert!(HardwareAccel::Amf.is_specific());
329 assert!(HardwareAccel::VideoToolbox.is_specific());
330 assert!(HardwareAccel::Vaapi.is_specific());
331 }
332
333 #[test]
334 fn test_hardware_accel_name() {
335 assert_eq!(HardwareAccel::Auto.name(), "auto");
336 assert_eq!(HardwareAccel::None.name(), "none");
337 assert_eq!(HardwareAccel::Nvdec.name(), "nvdec");
338 assert_eq!(HardwareAccel::Qsv.name(), "qsv");
339 assert_eq!(HardwareAccel::Amf.name(), "amf");
340 assert_eq!(HardwareAccel::VideoToolbox.name(), "videotoolbox");
341 assert_eq!(HardwareAccel::Vaapi.name(), "vaapi");
342 }
343
344 #[test]
345 fn test_seek_mode_variants() {
346 let modes = [SeekMode::Keyframe, SeekMode::Exact, SeekMode::Backward];
347 for mode in modes {
348 // Ensure all variants are accessible
349 let _ = format!("{mode:?}");
350 }
351 }
352
353 #[test]
354 fn test_hardware_accel_variants() {
355 let accels = [
356 HardwareAccel::Auto,
357 HardwareAccel::None,
358 HardwareAccel::Nvdec,
359 HardwareAccel::Qsv,
360 HardwareAccel::Amf,
361 HardwareAccel::VideoToolbox,
362 HardwareAccel::Vaapi,
363 ];
364 for accel in accels {
365 // Ensure all variants are accessible
366 let _ = format!("{accel:?}");
367 }
368 }
369
370 #[test]
371 fn test_decode_error_display() {
372 use std::path::PathBuf;
373
374 let error = DecodeError::FileNotFound {
375 path: PathBuf::from("/path/to/video.mp4"),
376 };
377 assert!(error.to_string().contains("File not found"));
378
379 let error = DecodeError::NoVideoStream {
380 path: PathBuf::from("/path/to/audio.mp3"),
381 };
382 assert!(error.to_string().contains("No video stream"));
383
384 let error = DecodeError::UnsupportedCodec {
385 codec: "unknown_codec".to_string(),
386 };
387 assert!(error.to_string().contains("Codec not supported"));
388 }
389
390 #[test]
391 fn test_prelude_imports() {
392 // Verify prelude exports all expected types
393 use crate::prelude::*;
394
395 let _mode: SeekMode = SeekMode::default();
396 let _accel: HardwareAccel = HardwareAccel::default();
397
398 // Video builder can be created
399 let _video_builder: VideoDecoderBuilder = VideoDecoder::open("test.mp4");
400
401 // Audio builder can be created
402 let _audio_builder: AudioDecoderBuilder = AudioDecoder::open("test.mp3");
403 }
404}