vpx_rs/
dec.rs

1/*
2 * Copyright (c) 2025, Saso Kiselkov. All rights reserved.
3 *
4 * Use of this source code is governed by the 2-clause BSD license,
5 * which can be found in a file named LICENSE, located at the root
6 * of this source tree.
7 */
8
9use bitflags::bitflags;
10use std::mem::MaybeUninit;
11use std::ptr::NonNull;
12
13use crate::common::StreamInfo;
14use crate::image::YUVImageData;
15use crate::NoDebugWrapper;
16use crate::{call_vpx, call_vpx_ptr};
17use crate::{Error, Result};
18
19#[allow(missing_docs)]
20/// Specifies the codec to be used for decoding.
21#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub enum CodecId {
23    VP8,
24    VP9,
25}
26
27impl CodecId {
28    pub(crate) fn iface(&self) -> Result<*const vpx_sys::vpx_codec_iface_t> {
29        match self {
30            Self::VP8 => call_vpx_ptr!(
31                vpx_sys::vpx_codec_vp8_dx(),
32                Error::CodecInterfaceNotAvailable,
33            ),
34            Self::VP9 => call_vpx_ptr!(
35                vpx_sys::vpx_codec_vp9_dx(),
36                Error::CodecInterfaceNotAvailable,
37            ),
38        }
39    }
40}
41
42/// The main VP8/VP9 video decoder.
43///
44/// # Example
45/// ```
46/// use std::num::NonZero;
47/// use vpx_rs::{Decoder, DecoderConfig, Error, DecodedImageData};
48///
49/// // Initialize a new decoder
50/// fn new_vp8_decoder(width: u32, height: u32,) -> Result<Decoder, Error> {
51///     // Prepare the configuration
52///     let config = DecoderConfig::new(
53///         vpx_rs::dec::CodecId::VP8,
54///         width,
55///         height,
56///     );
57///     // Instantiate the decoder
58///     Decoder::new(config)
59/// }
60///
61/// // Passes a buffer to the decoder and extracts any completed frames
62/// fn decode_vp8_buf(
63///     decoder: &mut Decoder,
64///     stream_data: &[u8],
65/// ) -> Result<(), Error> {
66///     for decoded_image in decoder.decode(stream_data)?.into_iter() {
67///         let (format, bit_depth) = match decoded_image.data() {
68///             DecodedImageData::Data8b(data) =>
69///                 (format!("{:?}", data.format()), 8),
70///             DecodedImageData::Data16b(data) =>
71///                 (format!("{:?}", data.format()), 16),
72///         };
73///         println!(
74///             "Decoded a {}x{} pixel image in {} format with bit depth {}",
75///             decoded_image.width(),
76///             decoded_image.height(),
77///             format,
78///             bit_depth,
79///         );
80///     }
81///     Ok(())
82/// }
83/// ```
84#[derive(Debug)]
85pub struct Decoder {
86    ctx: NoDebugWrapper<vpx_sys::vpx_codec_ctx_t>,
87}
88
89// We are Send-safe, because we don't hold onto any mutexes between calls
90// and no functions are async. Also, we have no interior mutability through
91// immutable references.
92unsafe impl Send for Decoder {}
93unsafe impl Sync for Decoder {}
94
95impl Decoder {
96    /// Constructs a new decoder for the given configuration. After
97    /// successful construction, you can start sending bitstream buffers
98    /// to the decoder by calling [`Decoder::decode()`].
99    pub fn new(config: DecoderConfig) -> Result<Self> {
100        let mut ctx = unsafe { MaybeUninit::zeroed().assume_init() };
101        let (iface, cfg) = config.cfg()?;
102        call_vpx!(
103            vpx_sys::vpx_codec_dec_init_ver(
104                &mut ctx,
105                iface,
106                &cfg,
107                config.flags.bits() as _,
108                vpx_sys::VPX_DECODER_ABI_VERSION as _,
109            ),
110            Error::VpxCodecInitFailed,
111        )?;
112        Ok(Self {
113            ctx: NoDebugWrapper(ctx),
114        })
115    }
116    /// Submits data to be decoded by the decoder, yielding a number of
117    /// decoded images as a result. Please note that even if you only send
118    /// data for a single frame at a time, due to internal bitstream frame
119    /// rendering, the decoder might produce any number of frames per
120    /// `decode` call. You should retrieve all decoded images from the
121    /// returned iterator before trying to push more data to the decoder.
122    /// The images will always be returned to you in presentation order,
123    /// so there's no need to perform image reordering on your end.
124    pub fn decode(&mut self, data: &[u8]) -> Result<DecodedImageIterator> {
125        call_vpx!(
126            vpx_sys::vpx_codec_decode(
127                &mut self.ctx.0,
128                data.as_ptr(),
129                data.len() as _,
130                std::ptr::null_mut(),
131                0,
132            ),
133            Error::ErrorDecodingBitstream,
134        )?;
135        Ok(DecodedImageIterator {
136            ctx: &mut self.ctx.0,
137            iter: std::ptr::null_mut(),
138        })
139    }
140    /// Returns stream information for the decoder. This might not return
141    /// stream information initially, especially before sending any data
142    /// to the decoder.
143    pub fn stream_info(&mut self) -> Option<StreamInfo> {
144        crate::common::get_stream_info(&mut self.ctx.0)
145    }
146}
147
148impl Drop for Decoder {
149    fn drop(&mut self) {
150        // We unwrap here, because the only documented cases for this to fail
151        // are when passing a null pointer (which we cannot do) and the context
152        // not being initialized (which by design cannot happen in Rust).
153        unsafe {
154            let error = vpx_sys::vpx_codec_destroy(&mut self.ctx.0);
155            assert_eq!(error, vpx_sys::VPX_CODEC_OK);
156        }
157    }
158}
159
160/// The configuration for a new decoder. You must pass this to
161/// [`Decoder::new()`].
162#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
163pub struct DecoderConfig {
164    /// The codec to initialize in the decoder.
165    pub codec: CodecId,
166    /// The initial width of the decoder in pixels. Please note that
167    /// VPx streams can change resolution dynamically, so this is only
168    /// an initial hint to the decoder and you should look at the
169    /// decoded images to follow a potential resolution change.
170    pub width: u32,
171    /// The initial height of the decoder in pixels. Please note that
172    /// VPx streams can change resolution dynamically, so this is only
173    /// an initial hint to the decoder and you should look at the
174    /// decoded images to follow a potential resolution change.
175    pub height: u32,
176    /// Maximum number of threads to use, default 1. Zero is ignored and
177    /// treated as 1.
178    pub threads: u32,
179    /// Flags to set on the decoder.
180    pub flags: DecoderFlags,
181}
182
183impl DecoderConfig {
184    /// Constructs a new decoder configuration.
185    /// Note: `width` and `height` are in pixels.
186    pub fn new(codec: CodecId, width: u32, height: u32) -> Self {
187        Self {
188            codec,
189            width,
190            height,
191            threads: 1,
192            flags: DecoderFlags::default(),
193        }
194    }
195    fn cfg(
196        &self,
197    ) -> Result<(*const vpx_sys::vpx_codec_iface, vpx_sys::vpx_codec_dec_cfg)>
198    {
199        Ok((
200            self.codec.iface()?,
201            vpx_sys::vpx_codec_dec_cfg {
202                w: self.width,
203                h: self.height,
204                threads: self.threads,
205            },
206        ))
207    }
208}
209
210bitflags! {
211    /// Configuration flags for the decoder.
212    #[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
213    pub struct DecoderFlags: u32 {
214        /// Postprocess the decoded frame
215        const POSTPROC = vpx_sys::VPX_CODEC_USE_POSTPROC;
216        /// Conceal errors in decoded frames
217        const ERROR_CONCEALMENT = vpx_sys::VPX_CODEC_USE_ERROR_CONCEALMENT;
218        /// The input frame should be passed to the decoder one fragment
219        /// at a time
220        const INPUT_FRAGMENTS = vpx_sys::VPX_CODEC_USE_INPUT_FRAGMENTS;
221        /// Enable frame-based multi-threading
222        const FRAME_THREADING = vpx_sys::VPX_CODEC_USE_FRAME_THREADING;
223    }
224}
225
226/// An iterator returned from a call to [`Decoder::decode`]. Due to frame
227/// reordering, the decoder can return any number of frames for a single
228/// `decode` call. You should retrieve all decoded images from this iterator
229/// before trying to push more data to the decoder. The images will always
230/// be returned to you in presentation order, so there's no need to perform
231/// image reordering on your end.
232pub struct DecodedImageIterator<'a> {
233    ctx: &'a mut vpx_sys::vpx_codec_ctx,
234    iter: vpx_sys::vpx_codec_iter_t,
235}
236
237// This is send-safe, because we don't hold onto any mutexes between calls
238// and none of our functions are async. Also, we have no interior mutability
239// through immutable references, so we're Sync-safe.
240unsafe impl Send for DecodedImageIterator<'_> {}
241unsafe impl Sync for DecodedImageIterator<'_> {}
242
243impl Iterator for DecodedImageIterator<'_> {
244    type Item = DecodedImage;
245    fn next(&mut self) -> Option<Self::Item> {
246        unsafe {
247            let img = vpx_sys::vpx_codec_get_frame(self.ctx, &mut self.iter);
248            Some(DecodedImage {
249                img: NonNull::new(img)?,
250            })
251        }
252    }
253}
254
255/// Data structure owning a decoded image from a VPx video stream.
256/// Call [`DecodedImage::data()`] to access the image data.
257#[derive(Debug, PartialEq, Eq, Hash)]
258pub struct DecodedImage {
259    // We own the vpx_image, so during drop, we free the image.
260    img: NonNull<vpx_sys::vpx_image>,
261}
262
263// A decoded image is both send and sync safe, because it is completely
264// owned by us and holds no mutexes for access.
265unsafe impl Send for DecodedImage {}
266unsafe impl Sync for DecodedImage {}
267
268/// Encapsulates the specific pixel type returned in the decoded image.
269/// libvpx will return [`u16`] pixel data from high-bit-depth (10-bit
270/// and 12-bit) video streams.
271#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
272pub enum DecodedImageData<'a> {
273    /// Underlying data is 8-bits per sample. This variant is generated
274    /// for a normal bit depth (8-bit) video stream.
275    Data8b(YUVImageData<'a, u8>),
276    /// Underlying data is 16-bits per sample. This variant is generated
277    /// for a high bit depth (10-bit/12-bit) video stream.
278    Data16b(YUVImageData<'a, u16>),
279}
280
281impl DecodedImage {
282    /// Returns the color space of the decoded image.
283    pub fn color_space(&self) -> vpx_sys::vpx_color_space {
284        unsafe { self.img.as_ref().cs }
285    }
286    /// Returns the display width of the decoded image in pixels.
287    pub fn width(&self) -> u32 {
288        unsafe { self.img.as_ref().d_w }
289    }
290    /// Returns the display height of the decoded image in pixels.
291    pub fn height(&self) -> u32 {
292        unsafe { self.img.as_ref().d_h }
293    }
294    /// Allows access to the decoded image data.
295    pub fn data(&self) -> DecodedImageData {
296        unsafe {
297            use vpx_sys::vpx_img_fmt::*;
298            let img = self.img.as_ref();
299            match img.fmt {
300                VPX_IMG_FMT_YV12 | VPX_IMG_FMT_NV12 | VPX_IMG_FMT_I420
301                | VPX_IMG_FMT_I422 | VPX_IMG_FMT_I444 | VPX_IMG_FMT_I440 => {
302                    DecodedImageData::Data8b(
303                        YUVImageData::<u8>::from_vpx_image(img),
304                    )
305                }
306                VPX_IMG_FMT_I42016 | VPX_IMG_FMT_I42216
307                | VPX_IMG_FMT_I44416 | VPX_IMG_FMT_I44016 => {
308                    DecodedImageData::Data16b(
309                        YUVImageData::<u16>::from_vpx_image(img),
310                    )
311                }
312                _ => unreachable!(
313                    "libvpx returned invalid image format: {:?}",
314                    img.fmt,
315                ),
316            }
317        }
318    }
319}
320
321impl Drop for DecodedImage {
322    fn drop(&mut self) {
323        unsafe { vpx_sys::vpx_img_free(self.img.as_ptr()) }
324    }
325}
326
327#[cfg(test)]
328mod test {
329    use super::{
330        DecodedImage, DecodedImageIterator, Decoder, DecoderConfig,
331        DecoderFlags,
332    };
333    #[test]
334    fn test_send() {
335        fn assert_send<T: Send>() {}
336        assert_send::<Decoder>();
337        assert_send::<DecoderConfig>();
338        assert_send::<DecoderFlags>();
339        assert_send::<DecodedImage>();
340        assert_send::<DecodedImageIterator>();
341    }
342    #[test]
343    fn test_sync() {
344        fn assert_sync<T: Sync>() {}
345        assert_sync::<Decoder>();
346        assert_sync::<DecoderConfig>();
347        assert_sync::<DecoderFlags>();
348        assert_sync::<DecodedImage>();
349        assert_sync::<DecodedImageIterator>();
350    }
351}