Skip to main content

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 frame in decoder.frames().take(100) {
38//!     let frame = frame?;
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 frame in decoder.frames().take(100) {
59//!     let frame = frame?;
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 mod video;
107
108// Re-exports for convenience
109pub use audio::{AudioDecoder, AudioDecoderBuilder};
110pub use error::DecodeError;
111pub use ff_common::{FramePool, PooledBuffer};
112pub use image::{ImageDecoder, ImageDecoderBuilder};
113pub use video::{VideoDecoder, VideoDecoderBuilder};
114
115#[cfg(feature = "tokio")]
116pub use audio::AsyncAudioDecoder;
117#[cfg(feature = "tokio")]
118pub use image::AsyncImageDecoder;
119#[cfg(feature = "tokio")]
120pub use video::AsyncVideoDecoder;
121
122/// Seek mode for positioning the decoder.
123///
124/// This enum determines how seeking is performed when navigating
125/// through a media file.
126///
127/// # Performance Considerations
128///
129/// - [`Keyframe`](Self::Keyframe) is fastest but may land slightly before or after the target
130/// - [`Exact`](Self::Exact) is slower but guarantees landing on the exact frame
131/// - [`Backward`](Self::Backward) is useful for editing workflows where the previous keyframe is needed
132///
133/// # Examples
134///
135/// ```
136/// use ff_decode::SeekMode;
137///
138/// // Default is Keyframe mode
139/// let mode = SeekMode::default();
140/// assert_eq!(mode, SeekMode::Keyframe);
141///
142/// // Use exact mode for frame-accurate positioning
143/// let exact = SeekMode::Exact;
144/// assert_eq!(format!("{:?}", exact), "Exact");
145/// ```
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
147#[repr(u8)]
148pub enum SeekMode {
149    /// Seek to nearest keyframe (fast, may have small offset).
150    ///
151    /// This mode seeks to the closest keyframe to the target position.
152    /// It's the fastest option but the actual position may differ from
153    /// the requested position by up to the GOP (Group of Pictures) size.
154    #[default]
155    Keyframe = 0,
156
157    /// Seek to exact frame (slower but precise).
158    ///
159    /// This mode first seeks to the previous keyframe, then decodes
160    /// frames until reaching the exact target position. This guarantees
161    /// frame-accurate positioning but is slower, especially for long GOPs.
162    Exact = 1,
163
164    /// Seek to keyframe at or before the target position.
165    ///
166    /// Similar to [`Keyframe`](Self::Keyframe), but guarantees the resulting
167    /// position is at or before the target. Useful for editing workflows
168    /// where you need to start decoding before a specific point.
169    Backward = 2,
170}
171
172/// Hardware acceleration configuration.
173///
174/// This enum specifies which hardware acceleration method to use for
175/// video decoding. Hardware acceleration can significantly improve
176/// decoding performance, especially for high-resolution content.
177///
178/// # Platform Support
179///
180/// | Mode | Platform | GPU Required |
181/// |------|----------|--------------|
182/// | [`Nvdec`](Self::Nvdec) | Windows/Linux | NVIDIA |
183/// | [`Qsv`](Self::Qsv) | Windows/Linux | Intel |
184/// | [`Amf`](Self::Amf) | Windows/Linux | AMD |
185/// | [`VideoToolbox`](Self::VideoToolbox) | macOS/iOS | Any |
186/// | [`Vaapi`](Self::Vaapi) | Linux | Various |
187///
188/// # Fallback Behavior
189///
190/// When [`Auto`](Self::Auto) is used, the decoder will try available
191/// accelerators in order of preference and fall back to software
192/// decoding if none are available.
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
194pub enum HardwareAccel {
195    /// Automatically detect and use available hardware.
196    ///
197    /// The decoder will probe for available hardware accelerators
198    /// and use the best one available. Falls back to software decoding
199    /// if no hardware acceleration is available.
200    #[default]
201    Auto,
202
203    /// Disable hardware acceleration (CPU only).
204    ///
205    /// Forces software decoding using the CPU. This may be useful for
206    /// debugging, consistency, or when hardware acceleration causes issues.
207    None,
208
209    /// NVIDIA NVDEC.
210    ///
211    /// Uses NVIDIA's dedicated video decoding hardware. Supports most
212    /// common codecs including H.264, H.265, VP9, and AV1 (on newer GPUs).
213    /// Requires an NVIDIA GPU with NVDEC support.
214    Nvdec,
215
216    /// Intel Quick Sync Video.
217    ///
218    /// Uses Intel's integrated GPU video engine. Available on most
219    /// Intel CPUs with integrated graphics. Supports H.264, H.265,
220    /// VP9, and AV1 (on newer platforms).
221    Qsv,
222
223    /// AMD Advanced Media Framework.
224    ///
225    /// Uses AMD's dedicated video decoding hardware. Available on AMD
226    /// GPUs and APUs. Supports H.264, H.265, and VP9.
227    Amf,
228
229    /// Apple `VideoToolbox`.
230    ///
231    /// Uses Apple's hardware video decoding on macOS and iOS. Works with
232    /// both Intel and Apple Silicon Macs. Supports H.264, H.265, and `ProRes`.
233    VideoToolbox,
234
235    /// Video Acceleration API (Linux).
236    ///
237    /// A Linux-specific API that provides hardware-accelerated video
238    /// decoding across different GPU vendors. Widely supported on
239    /// Intel, AMD, and NVIDIA GPUs on Linux.
240    Vaapi,
241}
242
243impl HardwareAccel {
244    /// Returns `true` if this represents an enabled hardware accelerator.
245    ///
246    /// Returns `false` for [`None`](Self::None) and [`Auto`](Self::Auto).
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// use ff_decode::HardwareAccel;
252    ///
253    /// assert!(!HardwareAccel::Auto.is_specific());
254    /// assert!(!HardwareAccel::None.is_specific());
255    /// assert!(HardwareAccel::Nvdec.is_specific());
256    /// assert!(HardwareAccel::Qsv.is_specific());
257    /// ```
258    #[must_use]
259    pub const fn is_specific(&self) -> bool {
260        !matches!(self, Self::Auto | Self::None)
261    }
262
263    /// Returns the name of the hardware accelerator.
264    ///
265    /// # Examples
266    ///
267    /// ```
268    /// use ff_decode::HardwareAccel;
269    ///
270    /// assert_eq!(HardwareAccel::Auto.name(), "auto");
271    /// assert_eq!(HardwareAccel::Nvdec.name(), "nvdec");
272    /// assert_eq!(HardwareAccel::VideoToolbox.name(), "videotoolbox");
273    /// ```
274    #[must_use]
275    pub const fn name(&self) -> &'static str {
276        match self {
277            Self::Auto => "auto",
278            Self::None => "none",
279            Self::Nvdec => "nvdec",
280            Self::Qsv => "qsv",
281            Self::Amf => "amf",
282            Self::VideoToolbox => "videotoolbox",
283            Self::Vaapi => "vaapi",
284        }
285    }
286}
287
288/// Prelude module for convenient imports.
289///
290/// This module re-exports all commonly used types:
291///
292/// ```ignore
293/// use ff_decode::prelude::*;
294/// ```
295pub mod prelude {
296    #[cfg(feature = "tokio")]
297    pub use crate::{AsyncAudioDecoder, AsyncImageDecoder, AsyncVideoDecoder};
298    pub use crate::{
299        AudioDecoder, AudioDecoderBuilder, DecodeError, FramePool, HardwareAccel, ImageDecoder,
300        ImageDecoderBuilder, PooledBuffer, SeekMode, VideoDecoder, VideoDecoderBuilder,
301    };
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_seek_mode_default() {
310        let mode = SeekMode::default();
311        assert_eq!(mode, SeekMode::Keyframe);
312    }
313
314    #[test]
315    fn test_hardware_accel_default() {
316        let accel = HardwareAccel::default();
317        assert_eq!(accel, HardwareAccel::Auto);
318    }
319
320    #[test]
321    fn test_hardware_accel_is_specific() {
322        assert!(!HardwareAccel::Auto.is_specific());
323        assert!(!HardwareAccel::None.is_specific());
324        assert!(HardwareAccel::Nvdec.is_specific());
325        assert!(HardwareAccel::Qsv.is_specific());
326        assert!(HardwareAccel::Amf.is_specific());
327        assert!(HardwareAccel::VideoToolbox.is_specific());
328        assert!(HardwareAccel::Vaapi.is_specific());
329    }
330
331    #[test]
332    fn test_hardware_accel_name() {
333        assert_eq!(HardwareAccel::Auto.name(), "auto");
334        assert_eq!(HardwareAccel::None.name(), "none");
335        assert_eq!(HardwareAccel::Nvdec.name(), "nvdec");
336        assert_eq!(HardwareAccel::Qsv.name(), "qsv");
337        assert_eq!(HardwareAccel::Amf.name(), "amf");
338        assert_eq!(HardwareAccel::VideoToolbox.name(), "videotoolbox");
339        assert_eq!(HardwareAccel::Vaapi.name(), "vaapi");
340    }
341
342    #[test]
343    fn test_seek_mode_variants() {
344        let modes = [SeekMode::Keyframe, SeekMode::Exact, SeekMode::Backward];
345        for mode in modes {
346            // Ensure all variants are accessible
347            let _ = format!("{mode:?}");
348        }
349    }
350
351    #[test]
352    fn test_hardware_accel_variants() {
353        let accels = [
354            HardwareAccel::Auto,
355            HardwareAccel::None,
356            HardwareAccel::Nvdec,
357            HardwareAccel::Qsv,
358            HardwareAccel::Amf,
359            HardwareAccel::VideoToolbox,
360            HardwareAccel::Vaapi,
361        ];
362        for accel in accels {
363            // Ensure all variants are accessible
364            let _ = format!("{accel:?}");
365        }
366    }
367
368    #[test]
369    fn test_decode_error_display() {
370        use std::path::PathBuf;
371
372        let error = DecodeError::FileNotFound {
373            path: PathBuf::from("/path/to/video.mp4"),
374        };
375        assert!(error.to_string().contains("File not found"));
376
377        let error = DecodeError::NoVideoStream {
378            path: PathBuf::from("/path/to/audio.mp3"),
379        };
380        assert!(error.to_string().contains("No video stream"));
381
382        let error = DecodeError::UnsupportedCodec {
383            codec: "unknown_codec".to_string(),
384        };
385        assert!(error.to_string().contains("Codec not supported"));
386
387        let error = DecodeError::EndOfStream;
388        assert_eq!(error.to_string(), "End of stream");
389    }
390
391    #[test]
392    fn test_prelude_imports() {
393        // Verify prelude exports all expected types
394        use crate::prelude::*;
395
396        let _mode: SeekMode = SeekMode::default();
397        let _accel: HardwareAccel = HardwareAccel::default();
398
399        // Video builder can be created
400        let _video_builder: VideoDecoderBuilder = VideoDecoder::open("test.mp4");
401
402        // Audio builder can be created
403        let _audio_builder: AudioDecoderBuilder = AudioDecoder::open("test.mp3");
404    }
405}