styx_codec/
mjpeg.rs

1use std::io::Cursor;
2
3use jpeg_decoder::{Decoder, PixelFormat};
4use styx_core::prelude::*;
5
6#[cfg(feature = "image")]
7use crate::decoder::{ImageDecode, process_to_dynamic};
8use crate::{Codec, CodecDescriptor, CodecError, CodecKind};
9
10/// MJPEG decoder that produces RGB24 frames using a reusable buffer pool.
11pub struct MjpegDecoder {
12    descriptor: CodecDescriptor,
13    pool: BufferPool,
14}
15
16impl MjpegDecoder {
17    /// Create a decoder that outputs the given FourCc (e.g. `FourCc::new(*b"RG24")`).
18    pub fn new(output: FourCc) -> Self {
19        Self::with_pool(output, BufferPool::with_limits(2, 1 << 20, 4))
20    }
21
22    /// Create a decoder for a specific input FourCc (e.g. `MJPG` or `JPEG`).
23    pub fn new_for_input(input: FourCc, output: FourCc) -> Self {
24        Self::with_pool_for_input(input, output, BufferPool::with_limits(2, 1 << 20, 4))
25    }
26
27    /// Create a decoder with a caller-provided buffer pool.
28    pub fn with_pool(output: FourCc, pool: BufferPool) -> Self {
29        Self::with_pool_for_input(FourCc::new(*b"MJPG"), output, pool)
30    }
31
32    /// Create a decoder for a specific input FourCc using a caller-provided buffer pool.
33    pub fn with_pool_for_input(input: FourCc, output: FourCc, pool: BufferPool) -> Self {
34        Self {
35            descriptor: CodecDescriptor {
36                kind: CodecKind::Decoder,
37                input,
38                output,
39                name: "mjpeg",
40                impl_name: "jpeg-decoder",
41            },
42            pool,
43        }
44    }
45}
46
47impl Codec for MjpegDecoder {
48    fn descriptor(&self) -> &CodecDescriptor {
49        &self.descriptor
50    }
51
52    fn process(&self, input: FrameLease) -> Result<FrameLease, CodecError> {
53        if input.meta().format.code != self.descriptor.input {
54            return Err(CodecError::FormatMismatch {
55                expected: self.descriptor.input,
56                actual: input.meta().format.code,
57            });
58        }
59
60        let plane = input
61            .planes()
62            .into_iter()
63            .next()
64            .ok_or_else(|| CodecError::Codec("mjpeg frame missing plane".into()))?;
65
66        let mut decoder = Decoder::new(Cursor::new(plane.data()));
67        let pixels = decoder
68            .decode()
69            .map_err(|e| CodecError::Codec(e.to_string()))?;
70        let info = decoder
71            .info()
72            .ok_or_else(|| CodecError::Codec("mjpeg missing info".into()))?;
73
74        if info.pixel_format != PixelFormat::RGB24 {
75            return Err(CodecError::Codec(format!(
76                "unsupported MJPEG pixel format {:?}",
77                info.pixel_format
78            )));
79        }
80
81        let resolution = Resolution::new(info.width as u32, info.height as u32)
82            .ok_or_else(|| CodecError::Codec("invalid jpeg resolution".into()))?;
83        let format = MediaFormat::new(
84            self.descriptor.output,
85            resolution,
86            input.meta().format.color,
87        );
88        let layout = plane_layout_from_dims(resolution.width, resolution.height, 3);
89
90        let mut buf = self.pool.lease();
91        buf.resize(layout.len);
92        let copy_len = pixels.len().min(layout.len);
93        buf.as_mut_slice()[..copy_len].copy_from_slice(&pixels[..copy_len]);
94
95        Ok(FrameLease::single_plane(
96            FrameMeta::new(format, input.meta().timestamp),
97            buf,
98            layout.len,
99            layout.stride,
100        ))
101    }
102}
103
104#[cfg(feature = "image")]
105impl ImageDecode for MjpegDecoder {
106    fn decode_image(&self, frame: FrameLease) -> Result<image::DynamicImage, CodecError> {
107        process_to_dynamic(self, frame)
108    }
109}