use super::descriptor_body;
use crate::error::{Error, Result};
use dvb_common::{Parse, Serialize};
pub const TAG: u8 = 0x32;
const HEADER_LEN: usize = 2;
#[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))]
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>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
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],
}
fn parse_extended_capability(
body: &[u8],
mut pos: usize,
) -> Result<(J2kExtendedCapability, 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;
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;
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,
},
pos,
))
}
fn serialize_extended_capability(
ext: &J2kExtendedCapability,
buf: &mut [u8],
mut pos: usize,
) -> usize {
let mut sb = 0u8;
if ext.stripe_flag {
sb |= 0x80;
}
if ext.block_flag {
sb |= 0x40;
}
if ext.mdm_flag {
sb |= 0x20;
}
buf[pos] = sb;
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
};
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;
}
pos
}
fn extended_capability_serialized_len(ext: &J2kExtendedCapability) -> usize {
let mut len: usize = 5; 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 mut pos = 22;
let (extended_capability, color_specification, mut pos) = if extended_capability_flag {
let (ext, new_pos) = parse_extended_capability(body, pos)?;
(Some(ext), None, new_pos)
} else {
if body.len() < pos + 1 {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "J2K_video_descriptor too short for color_specification",
});
}
let cs = body[pos];
pos += 1;
(None, Some(cs), pos)
};
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;
pos += 1;
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,
})
}
}
impl Serialize for J2kVideoDescriptor<'_> {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
let mut len: usize = HEADER_LEN + 23; if let Some(ref ext) = self.extended_capability {
len += extended_capability_serialized_len(ext);
} else {
len += 1; }
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(),
});
}
buf[0] = TAG;
buf[1] = (len - HEADER_LEN) as u8;
let mut b01 = self.profile_and_level & 0x7FFF;
if self.extended_capability_flag {
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, buf, pos);
} else if let Some(cs) = self.color_specification {
buf[pos] = cs;
pos += 1;
}
let mut sm_iv = 0u8;
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_trip_no_extended_capability() {
let d = 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,
private_data: &[],
};
serialize_round_trip(&d);
}
#[test]
fn round_trip_extended_capability_basic() {
let d = 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,
stripe: None,
block: None,
mdm: None,
}),
color_specification: None,
still_mode: false,
interlaced_video: false,
private_data: &[],
};
serialize_round_trip(&d);
}
#[test]
fn round_trip_extended_capability_stripe_only() {
let d = 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,
stripe: Some(J2kStripe {
strp_max_idx: 3,
strp_height: 1024,
}),
block: None,
mdm: None,
}),
color_specification: None,
still_mode: true,
interlaced_video: false,
private_data: &[0xAA],
};
serialize_round_trip(&d);
}
#[test]
fn round_trip_extended_capability_block_only() {
let d = 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,
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,
private_data: &[],
};
serialize_round_trip(&d);
}
#[test]
fn round_trip_extended_capability_mdm_only() {
let d = 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,
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,
private_data: &[],
};
serialize_round_trip(&d);
}
#[test]
fn round_trip_extended_capability_all() {
let d = 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,
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,
private_data: &[0xDD, 0xEE, 0xFF],
};
serialize_round_trip(&d);
}
#[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, .. }));
}
}