fev/
jpeg.rs

1//! JPEG-related types and utilities.
2
3mod parser;
4
5#[cfg(test)]
6mod tests;
7
8use std::{cmp, mem};
9
10use bytemuck::{AnyBitPattern, Pod, Zeroable};
11
12use crate::{
13    buffer::{Buffer, BufferType},
14    config::Config,
15    context::Context,
16    display::Display,
17    error::Error,
18    raw::{Rectangle, VA_PADDING_LOW, VA_PADDING_MEDIUM},
19    surface::{RTFormat, Surface},
20    Entrypoint, Profile, Result, Rotation, SliceParameterBufferBase,
21};
22
23use self::parser::{JpegParser, SegmentKind, SofMarker};
24
25ffi_enum! {
26    pub enum ColorSpace: u8 {
27        YUV = 0,
28        RGB = 1,
29        BGR = 2,
30    }
31}
32
33/// Stores up to 4 quantizer tables and remembers which ones have been modified and need reloading.
34#[derive(Clone, Copy)]
35#[repr(C)]
36pub struct IQMatrixBuffer {
37    load_quantiser_table: [u8; 4],
38    /// 4 quantization tables, indexed by the `Tqi` field of the color component.
39    quantiser_table: [[u8; 64]; 4],
40    va_reserved: [u32; VA_PADDING_LOW],
41}
42
43impl IQMatrixBuffer {
44    pub fn new() -> Self {
45        unsafe { mem::zeroed() }
46    }
47
48    pub fn set_quantization_table(&mut self, index: u8, table_data: &[u8; 64]) {
49        assert!(index <= 3, "index {index} out of bounds");
50        let index = usize::from(index);
51        self.load_quantiser_table[index] = 1;
52        self.quantiser_table[index] = *table_data;
53    }
54}
55
56#[derive(Clone, Copy)]
57#[repr(C)]
58pub struct PictureParameterBuffer {
59    picture_width: u16,
60    picture_height: u16,
61    components: [Component; 255],
62    num_components: u8,
63    color_space: ColorSpace,
64    rotation: Rotation,
65    crop_rectangle: Rectangle,
66    va_reserved: [u32; VA_PADDING_MEDIUM - 3],
67}
68
69#[derive(Clone, Copy)]
70#[repr(C)]
71pub struct Component {
72    component_id: u8,
73    h_sampling_factor: u8,
74    v_sampling_factor: u8,
75    quantiser_table_selector: u8,
76}
77
78impl PictureParameterBuffer {
79    pub fn new(picture_width: u16, picture_height: u16, color_space: ColorSpace) -> Self {
80        unsafe {
81            let mut this: Self = mem::zeroed();
82            this.picture_width = picture_width;
83            this.picture_height = picture_height;
84            this.color_space = color_space;
85            this
86        }
87    }
88
89    #[inline]
90    pub fn picture_width(&self) -> u16 {
91        self.picture_width
92    }
93
94    #[inline]
95    pub fn picture_height(&self) -> u16 {
96        self.picture_height
97    }
98
99    #[inline]
100    pub fn set_rotation(&mut self, rotation: Rotation) {
101        self.rotation = rotation;
102    }
103
104    /// Adds a frame component.
105    ///
106    /// # Parameters
107    ///
108    /// - `Ci`: component identifier.
109    /// - `Hi`: horizontal sampling factor.
110    /// - `Vi`: vertical sampling factor.
111    /// - `Tqi`: quantization table destination selector.
112    #[allow(non_snake_case)]
113    pub fn push_component(&mut self, Ci: u8, Hi: u8, Vi: u8, Tqi: u8) {
114        let index = usize::from(self.num_components);
115        self.num_components = self
116            .num_components
117            .checked_add(1)
118            .expect("maximum number of frame components reached");
119
120        self.components[index].component_id = Ci;
121        self.components[index].h_sampling_factor = Hi;
122        self.components[index].v_sampling_factor = Vi;
123        self.components[index].quantiser_table_selector = Tqi;
124    }
125}
126
127#[derive(Clone, Copy)]
128#[repr(C)]
129pub struct SliceParameterBuffer {
130    base: SliceParameterBufferBase,
131
132    slice_horizontal_position: u32,
133    slice_vertical_position: u32,
134
135    components: [ScanComponent; 4],
136    num_components: u8,
137
138    restart_interval: u16,
139    num_mcus: u32,
140
141    va_reserved: [u32; VA_PADDING_LOW],
142}
143
144#[derive(Clone, Copy)]
145#[repr(C)]
146struct ScanComponent {
147    component_selector: u8,
148    dc_table_selector: u8,
149    ac_table_selector: u8,
150}
151
152impl SliceParameterBuffer {
153    /// Creates a new JPEG slice parameter structure.
154    ///
155    /// # Parameters
156    ///
157    /// - `base`: codec-independent slice parameters
158    /// - `Ri`: number of MCUs per restart interval
159    /// - `num_mcus`: total number of MCUs in this scan
160    #[allow(non_snake_case)]
161    pub fn new(base: SliceParameterBufferBase, Ri: u16, num_mcus: u32) -> Self {
162        unsafe {
163            let mut this: Self = mem::zeroed();
164            this.base = base;
165            this.restart_interval = Ri;
166            this.num_mcus = num_mcus;
167            this
168        }
169    }
170
171    #[allow(non_snake_case)]
172    pub fn push_component(&mut self, Csj: u8, Tdj: u8, Taj: u8) {
173        let index = usize::from(self.num_components);
174        self.num_components = self
175            .num_components
176            .checked_add(1)
177            .expect("maximum number of scan components reached");
178
179        self.components[index].component_selector = Csj;
180        self.components[index].dc_table_selector = Tdj;
181        self.components[index].ac_table_selector = Taj;
182    }
183}
184
185/// Stores up to 2 [`HuffmanTable`]s and remembers which have been modified and need reloading.
186#[derive(Clone, Copy, AnyBitPattern)]
187#[repr(C)]
188pub struct HuffmanTableBuffer {
189    load_huffman_table: [u8; 2],
190    huffman_table: [HuffmanTable; 2],
191    va_reserved: [u32; VA_PADDING_LOW],
192}
193
194impl HuffmanTableBuffer {
195    pub fn default_tables() -> Self {
196        let mut this = Self::zeroed();
197        this.set_huffman_table(0, &HuffmanTable::default_luminance());
198        this.set_huffman_table(1, &HuffmanTable::default_chrominance());
199        this
200    }
201
202    pub fn zeroed() -> Self {
203        unsafe { mem::zeroed() }
204    }
205
206    pub fn set_huffman_table(&mut self, index: u8, tbl: &HuffmanTable) {
207        assert!(index <= 1, "huffman table index {index} out of bounds");
208        let index = usize::from(index);
209        self.huffman_table[index] = *tbl;
210        self.load_huffman_table[index] = 1; // mark as modified
211    }
212
213    pub fn huffman_table_mut(&mut self, index: u8) -> &mut HuffmanTable {
214        assert!(index <= 1, "huffman table index {index} out of bounds");
215        let index = usize::from(index);
216        self.load_huffman_table[index] = 1; // mark as modified
217        &mut self.huffman_table[index]
218    }
219
220    pub fn clear_modified(&mut self) {
221        self.load_huffman_table = [0; 2];
222    }
223}
224
225#[derive(Clone, Copy, Pod, Zeroable)]
226#[repr(C)]
227pub struct HuffmanTable {
228    num_dc_codes: [u8; 16],
229    dc_values: [u8; 12],
230    num_ac_codes: [u8; 16],
231    ac_values: [u8; 162],
232    pad: [u8; 2],
233}
234
235impl HuffmanTable {
236    /// Returns the default [`HuffmanTable`] to use for luminance data.
237    #[rustfmt::skip]
238    pub fn default_luminance() -> Self {
239        let mut this = Self::zeroed();
240        this.set_dc_table(
241            &[0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0],
242            &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b],
243        );
244        this.set_ac_table(
245            &[0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125],
246            &[
247                0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
248                0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
249                0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
250                0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
251                0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
252                0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
253                0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
254                0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
255                0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
256                0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
257                0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
258                0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
259                0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
260                0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
261                0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
262                0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
263                0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
264                0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
265                0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
266                0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
267                0xf9, 0xfa,
268            ]
269        );
270        this
271    }
272
273    /// Returns the default [`HuffmanTable`] to use for chrominance data.
274    #[rustfmt::skip]
275    pub fn default_chrominance() -> Self {
276        let mut this = Self::zeroed();
277        this.set_dc_table(
278            &[0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
279            &[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b]
280        );
281        this.set_ac_table(
282            &[0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119],
283            &[
284                0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
285                0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
286                0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
287                0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
288                0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
289                0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
290                0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
291                0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
292                0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
293                0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
294                0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
295                0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
296                0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
297                0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
298                0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
299                0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
300                0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
301                0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
302                0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
303                0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
304                0xf9, 0xfa,
305            ]
306        );
307        this
308    }
309
310    pub fn zeroed() -> Self {
311        unsafe { mem::zeroed() }
312    }
313
314    #[allow(non_snake_case)]
315    pub fn set_dc_table(&mut self, Li: &[u8], Vij: &[u8]) {
316        assert!(
317            Li.len() <= 16,
318            "DC huffman table code count {} exceeds maximum",
319            Li.len(),
320        );
321        assert!(
322            Vij.len() <= 12,
323            "DC huffman table value count {} exceeds maximum",
324            Vij.len(),
325        );
326
327        self.num_dc_codes[..Li.len()].copy_from_slice(Li);
328        self.dc_values[..Vij.len()].copy_from_slice(Vij);
329    }
330
331    #[allow(non_snake_case)]
332    pub fn set_ac_table(&mut self, Li: &[u8], Vij: &[u8]) {
333        assert!(
334            Li.len() <= 16,
335            "AC huffman table code count {} exceeds maximum",
336            Li.len(),
337        );
338        assert!(
339            Vij.len() <= 162,
340            "AC huffman table value count {} exceeds maximum",
341            Vij.len(),
342        );
343
344        self.num_ac_codes[..Li.len()].copy_from_slice(Li);
345        self.ac_values[..Vij.len()].copy_from_slice(Vij);
346    }
347}
348
349/// JPEG metadata required to create a VA-API JPEG decoding session.
350#[derive(Debug, Clone, Copy)]
351pub struct JpegInfo {
352    width: u16,
353    height: u16,
354}
355
356impl JpegInfo {
357    /// Parses the given JPEG image.
358    ///
359    /// # Errors
360    ///
361    /// If this returns an error, the JPEG image is either malformed, or of an incompatible format
362    /// that is not supported by VA-API. In that case, the caller should fall back to software
363    /// decoding.
364    pub fn new(jpeg: &[u8]) -> Result<Self> {
365        let mut parser = JpegParser::new(&jpeg);
366        let segment = parser
367            .next_segment()?
368            .ok_or_else(|| Error::from("missing SOI segment"))?;
369        if !matches!(segment.kind, parser::SegmentKind::Soi) {
370            return Err(Error::from("missing SOI segment"));
371        }
372
373        let sof = loop {
374            let segment = parser
375                .next_segment()?
376                .ok_or_else(|| Error::from("missing SOF segment"))?;
377            match segment.kind {
378                SegmentKind::Sof(sof) => break sof,
379                _ => {}
380            }
381        };
382
383        if sof.sof() != SofMarker::SOF0 {
384            return Err(Error::from(format!(
385                "not a baseline JPEG ({:?})",
386                sof.sof()
387            )));
388        }
389        if sof.P() != 8 {
390            return Err(Error::from(format!(
391                "unsupported sample precision of {} bits (only 8-bit samples are supported)",
392                sof.P()
393            )));
394        }
395
396        Ok(Self {
397            width: sof.X(),
398            height: sof.Y(),
399        })
400    }
401
402    #[inline]
403    pub fn width(&self) -> u16 {
404        self.width
405    }
406
407    #[inline]
408    pub fn height(&self) -> u16 {
409        self.height
410    }
411}
412
413/// A VA-API JPEG decoding session.
414///
415/// This type encapsulates [`Surface`]s and [`Context`]s for decoding baseline JPEG files of a
416/// particular size. It will also convert the JPEG to standard sRGB color space.
417///
418/// [`Surface`]: crate::surface::Surface
419pub struct JpegDecodeSession {
420    width: u32,
421    height: u32,
422    jpeg_surface: Surface,
423    jpeg_context: Context,
424}
425
426impl JpegDecodeSession {
427    /// Creates [`Surface`]s and [`Context`]s to decode JPEG images of the given size.
428    ///
429    /// # Errors
430    ///
431    /// This function will return an error if VA-API object creation fails. This typically means
432    /// that the implementation does not support JPEG decoding, but it can also indicate that the
433    /// JPEG is simply too large and smaller ones would work.
434    ///
435    /// [`Surface`]: crate::surface::Surface
436    pub fn new(display: &Display, width: u16, height: u16) -> Result<Self> {
437        let width = u32::from(width);
438        let height = u32::from(height);
439
440        let config = Config::new(&display, Profile::JPEGBaseline, Entrypoint::VLD)?;
441        let jpeg_context = Context::new(&config, width, height)?;
442        let jpeg_surface = Surface::new(&display, width, height, RTFormat::YUV420)?;
443
444        Ok(Self {
445            width,
446            height,
447            jpeg_surface,
448            jpeg_context,
449        })
450    }
451
452    #[inline]
453    pub fn surface(&mut self) -> &mut Surface {
454        &mut self.jpeg_surface
455    }
456
457    /// Decodes a baseline JPEG, returning a [`Surface`] containing the decoded image.
458    ///
459    /// The decoded image is in the JPEG's native color space and uses an unspecified pixel format.
460    ///
461    /// # Errors
462    ///
463    /// This method returns an error when the JPEG is malformed or VA-API returns an error during
464    /// decoding.
465    pub fn decode(&mut self, jpeg: &[u8]) -> Result<&mut Surface> {
466        // TODO make this more flexible and move to `error` module
467        macro_rules! bail {
468            ($($args:tt)*) => {
469                return Err(Error::from(format!(
470                    $($args)*
471                )))
472            };
473        }
474
475        let mut dhtbuf = HuffmanTableBuffer::zeroed();
476        let mut max_h_factor = 0;
477        let mut max_v_factor = 0;
478        let mut restart_interval = 0;
479        let mut ppbuf = None;
480        let mut slice = None;
481        let mut iqbuf = IQMatrixBuffer::new();
482
483        let mut parser = JpegParser::new(&jpeg);
484        while let Some(segment) = parser.next_segment()? {
485            match segment.kind {
486                SegmentKind::Dqt(dqt) => {
487                    for dqt in dqt.tables() {
488                        if dqt.Pq() != 0 {
489                            bail!("unexpected value `{}` for DQT Pq", dqt.Pq());
490                        }
491                        iqbuf.set_quantization_table(dqt.Tq(), &dqt.Qk());
492                    }
493                }
494                SegmentKind::Dht(dht) => {
495                    for table in dht.tables() {
496                        if table.Th() > 1 {
497                            bail!(
498                                "invalid DHT destination slot {} (expected 0 or 1)",
499                                table.Th()
500                            );
501                        }
502                        let tbl = dhtbuf.huffman_table_mut(table.Th());
503                        match table.Tc() {
504                            0 => tbl.set_dc_table(table.Li(), table.Vij()),
505                            1 => tbl.set_ac_table(table.Li(), table.Vij()),
506                            _ => bail!("invalid DHT class {}", table.Tc()),
507                        }
508                    }
509                }
510                SegmentKind::Dri(dri) => restart_interval = dri.Ri(),
511                SegmentKind::Sof(sof) => {
512                    if sof.sof() != SofMarker::SOF0 {
513                        bail!("not a baseline JPEG (SOF={:?})", sof.sof());
514                    }
515
516                    if sof.P() != 8 {
517                        bail!("sample precision of {} bits is not supported", sof.P());
518                    }
519
520                    if u32::from(sof.Y()) != self.height || u32::from(sof.X()) != self.width {
521                        bail!(
522                            "image dimension {}x{} does not match context dimention {}x{}",
523                            sof.X(),
524                            sof.Y(),
525                            self.width,
526                            self.height
527                        );
528                    }
529
530                    let mut buf = PictureParameterBuffer::new(sof.X(), sof.Y(), ColorSpace::YUV);
531                    for component in sof.components() {
532                        buf.push_component(
533                            component.Ci(),
534                            component.Hi(),
535                            component.Vi(),
536                            component.Tqi(),
537                        );
538                        max_h_factor = cmp::max(u32::from(component.Hi()), max_h_factor);
539                        max_v_factor = cmp::max(u32::from(component.Vi()), max_v_factor);
540                    }
541                    ppbuf = Some(buf);
542                }
543                SegmentKind::Sos(sos) => {
544                    if sos.Ss() != 0 || sos.Se() != 63 {
545                        // Baseline JPEGs always use 0...63
546                        bail!(
547                            "invalid SOS header: Ss={}, Se={} (expected 0...63)",
548                            sos.Ss(),
549                            sos.Se(),
550                        );
551                    }
552
553                    if sos.Ah() != 0 || sos.Al() != 0 {
554                        // Baseline JPEGs always use 0...0
555                        bail!("invalid SOS header: Ah={}, Al={}", sos.Ah(), sos.Al());
556                    }
557
558                    let slice_data = sos.data();
559                    let num_mcus = ((self.width + max_h_factor * 8 - 1) / (max_h_factor * 8))
560                        * ((self.height + max_v_factor * 8 - 1) / (max_v_factor * 8));
561                    let mut slice_params = SliceParameterBuffer::new(
562                        SliceParameterBufferBase::new(slice_data.len().try_into().unwrap()),
563                        restart_interval,
564                        num_mcus,
565                    );
566                    for component in sos.components() {
567                        slice_params.push_component(
568                            component.Csj(),
569                            component.Tdj(),
570                            component.Taj(),
571                        );
572                    }
573                    slice = Some((slice_params, slice_data));
574                }
575                SegmentKind::Eoi => break,
576                _ => {}
577            }
578        }
579
580        let Some(ppbuf) = ppbuf else {
581            bail!("file is missing SOI segment")
582        };
583        let Some((slice_params, slice_data)) = slice else {
584            bail!("file is missing SOS header")
585        };
586
587        let mut buf_dht = Buffer::new_param(&self.jpeg_context, BufferType::HuffmanTable, dhtbuf)?;
588        let mut buf_iq = Buffer::new_param(&self.jpeg_context, BufferType::IQMatrix, iqbuf)?;
589        let mut buf_pp =
590            Buffer::new_param(&self.jpeg_context, BufferType::PictureParameter, ppbuf)?;
591        let mut buf_slice_param =
592            Buffer::new_param(&self.jpeg_context, BufferType::SliceParameter, slice_params)?;
593        let mut buf_slice_data =
594            Buffer::new_data(&self.jpeg_context, BufferType::SliceData, &slice_data)?;
595
596        let mut picture = self.jpeg_context.begin_picture(&mut self.jpeg_surface)?;
597        unsafe {
598            picture.render_picture(&mut buf_dht)?;
599            picture.render_picture(&mut buf_iq)?;
600            picture.render_picture(&mut buf_pp)?;
601            picture.render_picture(&mut buf_slice_param)?;
602            picture.render_picture(&mut buf_slice_data)?;
603            picture.end_picture()?;
604        }
605
606        Ok(&mut self.jpeg_surface)
607    }
608}