Skip to main content

dvb_si/descriptors/
j2k_video.rs

1//! J2K Video Descriptor — ISO/IEC 13818-1 §2.6.80, Table 2-101 (tag 0x32).
2//!
3//! Describes a JPEG 2000 (J2K) video elementary stream. The `extended_capability_flag`
4//! controls a large nested block: when true, stripe/block/mdm flags appear,
5//! followed by colour parameters and up to three conditional sub-blocks
6//! (stripe, block, mastering display metadata). When false, `color_specification`
7//! is present instead. `still_mode` and `interlaced_video` follow in both cases,
8//! plus `private_data` tail bytes.
9
10use super::descriptor_body;
11use crate::error::{Error, Result};
12use dvb_common::{Parse, Serialize};
13
14/// Descriptor tag for J2K_video_descriptor.
15pub const TAG: u8 = 0x32;
16const HEADER_LEN: usize = 2;
17
18/// Stripe sub-block — present when extended_capability_flag AND stripe_flag.
19#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21pub struct J2kStripe {
22    /// Maximum stripe index (1..=255, 0 is forbidden).
23    pub strp_max_idx: u8,
24    /// Default vertical size of a stripe.
25    pub strp_height: u16,
26}
27
28/// Block sub-block — present when extended_capability_flag AND block_flag.
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31pub struct J2kBlock {
32    /// Horizontal size of entire video frame.
33    pub full_horizontal_size: u32,
34    /// Vertical size of entire video frame.
35    pub full_vertical_size: u32,
36    /// Default width of a J2K block.
37    pub blk_width: u16,
38    /// Default height of a J2K block.
39    pub blk_height: u16,
40    /// Maximum block index in horizontal direction.
41    pub max_blk_idx_h: u8,
42    /// Maximum block index in vertical direction.
43    pub max_blk_idx_v: u8,
44    /// Block index in horizontal direction.
45    pub blk_idx_h: u8,
46    /// Block index in vertical direction.
47    pub blk_idx_v: u8,
48}
49
50/// Mastering Display Metadata (MDM) sub-block — present when extended_capability_flag AND mdm_flag.
51#[derive(Debug, Clone, PartialEq, Eq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize))]
53pub struct J2kMdm {
54    /// X coordinate of primary 0 (× 16-bit).
55    pub x_c0: u16,
56    /// Y coordinate of primary 0 (× 16-bit).
57    pub y_c0: u16,
58    /// X coordinate of primary 1 (× 16-bit).
59    pub x_c1: u16,
60    /// Y coordinate of primary 1 (× 16-bit).
61    pub y_c1: u16,
62    /// X coordinate of primary 2 (× 16-bit).
63    pub x_c2: u16,
64    /// Y coordinate of primary 2 (× 16-bit).
65    pub y_c2: u16,
66    /// White point X.
67    pub x_wp: u16,
68    /// White point Y.
69    pub y_wp: u16,
70    /// Max luminance (cd/m² × 10000).
71    pub l_max: u32,
72    /// Min luminance (cd/m² × 10000).
73    pub l_min: u32,
74    /// Max Content Light Level.
75    pub max_cll: u16,
76    /// Max Frame Average Light Level.
77    pub max_fall: u16,
78}
79
80/// Extended capability block — present when extended_capability_flag is true.
81#[derive(Debug, Clone, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize))]
83pub struct J2kExtendedCapability {
84    /// Stripe mode enabled.
85    pub stripe_flag: bool,
86    /// Block mode enabled.
87    pub block_flag: bool,
88    /// Mastering display metadata present.
89    pub mdm_flag: bool,
90    /// Colour primaries (H.273 / ISO 23001-8).
91    pub colour_primaries: u8,
92    /// Transfer characteristics (H.273 / ISO 23001-8).
93    pub transfer_characteristics: u8,
94    /// Matrix coefficients (H.273 / ISO 23001-8).
95    pub matrix_coefficients: u8,
96    /// Video full range flag.
97    pub video_full_range_flag: bool,
98    /// Stripe sub-block — present when stripe_flag.
99    pub stripe: Option<J2kStripe>,
100    /// Block sub-block — present when block_flag.
101    pub block: Option<J2kBlock>,
102    /// Mastering display metadata sub-block — present when mdm_flag.
103    pub mdm: Option<J2kMdm>,
104}
105
106/// J2K Video Descriptor.
107#[derive(Debug, Clone, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
109#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
110pub struct J2kVideoDescriptor<'a> {
111    /// Extended capability flag.
112    pub extended_capability_flag: bool,
113    /// Profile and level (15-bit, from least significant 15 bits of Rsiz).
114    pub profile_and_level: u16,
115    /// Horizontal size of the frame/field.
116    pub horizontal_size: u32,
117    /// Vertical size of the frame/field.
118    pub vertical_size: u32,
119    /// Maximum bit rate.
120    pub max_bit_rate: u32,
121    /// Maximum buffer size.
122    pub max_buffer_size: u32,
123    /// Frame rate denominator.
124    pub den_frame_rate: u16,
125    /// Frame rate numerator.
126    pub num_frame_rate: u16,
127    /// Extended capability block (when flag is true).
128    pub extended_capability: Option<J2kExtendedCapability>,
129    /// Legacy color specification (when extended_capability_flag is false).
130    pub color_specification: Option<u8>,
131    /// Still picture mode.
132    pub still_mode: bool,
133    /// Interlaced video.
134    pub interlaced_video: bool,
135    /// Trailing private data bytes.
136    #[cfg_attr(feature = "serde", serde(borrow))]
137    pub private_data: &'a [u8],
138}
139
140fn parse_extended_capability(
141    body: &[u8],
142    mut pos: usize,
143) -> Result<(J2kExtendedCapability, usize)> {
144    // stripe_flag(1) + block_flag(1) + mdm_flag(1) + reserved(5) = 1 byte
145    if body.len() < pos + 1 {
146        return Err(Error::InvalidDescriptor {
147            tag: TAG,
148            reason: "J2K_video_descriptor too short for stripe/block/mdm flags",
149        });
150    }
151    let sb = body[pos];
152    let stripe_flag = (sb & 0x80) != 0;
153    let block_flag = (sb & 0x40) != 0;
154    let mdm_flag = (sb & 0x20) != 0;
155    pos += 1;
156
157    // colour_primaries(1) + transfer(1) + matrix(1) + video_full_range(1) + reserved(7) = 4 bytes
158    if body.len() < pos + 4 {
159        return Err(Error::InvalidDescriptor {
160            tag: TAG,
161            reason: "J2K_video_descriptor too short for colour parameters",
162        });
163    }
164    let colour_primaries = body[pos];
165    let transfer_characteristics = body[pos + 1];
166    let matrix_coefficients = body[pos + 2];
167    let vfr = body[pos + 3];
168    let video_full_range_flag = (vfr & 0x80) != 0;
169    pos += 4;
170
171    // Stripe sub-block: strp_max_idx(1) + strp_height(2) = 3 bytes
172    let stripe = if stripe_flag {
173        if body.len() < pos + 3 {
174            return Err(Error::InvalidDescriptor {
175                tag: TAG,
176                reason: "J2K_video_descriptor too short for stripe sub-block",
177            });
178        }
179        let strp_max_idx = body[pos];
180        let strp_height = u16::from_be_bytes([body[pos + 1], body[pos + 2]]);
181        pos += 3;
182        Some(J2kStripe {
183            strp_max_idx,
184            strp_height,
185        })
186    } else {
187        None
188    };
189
190    // Block sub-block: full_h(4) + full_v(4) + blk_w(2) + blk_h(2) + max_idx_h(1) + max_idx_v(1) + blk_idx_h(1) + blk_idx_v(1) = 16
191    let block = if block_flag {
192        if body.len() < pos + 16 {
193            return Err(Error::InvalidDescriptor {
194                tag: TAG,
195                reason: "J2K_video_descriptor too short for block sub-block",
196            });
197        }
198        let full_horizontal_size =
199            u32::from_be_bytes([body[pos], body[pos + 1], body[pos + 2], body[pos + 3]]);
200        let full_vertical_size =
201            u32::from_be_bytes([body[pos + 4], body[pos + 5], body[pos + 6], body[pos + 7]]);
202        let blk_width = u16::from_be_bytes([body[pos + 8], body[pos + 9]]);
203        let blk_height = u16::from_be_bytes([body[pos + 10], body[pos + 11]]);
204        let max_blk_idx_h = body[pos + 12];
205        let max_blk_idx_v = body[pos + 13];
206        let blk_idx_h = body[pos + 14];
207        let blk_idx_v = body[pos + 15];
208        pos += 16;
209        Some(J2kBlock {
210            full_horizontal_size,
211            full_vertical_size,
212            blk_width,
213            blk_height,
214            max_blk_idx_h,
215            max_blk_idx_v,
216            blk_idx_h,
217            blk_idx_v,
218        })
219    } else {
220        None
221    };
222
223    // MDM sub-block: 6×u16(=12) + X_wp(2) + Y_wp(2) + L_max(4) + L_min(4) + MaxCLL(2) + MaxFALL(2) = 28
224    let mdm = if mdm_flag {
225        if body.len() < pos + 28 {
226            return Err(Error::InvalidDescriptor {
227                tag: TAG,
228                reason: "J2K_video_descriptor too short for MDM sub-block",
229            });
230        }
231        let x_c0 = u16::from_be_bytes([body[pos], body[pos + 1]]);
232        let y_c0 = u16::from_be_bytes([body[pos + 2], body[pos + 3]]);
233        let x_c1 = u16::from_be_bytes([body[pos + 4], body[pos + 5]]);
234        let y_c1 = u16::from_be_bytes([body[pos + 6], body[pos + 7]]);
235        let x_c2 = u16::from_be_bytes([body[pos + 8], body[pos + 9]]);
236        let y_c2 = u16::from_be_bytes([body[pos + 10], body[pos + 11]]);
237        let x_wp = u16::from_be_bytes([body[pos + 12], body[pos + 13]]);
238        let y_wp = u16::from_be_bytes([body[pos + 14], body[pos + 15]]);
239        let l_max = u32::from_be_bytes([
240            body[pos + 16],
241            body[pos + 17],
242            body[pos + 18],
243            body[pos + 19],
244        ]);
245        let l_min = u32::from_be_bytes([
246            body[pos + 20],
247            body[pos + 21],
248            body[pos + 22],
249            body[pos + 23],
250        ]);
251        let max_cll = u16::from_be_bytes([body[pos + 24], body[pos + 25]]);
252        let max_fall = u16::from_be_bytes([body[pos + 26], body[pos + 27]]);
253        pos += 28;
254        Some(J2kMdm {
255            x_c0,
256            y_c0,
257            x_c1,
258            y_c1,
259            x_c2,
260            y_c2,
261            x_wp,
262            y_wp,
263            l_max,
264            l_min,
265            max_cll,
266            max_fall,
267        })
268    } else {
269        None
270    };
271
272    Ok((
273        J2kExtendedCapability {
274            stripe_flag,
275            block_flag,
276            mdm_flag,
277            colour_primaries,
278            transfer_characteristics,
279            matrix_coefficients,
280            video_full_range_flag,
281            stripe,
282            block,
283            mdm,
284        },
285        pos,
286    ))
287}
288
289fn serialize_extended_capability(
290    ext: &J2kExtendedCapability,
291    buf: &mut [u8],
292    mut pos: usize,
293) -> usize {
294    let mut sb = 0u8;
295    if ext.stripe_flag {
296        sb |= 0x80;
297    }
298    if ext.block_flag {
299        sb |= 0x40;
300    }
301    if ext.mdm_flag {
302        sb |= 0x20;
303    }
304    buf[pos] = sb;
305    pos += 1;
306
307    buf[pos] = ext.colour_primaries;
308    buf[pos + 1] = ext.transfer_characteristics;
309    buf[pos + 2] = ext.matrix_coefficients;
310    buf[pos + 3] = if ext.video_full_range_flag {
311        0x80
312    } else {
313        0x00
314    };
315    pos += 4;
316
317    if let Some(ref s) = ext.stripe {
318        buf[pos] = s.strp_max_idx;
319        buf[pos + 1..pos + 3].copy_from_slice(&s.strp_height.to_be_bytes());
320        pos += 3;
321    }
322
323    if let Some(ref b) = ext.block {
324        buf[pos..pos + 4].copy_from_slice(&b.full_horizontal_size.to_be_bytes());
325        buf[pos + 4..pos + 8].copy_from_slice(&b.full_vertical_size.to_be_bytes());
326        buf[pos + 8..pos + 10].copy_from_slice(&b.blk_width.to_be_bytes());
327        buf[pos + 10..pos + 12].copy_from_slice(&b.blk_height.to_be_bytes());
328        buf[pos + 12] = b.max_blk_idx_h;
329        buf[pos + 13] = b.max_blk_idx_v;
330        buf[pos + 14] = b.blk_idx_h;
331        buf[pos + 15] = b.blk_idx_v;
332        pos += 16;
333    }
334
335    if let Some(ref m) = ext.mdm {
336        buf[pos..pos + 2].copy_from_slice(&m.x_c0.to_be_bytes());
337        buf[pos + 2..pos + 4].copy_from_slice(&m.y_c0.to_be_bytes());
338        buf[pos + 4..pos + 6].copy_from_slice(&m.x_c1.to_be_bytes());
339        buf[pos + 6..pos + 8].copy_from_slice(&m.y_c1.to_be_bytes());
340        buf[pos + 8..pos + 10].copy_from_slice(&m.x_c2.to_be_bytes());
341        buf[pos + 10..pos + 12].copy_from_slice(&m.y_c2.to_be_bytes());
342        buf[pos + 12..pos + 14].copy_from_slice(&m.x_wp.to_be_bytes());
343        buf[pos + 14..pos + 16].copy_from_slice(&m.y_wp.to_be_bytes());
344        buf[pos + 16..pos + 20].copy_from_slice(&m.l_max.to_be_bytes());
345        buf[pos + 20..pos + 24].copy_from_slice(&m.l_min.to_be_bytes());
346        buf[pos + 24..pos + 26].copy_from_slice(&m.max_cll.to_be_bytes());
347        buf[pos + 26..pos + 28].copy_from_slice(&m.max_fall.to_be_bytes());
348        pos += 28;
349    }
350
351    pos
352}
353
354fn extended_capability_serialized_len(ext: &J2kExtendedCapability) -> usize {
355    let mut len: usize = 5; // flags(1) + colour_primaries(1) + transfer(1) + matrix(1) + vfr(1)
356    if ext.stripe.is_some() {
357        len += 3;
358    }
359    if ext.block.is_some() {
360        len += 16;
361    }
362    if ext.mdm.is_some() {
363        len += 28;
364    }
365    len
366}
367
368impl<'a> Parse<'a> for J2kVideoDescriptor<'a> {
369    type Error = crate::error::Error;
370
371    fn parse(bytes: &'a [u8]) -> Result<Self> {
372        let body = descriptor_body(
373            bytes,
374            TAG,
375            "J2kVideoDescriptor",
376            "unexpected tag for J2K_video_descriptor",
377        )?;
378
379        // Minimum before extended_capability decision:
380        //   flag+profile_and_level(2) + h_size(4) + v_size(4) + max_bit_rate(4) + max_buf(4)
381        //   + DEN(2) + NUM(2) = 22
382        if body.len() < 22 {
383            return Err(Error::InvalidDescriptor {
384                tag: TAG,
385                reason: "J2K_video_descriptor too short (< 22 body bytes)",
386            });
387        }
388
389        // extended_capability_flag(1) | profile_and_level(15)
390        let b01 = u16::from_be_bytes([body[0], body[1]]);
391        let extended_capability_flag = (b01 & 0x8000) != 0;
392        let profile_and_level = b01 & 0x7FFF;
393
394        let horizontal_size = u32::from_be_bytes([body[2], body[3], body[4], body[5]]);
395        let vertical_size = u32::from_be_bytes([body[6], body[7], body[8], body[9]]);
396        let max_bit_rate = u32::from_be_bytes([body[10], body[11], body[12], body[13]]);
397        let max_buffer_size = u32::from_be_bytes([body[14], body[15], body[16], body[17]]);
398        let den_frame_rate = u16::from_be_bytes([body[18], body[19]]);
399        let num_frame_rate = u16::from_be_bytes([body[20], body[21]]);
400        let mut pos = 22;
401
402        let (extended_capability, color_specification, mut pos) = if extended_capability_flag {
403            let (ext, new_pos) = parse_extended_capability(body, pos)?;
404            (Some(ext), None, new_pos)
405        } else {
406            // color_specification(1)
407            if body.len() < pos + 1 {
408                return Err(Error::InvalidDescriptor {
409                    tag: TAG,
410                    reason: "J2K_video_descriptor too short for color_specification",
411                });
412            }
413            let cs = body[pos];
414            pos += 1;
415            (None, Some(cs), pos)
416        };
417
418        // still_mode(1) | interlaced_video(1) | reserved(6) = 1 byte
419        if body.len() < pos + 1 {
420            return Err(Error::InvalidDescriptor {
421                tag: TAG,
422                reason: "J2K_video_descriptor too short for still_mode/interlaced byte",
423            });
424        }
425        let sm_iv = body[pos];
426        let still_mode = (sm_iv & 0x80) != 0;
427        let interlaced_video = (sm_iv & 0x40) != 0;
428        pos += 1;
429
430        let private_data = &body[pos..];
431
432        Ok(Self {
433            extended_capability_flag,
434            profile_and_level,
435            horizontal_size,
436            vertical_size,
437            max_bit_rate,
438            max_buffer_size,
439            den_frame_rate,
440            num_frame_rate,
441            extended_capability,
442            color_specification,
443            still_mode,
444            interlaced_video,
445            private_data,
446        })
447    }
448}
449
450impl Serialize for J2kVideoDescriptor<'_> {
451    type Error = crate::error::Error;
452
453    fn serialized_len(&self) -> usize {
454        let mut len: usize = HEADER_LEN + 23; // pre-flag fields(22) + still_mode byte(1)
455        if let Some(ref ext) = self.extended_capability {
456            len += extended_capability_serialized_len(ext);
457        } else {
458            len += 1; // color_specification
459        }
460        len += self.private_data.len();
461        len
462    }
463
464    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
465        let len = self.serialized_len();
466        if buf.len() < len {
467            return Err(Error::OutputBufferTooSmall {
468                need: len,
469                have: buf.len(),
470            });
471        }
472        buf[0] = TAG;
473        buf[1] = (len - HEADER_LEN) as u8;
474
475        // extended_capability_flag(1) | profile_and_level(15)
476        let mut b01 = self.profile_and_level & 0x7FFF;
477        if self.extended_capability_flag {
478            b01 |= 0x8000;
479        }
480        buf[HEADER_LEN] = (b01 >> 8) as u8;
481        buf[HEADER_LEN + 1] = b01 as u8;
482        buf[HEADER_LEN + 2..HEADER_LEN + 6].copy_from_slice(&self.horizontal_size.to_be_bytes());
483        buf[HEADER_LEN + 6..HEADER_LEN + 10].copy_from_slice(&self.vertical_size.to_be_bytes());
484        buf[HEADER_LEN + 10..HEADER_LEN + 14].copy_from_slice(&self.max_bit_rate.to_be_bytes());
485        buf[HEADER_LEN + 14..HEADER_LEN + 18].copy_from_slice(&self.max_buffer_size.to_be_bytes());
486        buf[HEADER_LEN + 18..HEADER_LEN + 20].copy_from_slice(&self.den_frame_rate.to_be_bytes());
487        buf[HEADER_LEN + 20..HEADER_LEN + 22].copy_from_slice(&self.num_frame_rate.to_be_bytes());
488        let mut pos = HEADER_LEN + 22;
489
490        if let Some(ref ext) = self.extended_capability {
491            pos = serialize_extended_capability(ext, buf, pos);
492        } else if let Some(cs) = self.color_specification {
493            buf[pos] = cs;
494            pos += 1;
495        }
496
497        let mut sm_iv = 0u8;
498        if self.still_mode {
499            sm_iv |= 0x80;
500        }
501        if self.interlaced_video {
502            sm_iv |= 0x40;
503        }
504        buf[pos] = sm_iv;
505        pos += 1;
506
507        buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
508        Ok(len)
509    }
510}
511
512impl<'a> crate::traits::DescriptorDef<'a> for J2kVideoDescriptor<'a> {
513    const TAG: u8 = TAG;
514    const NAME: &'static str = "J2K_VIDEO";
515}
516
517#[cfg(test)]
518mod tests {
519    use super::*;
520
521    fn serialize_round_trip(d: &J2kVideoDescriptor<'_>) {
522        let mut buf = vec![0u8; d.serialized_len()];
523        let written = d.serialize_into(&mut buf).unwrap();
524        assert_eq!(written, d.serialized_len());
525        let reparsed = J2kVideoDescriptor::parse(&buf).unwrap();
526        assert_eq!(*d, reparsed, "round-trip mismatch");
527    }
528
529    #[test]
530    fn round_trip_no_extended_capability() {
531        let d = J2kVideoDescriptor {
532            extended_capability_flag: false,
533            profile_and_level: 0x0102,
534            horizontal_size: 1920,
535            vertical_size: 1080,
536            max_bit_rate: 10_000_000,
537            max_buffer_size: 8_000_000,
538            den_frame_rate: 1001,
539            num_frame_rate: 24000,
540            extended_capability: None,
541            color_specification: Some(0x03),
542            still_mode: false,
543            interlaced_video: true,
544            private_data: &[],
545        };
546        serialize_round_trip(&d);
547    }
548
549    #[test]
550    fn round_trip_extended_capability_basic() {
551        let d = J2kVideoDescriptor {
552            extended_capability_flag: true,
553            profile_and_level: 0x0307,
554            horizontal_size: 3840,
555            vertical_size: 2160,
556            max_bit_rate: 30_000_000,
557            max_buffer_size: 15_000_000,
558            den_frame_rate: 1001,
559            num_frame_rate: 60000,
560            extended_capability: Some(J2kExtendedCapability {
561                stripe_flag: false,
562                block_flag: false,
563                mdm_flag: false,
564                colour_primaries: 1,
565                transfer_characteristics: 16,
566                matrix_coefficients: 0,
567                video_full_range_flag: true,
568                stripe: None,
569                block: None,
570                mdm: None,
571            }),
572            color_specification: None,
573            still_mode: false,
574            interlaced_video: false,
575            private_data: &[],
576        };
577        serialize_round_trip(&d);
578    }
579
580    #[test]
581    fn round_trip_extended_capability_stripe_only() {
582        let d = J2kVideoDescriptor {
583            extended_capability_flag: true,
584            profile_and_level: 0x0307,
585            horizontal_size: 3840,
586            vertical_size: 2160,
587            max_bit_rate: 30_000_000,
588            max_buffer_size: 15_000_000,
589            den_frame_rate: 1,
590            num_frame_rate: 60,
591            extended_capability: Some(J2kExtendedCapability {
592                stripe_flag: true,
593                block_flag: false,
594                mdm_flag: false,
595                colour_primaries: 9,
596                transfer_characteristics: 14,
597                matrix_coefficients: 0,
598                video_full_range_flag: false,
599                stripe: Some(J2kStripe {
600                    strp_max_idx: 3,
601                    strp_height: 1024,
602                }),
603                block: None,
604                mdm: None,
605            }),
606            color_specification: None,
607            still_mode: true,
608            interlaced_video: false,
609            private_data: &[0xAA],
610        };
611        serialize_round_trip(&d);
612    }
613
614    #[test]
615    fn round_trip_extended_capability_block_only() {
616        let d = J2kVideoDescriptor {
617            extended_capability_flag: true,
618            profile_and_level: 0x0307,
619            horizontal_size: 1920,
620            vertical_size: 1080,
621            max_bit_rate: 20_000_000,
622            max_buffer_size: 10_000_000,
623            den_frame_rate: 1001,
624            num_frame_rate: 30000,
625            extended_capability: Some(J2kExtendedCapability {
626                stripe_flag: false,
627                block_flag: true,
628                mdm_flag: false,
629                colour_primaries: 1,
630                transfer_characteristics: 1,
631                matrix_coefficients: 1,
632                video_full_range_flag: true,
633                stripe: None,
634                block: Some(J2kBlock {
635                    full_horizontal_size: 3840,
636                    full_vertical_size: 2160,
637                    blk_width: 1920,
638                    blk_height: 1080,
639                    max_blk_idx_h: 1,
640                    max_blk_idx_v: 1,
641                    blk_idx_h: 0,
642                    blk_idx_v: 0,
643                }),
644                mdm: None,
645            }),
646            color_specification: None,
647            still_mode: false,
648            interlaced_video: false,
649            private_data: &[],
650        };
651        serialize_round_trip(&d);
652    }
653
654    #[test]
655    fn round_trip_extended_capability_mdm_only() {
656        let d = J2kVideoDescriptor {
657            extended_capability_flag: true,
658            profile_and_level: 0x0307,
659            horizontal_size: 1920,
660            vertical_size: 1080,
661            max_bit_rate: 15_000_000,
662            max_buffer_size: 8_000_000,
663            den_frame_rate: 1,
664            num_frame_rate: 25,
665            extended_capability: Some(J2kExtendedCapability {
666                stripe_flag: false,
667                block_flag: false,
668                mdm_flag: true,
669                colour_primaries: 9,
670                transfer_characteristics: 16,
671                matrix_coefficients: 9,
672                video_full_range_flag: false,
673                stripe: None,
674                block: None,
675                mdm: Some(J2kMdm {
676                    x_c0: 1,
677                    y_c0: 2,
678                    x_c1: 3,
679                    y_c1: 4,
680                    x_c2: 5,
681                    y_c2: 6,
682                    x_wp: 7,
683                    y_wp: 8,
684                    l_max: 1000,
685                    l_min: 5,
686                    max_cll: 800,
687                    max_fall: 400,
688                }),
689            }),
690            color_specification: None,
691            still_mode: false,
692            interlaced_video: true,
693            private_data: &[],
694        };
695        serialize_round_trip(&d);
696    }
697
698    #[test]
699    fn round_trip_extended_capability_all() {
700        let d = J2kVideoDescriptor {
701            extended_capability_flag: true,
702            profile_and_level: 0x0307,
703            horizontal_size: 7680,
704            vertical_size: 4320,
705            max_bit_rate: 100_000_000,
706            max_buffer_size: 50_000_000,
707            den_frame_rate: 1001,
708            num_frame_rate: 60000,
709            extended_capability: Some(J2kExtendedCapability {
710                stripe_flag: true,
711                block_flag: true,
712                mdm_flag: true,
713                colour_primaries: 9,
714                transfer_characteristics: 16,
715                matrix_coefficients: 9,
716                video_full_range_flag: true,
717                stripe: Some(J2kStripe {
718                    strp_max_idx: 7,
719                    strp_height: 540,
720                }),
721                block: Some(J2kBlock {
722                    full_horizontal_size: 15360,
723                    full_vertical_size: 8640,
724                    blk_width: 7680,
725                    blk_height: 4320,
726                    max_blk_idx_h: 1,
727                    max_blk_idx_v: 1,
728                    blk_idx_h: 0,
729                    blk_idx_v: 1,
730                }),
731                mdm: Some(J2kMdm {
732                    x_c0: 6800,
733                    y_c0: 3200,
734                    x_c1: 2650,
735                    y_c1: 6900,
736                    x_c2: 1500,
737                    y_c2: 600,
738                    x_wp: 3127,
739                    y_wp: 3290,
740                    l_max: 4_000_000,
741                    l_min: 50,
742                    max_cll: 10_000,
743                    max_fall: 5_000,
744                }),
745            }),
746            color_specification: None,
747            still_mode: true,
748            interlaced_video: true,
749            private_data: &[0xDD, 0xEE, 0xFF],
750        };
751        serialize_round_trip(&d);
752    }
753
754    #[test]
755    fn parse_rejects_wrong_tag() {
756        let buf = [
757            0x02, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
758        ];
759        let err = J2kVideoDescriptor::parse(&buf).unwrap_err();
760        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x02, .. }));
761    }
762
763    #[test]
764    fn parse_rejects_too_short() {
765        let err = J2kVideoDescriptor::parse(&[TAG, 0]).unwrap_err();
766        assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
767    }
768}