use super::descriptor_body;
use crate::error::{Error, Result};
use broadcast_common::{Parse, Serialize};
pub const TAG: u8 = 0x32;
const HEADER_LEN: usize = 2;
pub const RESERVED_5_EXTENDED_DEFAULT: u8 = 0;
pub const RESERVED_6_DEFAULT: u8 = 0b11_1111;
pub const RESERVED_7_DEFAULT: u8 = 0x7F;
const RESERVED_5_MASK: u8 = 0x1F;
const RESERVED_6_MASK: u8 = 0x3F;
const RESERVED_7_MASK: u8 = 0x7F;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct J2kStripe {
pub strp_max_idx: u8,
pub strp_height: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct J2kBlock {
pub full_horizontal_size: u32,
pub full_vertical_size: u32,
pub blk_width: u16,
pub blk_height: u16,
pub max_blk_idx_h: u8,
pub max_blk_idx_v: u8,
pub blk_idx_h: u8,
pub blk_idx_v: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct J2kMdm {
pub x_c0: u16,
pub y_c0: u16,
pub x_c1: u16,
pub y_c1: u16,
pub x_c2: u16,
pub y_c2: u16,
pub x_wp: u16,
pub y_wp: u16,
pub l_max: u32,
pub l_min: u32,
pub max_cll: u16,
pub max_fall: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct J2kExtendedCapability {
pub stripe_flag: bool,
pub block_flag: bool,
pub mdm_flag: bool,
pub colour_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coefficients: u8,
pub video_full_range_flag: bool,
pub stripe: Option<J2kStripe>,
pub block: Option<J2kBlock>,
pub mdm: Option<J2kMdm>,
pub reserved_5: u8,
pub reserved_7: u8,
}
impl J2kExtendedCapability {
#[must_use]
pub const fn new(
colour_primaries: u8,
transfer_characteristics: u8,
matrix_coefficients: u8,
) -> Self {
Self {
stripe_flag: false,
block_flag: false,
mdm_flag: false,
colour_primaries,
transfer_characteristics,
matrix_coefficients,
video_full_range_flag: false,
stripe: None,
block: None,
mdm: None,
reserved_5: RESERVED_5_EXTENDED_DEFAULT,
reserved_7: RESERVED_7_DEFAULT,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[non_exhaustive]
pub struct J2kVideoDescriptor<'a> {
pub extended_capability_flag: bool,
pub profile_and_level: u16,
pub horizontal_size: u32,
pub vertical_size: u32,
pub max_bit_rate: u32,
pub max_buffer_size: u32,
pub den_frame_rate: u16,
pub num_frame_rate: u16,
pub extended_capability: Option<J2kExtendedCapability>,
pub color_specification: Option<u8>,
pub still_mode: bool,
pub interlaced_video: bool,
#[cfg_attr(feature = "serde", serde(borrow))]
pub private_data: &'a [u8],
pub reserved_6: u8,
}
impl<'a> J2kVideoDescriptor<'a> {
#[must_use]
pub const fn new(profile_and_level: u16, horizontal_size: u32, vertical_size: u32) -> Self {
Self {
extended_capability_flag: false,
profile_and_level,
horizontal_size,
vertical_size,
max_bit_rate: 0,
max_buffer_size: 0,
den_frame_rate: 0,
num_frame_rate: 0,
extended_capability: None,
color_specification: Some(0),
still_mode: false,
interlaced_video: false,
private_data: &[],
reserved_6: RESERVED_6_DEFAULT,
}
}
}
impl Default for J2kVideoDescriptor<'_> {
fn default() -> Self {
Self::new(0, 0, 0)
}
}
fn parse_extended_capability(
body: &[u8],
mut pos: usize,
) -> Result<(J2kExtendedCapability, bool, bool, u8, usize)> {
if body.len() < pos + 1 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for stripe/block/mdm flags",
});
}
let sb = body[pos];
let stripe_flag = (sb & 0x80) != 0;
let block_flag = (sb & 0x40) != 0;
let mdm_flag = (sb & 0x20) != 0;
let reserved_5 = sb & RESERVED_5_MASK;
pos += 1;
if body.len() < pos + 1 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for still_mode/interlaced byte",
});
}
let sm_iv = body[pos];
let still_mode = (sm_iv & 0x80) != 0;
let interlaced_video = (sm_iv & 0x40) != 0;
let reserved_6 = sm_iv & RESERVED_6_MASK;
pos += 1;
if body.len() < pos + 4 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for colour parameters",
});
}
let colour_primaries = body[pos];
let transfer_characteristics = body[pos + 1];
let matrix_coefficients = body[pos + 2];
let vfr = body[pos + 3];
let video_full_range_flag = (vfr & 0x80) != 0;
let reserved_7 = vfr & RESERVED_7_MASK;
pos += 4;
let stripe = if stripe_flag {
if body.len() < pos + 3 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for stripe sub-block",
});
}
let strp_max_idx = body[pos];
let strp_height = u16::from_be_bytes([body[pos + 1], body[pos + 2]]);
pos += 3;
Some(J2kStripe {
strp_max_idx,
strp_height,
})
} else {
None
};
let block = if block_flag {
if body.len() < pos + 16 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for block sub-block",
});
}
let full_horizontal_size =
u32::from_be_bytes([body[pos], body[pos + 1], body[pos + 2], body[pos + 3]]);
let full_vertical_size =
u32::from_be_bytes([body[pos + 4], body[pos + 5], body[pos + 6], body[pos + 7]]);
let blk_width = u16::from_be_bytes([body[pos + 8], body[pos + 9]]);
let blk_height = u16::from_be_bytes([body[pos + 10], body[pos + 11]]);
let max_blk_idx_h = body[pos + 12];
let max_blk_idx_v = body[pos + 13];
let blk_idx_h = body[pos + 14];
let blk_idx_v = body[pos + 15];
pos += 16;
Some(J2kBlock {
full_horizontal_size,
full_vertical_size,
blk_width,
blk_height,
max_blk_idx_h,
max_blk_idx_v,
blk_idx_h,
blk_idx_v,
})
} else {
None
};
let mdm = if mdm_flag {
if body.len() < pos + 28 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for MDM sub-block",
});
}
let x_c0 = u16::from_be_bytes([body[pos], body[pos + 1]]);
let y_c0 = u16::from_be_bytes([body[pos + 2], body[pos + 3]]);
let x_c1 = u16::from_be_bytes([body[pos + 4], body[pos + 5]]);
let y_c1 = u16::from_be_bytes([body[pos + 6], body[pos + 7]]);
let x_c2 = u16::from_be_bytes([body[pos + 8], body[pos + 9]]);
let y_c2 = u16::from_be_bytes([body[pos + 10], body[pos + 11]]);
let x_wp = u16::from_be_bytes([body[pos + 12], body[pos + 13]]);
let y_wp = u16::from_be_bytes([body[pos + 14], body[pos + 15]]);
let l_max = u32::from_be_bytes([
body[pos + 16],
body[pos + 17],
body[pos + 18],
body[pos + 19],
]);
let l_min = u32::from_be_bytes([
body[pos + 20],
body[pos + 21],
body[pos + 22],
body[pos + 23],
]);
let max_cll = u16::from_be_bytes([body[pos + 24], body[pos + 25]]);
let max_fall = u16::from_be_bytes([body[pos + 26], body[pos + 27]]);
pos += 28;
Some(J2kMdm {
x_c0,
y_c0,
x_c1,
y_c1,
x_c2,
y_c2,
x_wp,
y_wp,
l_max,
l_min,
max_cll,
max_fall,
})
} else {
None
};
Ok((
J2kExtendedCapability {
stripe_flag,
block_flag,
mdm_flag,
colour_primaries,
transfer_characteristics,
matrix_coefficients,
video_full_range_flag,
stripe,
block,
mdm,
reserved_5,
reserved_7,
},
still_mode,
interlaced_video,
reserved_6,
pos,
))
}
fn serialize_extended_capability(
ext: &J2kExtendedCapability,
still_mode: bool,
interlaced_video: bool,
reserved_6: u8,
buf: &mut [u8],
mut pos: usize,
) -> Result<usize> {
let mut sb = broadcast_common::len::fit_mask(ext.reserved_5, RESERVED_5_MASK, "reserved_5")?;
if ext.stripe.is_some() {
sb |= 0x80;
}
if ext.block.is_some() {
sb |= 0x40;
}
if ext.mdm.is_some() {
sb |= 0x20;
}
buf[pos] = sb;
pos += 1;
let mut sm_iv = broadcast_common::len::fit_mask(reserved_6, RESERVED_6_MASK, "reserved_6")?;
if still_mode {
sm_iv |= 0x80;
}
if interlaced_video {
sm_iv |= 0x40;
}
buf[pos] = sm_iv;
pos += 1;
buf[pos] = ext.colour_primaries;
buf[pos + 1] = ext.transfer_characteristics;
buf[pos + 2] = ext.matrix_coefficients;
buf[pos + 3] =
if ext.video_full_range_flag {
0x80
} else {
0x00
} | broadcast_common::len::fit_mask(ext.reserved_7, RESERVED_7_MASK, "reserved_7")?;
pos += 4;
if let Some(ref s) = ext.stripe {
buf[pos] = s.strp_max_idx;
buf[pos + 1..pos + 3].copy_from_slice(&s.strp_height.to_be_bytes());
pos += 3;
}
if let Some(ref b) = ext.block {
buf[pos..pos + 4].copy_from_slice(&b.full_horizontal_size.to_be_bytes());
buf[pos + 4..pos + 8].copy_from_slice(&b.full_vertical_size.to_be_bytes());
buf[pos + 8..pos + 10].copy_from_slice(&b.blk_width.to_be_bytes());
buf[pos + 10..pos + 12].copy_from_slice(&b.blk_height.to_be_bytes());
buf[pos + 12] = b.max_blk_idx_h;
buf[pos + 13] = b.max_blk_idx_v;
buf[pos + 14] = b.blk_idx_h;
buf[pos + 15] = b.blk_idx_v;
pos += 16;
}
if let Some(ref m) = ext.mdm {
buf[pos..pos + 2].copy_from_slice(&m.x_c0.to_be_bytes());
buf[pos + 2..pos + 4].copy_from_slice(&m.y_c0.to_be_bytes());
buf[pos + 4..pos + 6].copy_from_slice(&m.x_c1.to_be_bytes());
buf[pos + 6..pos + 8].copy_from_slice(&m.y_c1.to_be_bytes());
buf[pos + 8..pos + 10].copy_from_slice(&m.x_c2.to_be_bytes());
buf[pos + 10..pos + 12].copy_from_slice(&m.y_c2.to_be_bytes());
buf[pos + 12..pos + 14].copy_from_slice(&m.x_wp.to_be_bytes());
buf[pos + 14..pos + 16].copy_from_slice(&m.y_wp.to_be_bytes());
buf[pos + 16..pos + 20].copy_from_slice(&m.l_max.to_be_bytes());
buf[pos + 20..pos + 24].copy_from_slice(&m.l_min.to_be_bytes());
buf[pos + 24..pos + 26].copy_from_slice(&m.max_cll.to_be_bytes());
buf[pos + 26..pos + 28].copy_from_slice(&m.max_fall.to_be_bytes());
pos += 28;
}
Ok(pos)
}
fn extended_capability_serialized_len(ext: &J2kExtendedCapability) -> usize {
let mut len: usize = 6; if ext.stripe.is_some() {
len += 3;
}
if ext.block.is_some() {
len += 16;
}
if ext.mdm.is_some() {
len += 28;
}
len
}
impl<'a> Parse<'a> for J2kVideoDescriptor<'a> {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"J2kVideoDescriptor",
"unexpected tag for J2K_video_descriptor",
)?;
if body.len() < 22 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short (< 22 body bytes)",
});
}
let b01 = u16::from_be_bytes([body[0], body[1]]);
let extended_capability_flag = (b01 & 0x8000) != 0;
let profile_and_level = b01 & 0x7FFF;
let horizontal_size = u32::from_be_bytes([body[2], body[3], body[4], body[5]]);
let vertical_size = u32::from_be_bytes([body[6], body[7], body[8], body[9]]);
let max_bit_rate = u32::from_be_bytes([body[10], body[11], body[12], body[13]]);
let max_buffer_size = u32::from_be_bytes([body[14], body[15], body[16], body[17]]);
let den_frame_rate = u16::from_be_bytes([body[18], body[19]]);
let num_frame_rate = u16::from_be_bytes([body[20], body[21]]);
let pos = 22;
let (
extended_capability,
color_specification,
still_mode,
interlaced_video,
reserved_6,
pos,
) = if extended_capability_flag {
let (ext, still_mode, interlaced_video, reserved_6, new_pos) =
parse_extended_capability(body, pos)?;
(
Some(ext),
None,
still_mode,
interlaced_video,
reserved_6,
new_pos,
)
} else {
if body.len() < pos + 2 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for color_specification/still_mode",
});
}
let cs = body[pos];
let sm_iv = body[pos + 1];
let still_mode = (sm_iv & 0x80) != 0;
let interlaced_video = (sm_iv & 0x40) != 0;
(
None,
Some(cs),
still_mode,
interlaced_video,
sm_iv & RESERVED_6_MASK,
pos + 2,
)
};
let private_data = &body[pos..];
Ok(Self {
extended_capability_flag,
profile_and_level,
horizontal_size,
vertical_size,
max_bit_rate,
max_buffer_size,
den_frame_rate,
num_frame_rate,
extended_capability,
color_specification,
still_mode,
interlaced_video,
private_data,
reserved_6,
})
}
}
impl Serialize for J2kVideoDescriptor<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let mut len: usize = HEADER_LEN + 22; if let Some(ref ext) = self.extended_capability {
len += extended_capability_serialized_len(ext); } else {
len += 2; }
len += self.private_data.len();
len
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
crate::descriptors::write_descriptor_header(buf, TAG, len - HEADER_LEN)?;
let mut b01 =
broadcast_common::len::fit_mask(self.profile_and_level, 0x7FFF, "profile_and_level")?;
if self.extended_capability.is_some() {
b01 |= 0x8000;
}
buf[HEADER_LEN] = (b01 >> 8) as u8;
buf[HEADER_LEN + 1] = b01 as u8;
buf[HEADER_LEN + 2..HEADER_LEN + 6].copy_from_slice(&self.horizontal_size.to_be_bytes());
buf[HEADER_LEN + 6..HEADER_LEN + 10].copy_from_slice(&self.vertical_size.to_be_bytes());
buf[HEADER_LEN + 10..HEADER_LEN + 14].copy_from_slice(&self.max_bit_rate.to_be_bytes());
buf[HEADER_LEN + 14..HEADER_LEN + 18].copy_from_slice(&self.max_buffer_size.to_be_bytes());
buf[HEADER_LEN + 18..HEADER_LEN + 20].copy_from_slice(&self.den_frame_rate.to_be_bytes());
buf[HEADER_LEN + 20..HEADER_LEN + 22].copy_from_slice(&self.num_frame_rate.to_be_bytes());
let mut pos = HEADER_LEN + 22;
if let Some(ref ext) = self.extended_capability {
pos = serialize_extended_capability(
ext,
self.still_mode,
self.interlaced_video,
self.reserved_6,
buf,
pos,
)?;
} else {
let cs = self.color_specification.ok_or(Error::ValueOutOfRange {
field: "color_specification",
reason: "required when extended_capability is absent",
})?;
buf[pos] = cs;
pos += 1;
let mut sm_iv =
broadcast_common::len::fit_mask(self.reserved_6, RESERVED_6_MASK, "reserved_6")?;
if self.still_mode {
sm_iv |= 0x80;
}
if self.interlaced_video {
sm_iv |= 0x40;
}
buf[pos] = sm_iv;
pos += 1;
}
buf[pos..pos + self.private_data.len()].copy_from_slice(self.private_data);
Ok(len)
}
}
impl<'a> crate::traits::DescriptorDef<'a> for J2kVideoDescriptor<'a> {
const TAG: u8 = TAG;
const NAME: &'static str = "J2K_VIDEO";
}
#[cfg(test)]
mod tests {
use super::*;
fn serialize_round_trip(d: &J2kVideoDescriptor<'_>) {
let mut buf = vec![0u8; d.serialized_len()];
let written = d.serialize_into(&mut buf).unwrap();
assert_eq!(written, d.serialized_len());
let reparsed = J2kVideoDescriptor::parse(&buf).unwrap();
assert_eq!(*d, reparsed, "round-trip mismatch");
}
#[test]
fn round_trips_every_case() {
for d in [
J2kVideoDescriptor {
extended_capability_flag: false,
profile_and_level: 0x0102,
horizontal_size: 1920,
vertical_size: 1080,
max_bit_rate: 10_000_000,
max_buffer_size: 8_000_000,
den_frame_rate: 1001,
num_frame_rate: 24000,
extended_capability: None,
color_specification: Some(0x03),
still_mode: false,
interlaced_video: true,
reserved_6: RESERVED_6_DEFAULT,
private_data: &[],
},
J2kVideoDescriptor {
extended_capability_flag: true,
profile_and_level: 0x0307,
horizontal_size: 3840,
vertical_size: 2160,
max_bit_rate: 30_000_000,
max_buffer_size: 15_000_000,
den_frame_rate: 1001,
num_frame_rate: 60000,
extended_capability: Some(J2kExtendedCapability {
stripe_flag: false,
block_flag: false,
mdm_flag: false,
colour_primaries: 1,
transfer_characteristics: 16,
matrix_coefficients: 0,
video_full_range_flag: true,
reserved_5: RESERVED_5_EXTENDED_DEFAULT,
reserved_7: RESERVED_7_DEFAULT,
stripe: None,
block: None,
mdm: None,
}),
color_specification: None,
still_mode: false,
interlaced_video: false,
reserved_6: RESERVED_6_DEFAULT,
private_data: &[],
},
J2kVideoDescriptor {
extended_capability_flag: true,
profile_and_level: 0x0307,
horizontal_size: 3840,
vertical_size: 2160,
max_bit_rate: 30_000_000,
max_buffer_size: 15_000_000,
den_frame_rate: 1,
num_frame_rate: 60,
extended_capability: Some(J2kExtendedCapability {
stripe_flag: true,
block_flag: false,
mdm_flag: false,
colour_primaries: 9,
transfer_characteristics: 14,
matrix_coefficients: 0,
video_full_range_flag: false,
reserved_5: RESERVED_5_EXTENDED_DEFAULT,
reserved_7: RESERVED_7_DEFAULT,
stripe: Some(J2kStripe {
strp_max_idx: 3,
strp_height: 1024,
}),
block: None,
mdm: None,
}),
color_specification: None,
still_mode: true,
interlaced_video: false,
reserved_6: RESERVED_6_DEFAULT,
private_data: &[0xAA],
},
J2kVideoDescriptor {
extended_capability_flag: true,
profile_and_level: 0x0307,
horizontal_size: 1920,
vertical_size: 1080,
max_bit_rate: 20_000_000,
max_buffer_size: 10_000_000,
den_frame_rate: 1001,
num_frame_rate: 30000,
extended_capability: Some(J2kExtendedCapability {
stripe_flag: false,
block_flag: true,
mdm_flag: false,
colour_primaries: 1,
transfer_characteristics: 1,
matrix_coefficients: 1,
video_full_range_flag: true,
reserved_5: RESERVED_5_EXTENDED_DEFAULT,
reserved_7: RESERVED_7_DEFAULT,
stripe: None,
block: Some(J2kBlock {
full_horizontal_size: 3840,
full_vertical_size: 2160,
blk_width: 1920,
blk_height: 1080,
max_blk_idx_h: 1,
max_blk_idx_v: 1,
blk_idx_h: 0,
blk_idx_v: 0,
}),
mdm: None,
}),
color_specification: None,
still_mode: false,
interlaced_video: false,
reserved_6: RESERVED_6_DEFAULT,
private_data: &[],
},
J2kVideoDescriptor {
extended_capability_flag: true,
profile_and_level: 0x0307,
horizontal_size: 1920,
vertical_size: 1080,
max_bit_rate: 15_000_000,
max_buffer_size: 8_000_000,
den_frame_rate: 1,
num_frame_rate: 25,
extended_capability: Some(J2kExtendedCapability {
stripe_flag: false,
block_flag: false,
mdm_flag: true,
colour_primaries: 9,
transfer_characteristics: 16,
matrix_coefficients: 9,
video_full_range_flag: false,
reserved_5: RESERVED_5_EXTENDED_DEFAULT,
reserved_7: RESERVED_7_DEFAULT,
stripe: None,
block: None,
mdm: Some(J2kMdm {
x_c0: 1,
y_c0: 2,
x_c1: 3,
y_c1: 4,
x_c2: 5,
y_c2: 6,
x_wp: 7,
y_wp: 8,
l_max: 1000,
l_min: 5,
max_cll: 800,
max_fall: 400,
}),
}),
color_specification: None,
still_mode: false,
interlaced_video: true,
reserved_6: RESERVED_6_DEFAULT,
private_data: &[],
},
J2kVideoDescriptor {
extended_capability_flag: true,
profile_and_level: 0x0307,
horizontal_size: 7680,
vertical_size: 4320,
max_bit_rate: 100_000_000,
max_buffer_size: 50_000_000,
den_frame_rate: 1001,
num_frame_rate: 60000,
extended_capability: Some(J2kExtendedCapability {
stripe_flag: true,
block_flag: true,
mdm_flag: true,
colour_primaries: 9,
transfer_characteristics: 16,
matrix_coefficients: 9,
video_full_range_flag: true,
reserved_5: RESERVED_5_EXTENDED_DEFAULT,
reserved_7: RESERVED_7_DEFAULT,
stripe: Some(J2kStripe {
strp_max_idx: 7,
strp_height: 540,
}),
block: Some(J2kBlock {
full_horizontal_size: 15360,
full_vertical_size: 8640,
blk_width: 7680,
blk_height: 4320,
max_blk_idx_h: 1,
max_blk_idx_v: 1,
blk_idx_h: 0,
blk_idx_v: 1,
}),
mdm: Some(J2kMdm {
x_c0: 6800,
y_c0: 3200,
x_c1: 2650,
y_c1: 6900,
x_c2: 1500,
y_c2: 600,
x_wp: 3127,
y_wp: 3290,
l_max: 4_000_000,
l_min: 50,
max_cll: 10_000,
max_fall: 5_000,
}),
}),
color_specification: None,
still_mode: true,
interlaced_video: true,
reserved_6: RESERVED_6_DEFAULT,
private_data: &[0xDD, 0xEE, 0xFF],
},
] {
serialize_round_trip(&d);
}
}
#[test]
fn extended_capability_spec_vector_field_by_field() {
#[rustfmt::skip]
let body: &[u8] = &[
0x83, 0x07, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x10, 0xE0, 0x05, 0xF5, 0xE1, 0x00, 0x02, 0xFA, 0xF0, 0x80, 0x03, 0xE9, 0xEA, 0x60, 0xE0, 0xC1, 0x09, 0x10, 0x09, 0xFF, 0x07, 0x02, 0x1C, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x21, 0xC0, 0x1E, 0x00, 0x10, 0xE0, 0x01, 0x02, 0x03, 0x04, 0x1A, 0x90, 0x0C, 0x80, 0x0A, 0x5A, 0x1A, 0xF4, 0x05, 0xDC, 0x02, 0x58, 0x0C, 0x37, 0x0C, 0xDA, 0x00, 0x3D, 0x09, 0x00, 0x00, 0x00, 0x00, 0x32, 0x27, 0x10, 0x13, 0x88, 0xDD, 0xEE, ];
let mut bytes = vec![TAG, body.len() as u8];
bytes.extend_from_slice(body);
let d = J2kVideoDescriptor::parse(&bytes).unwrap();
assert!(d.extended_capability_flag);
assert_eq!(d.profile_and_level, 0x0307);
assert_eq!((d.horizontal_size, d.vertical_size), (7680, 4320));
assert_eq!(
(d.max_bit_rate, d.max_buffer_size),
(100_000_000, 50_000_000)
);
assert_eq!((d.den_frame_rate, d.num_frame_rate), (1001, 60000));
assert!(d.still_mode);
assert!(d.interlaced_video);
assert_eq!(d.reserved_6, 1);
assert_eq!(d.color_specification, None);
let ext = d.extended_capability.as_ref().unwrap();
assert!(ext.stripe_flag && ext.block_flag && ext.mdm_flag);
assert_eq!(
(
ext.colour_primaries,
ext.transfer_characteristics,
ext.matrix_coefficients
),
(9, 16, 9)
);
assert!(ext.video_full_range_flag);
assert_eq!(ext.reserved_7, 0x7F);
let s = ext.stripe.as_ref().unwrap();
assert_eq!((s.strp_max_idx, s.strp_height), (7, 540));
let b = ext.block.as_ref().unwrap();
assert_eq!(b.full_horizontal_size, 15360);
assert_eq!(b.full_vertical_size, 8640);
assert_eq!((b.blk_width, b.blk_height), (7680, 4320));
assert_eq!(
(b.max_blk_idx_h, b.max_blk_idx_v, b.blk_idx_h, b.blk_idx_v),
(1, 2, 3, 4)
);
let m = ext.mdm.as_ref().unwrap();
assert_eq!((m.x_c0, m.y_c0, m.x_c1, m.y_c1), (6800, 3200, 2650, 6900));
assert_eq!((m.x_c2, m.y_c2, m.x_wp, m.y_wp), (1500, 600, 3127, 3290));
assert_eq!((m.l_max, m.l_min), (4_000_000, 50));
assert_eq!((m.max_cll, m.max_fall), (10_000, 5_000));
assert_eq!(d.private_data, &[0xDD, 0xEE]);
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, bytes);
}
#[test]
fn parse_rejects_wrong_tag() {
let buf = [
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,
];
let err = J2kVideoDescriptor::parse(&buf).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { tag: 0x02, .. }));
}
#[test]
fn parse_rejects_too_short() {
let err = J2kVideoDescriptor::parse(&[TAG, 0]).unwrap_err();
assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
}
fn plain<'a>() -> J2kVideoDescriptor<'a> {
J2kVideoDescriptor {
extended_capability_flag: false,
profile_and_level: 0x0102,
horizontal_size: 1920,
vertical_size: 1080,
max_bit_rate: 1,
max_buffer_size: 2,
den_frame_rate: 1001,
num_frame_rate: 24000,
extended_capability: None,
color_specification: Some(0x03),
still_mode: false,
interlaced_video: false,
reserved_6: RESERVED_6_DEFAULT,
private_data: &[],
}
}
#[test]
fn serialize_derives_flags_from_options() {
let mut d = plain();
d.extended_capability_flag = true;
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = J2kVideoDescriptor::parse(&buf).unwrap();
assert!(!re.extended_capability_flag);
assert_eq!(re.color_specification, Some(0x03));
let mut d = plain();
d.color_specification = None;
d.extended_capability = Some(J2kExtendedCapability {
stripe_flag: true,
block_flag: false,
mdm_flag: true,
colour_primaries: 1,
transfer_characteristics: 2,
matrix_coefficients: 3,
video_full_range_flag: false,
reserved_5: RESERVED_5_EXTENDED_DEFAULT,
reserved_7: RESERVED_7_DEFAULT,
stripe: None,
block: Some(J2kBlock {
full_horizontal_size: 1,
full_vertical_size: 2,
blk_width: 3,
blk_height: 4,
max_blk_idx_h: 5,
max_blk_idx_v: 6,
blk_idx_h: 7,
blk_idx_v: 8,
}),
mdm: None,
});
let mut buf = vec![0u8; d.serialized_len()];
d.serialize_into(&mut buf).unwrap();
let re = J2kVideoDescriptor::parse(&buf).unwrap();
assert!(re.extended_capability_flag);
let ext = re.extended_capability.unwrap();
assert!(!ext.stripe_flag && ext.block_flag && !ext.mdm_flag);
assert!(ext.stripe.is_none() && ext.block.is_some() && ext.mdm.is_none());
}
#[test]
fn serialize_rejects_missing_color_specification() {
let mut d = plain();
d.color_specification = None;
let mut buf = vec![0u8; d.serialized_len()];
assert!(matches!(
d.serialize_into(&mut buf),
Err(Error::ValueOutOfRange {
field: "color_specification",
..
})
));
}
#[test]
fn j2k_video_reserved_preserved() {
let head = |ext: bool| {
let mut v = vec![TAG, 0u8];
let b01: u16 = if ext { 0x8000 | 0x0307 } else { 0x0307 };
v.extend_from_slice(&b01.to_be_bytes());
v.extend_from_slice(&1920u32.to_be_bytes());
v.extend_from_slice(&1080u32.to_be_bytes());
v.extend_from_slice(&1u32.to_be_bytes());
v.extend_from_slice(&2u32.to_be_bytes());
v.extend_from_slice(&1001u16.to_be_bytes());
v.extend_from_slice(&24000u16.to_be_bytes());
v
};
for r6 in [0u8, 1, 0x15, 0x2A, 0x3F] {
for r5 in [0u8, 1, 0x0A, 0x1F] {
for r7 in [0u8, 1, 0x55, 0x7F] {
let mut bytes = head(true);
bytes.extend_from_slice(&[r5, 0x80 | r6, 1, 16, 0, 0x80 | r7]);
bytes[1] = (bytes.len() - 2) as u8;
let d = J2kVideoDescriptor::parse(&bytes).unwrap();
assert_eq!(d.reserved_6, r6);
let ext = d.extended_capability.as_ref().unwrap();
assert_eq!((ext.reserved_5, ext.reserved_7), (r5, r7));
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, bytes);
}
}
let mut bytes = head(false);
bytes.extend_from_slice(&[0x03, 0x40 | r6]);
bytes[1] = (bytes.len() - 2) as u8;
let d = J2kVideoDescriptor::parse(&bytes).unwrap();
assert_eq!(d.reserved_6, r6);
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
assert_eq!(out, bytes);
}
}
#[test]
fn j2k_video_default_is_spec_value() {
let mut d = J2kVideoDescriptor::new(0x0307, 1920, 1080);
d.color_specification = None;
d.extended_capability = Some(J2kExtendedCapability::new(1, 16, 0));
let mut out = vec![0u8; d.serialized_len()];
d.serialize_into(&mut out).unwrap();
let tail = &out[out.len() - 6..];
assert_eq!(tail, [0x00, 0x3F, 1, 16, 0, 0x7F]);
let legacy = J2kVideoDescriptor::new(0x0102, 1920, 1080);
let mut out = vec![0u8; legacy.serialized_len()];
legacy.serialize_into(&mut out).unwrap();
assert_eq!(&out[out.len() - 2..], [0x00, 0x3F]);
}
}