use crate::annexb::find_start_code_prefix;
use crate::bitreader::BitReader;
use crate::error::{Error, Result};
use crate::ir::{
AspectRatioIdc, ColourDescription, ColourPrimaries, DisplayGeometry, MatrixCoefficients,
PictureRect, TransferCharacteristics, VideoFormat, VideoSignalType,
};
use crate::mpeg_legacy::SEQUENCE_HEADER_CODE;
pub const SEQUENCE_HEADER_CODE_BYTE: u8 = SEQUENCE_HEADER_CODE[3];
pub const PICTURE_START_CODE_BYTE: u8 = 0x00;
const SLICE_FIRST: u8 = 0x01;
const SLICE_LAST: u8 = 0xAF;
pub const EXTENSION_START_CODE_BYTE: u8 = 0xB5;
pub const USER_DATA_START_CODE_BYTE: u8 = 0xB2;
pub const SEQUENCE_EXTENSION_ID: u8 = 0x1;
pub const SEQUENCE_DISPLAY_EXTENSION_ID: u8 = 0x2;
pub const ASPECT_SQUARE: u8 = 1;
pub const ASPECT_DAR_4_3: u8 = 2;
pub const ASPECT_DAR_16_9: u8 = 3;
pub const ASPECT_DAR_2_21: u8 = 4;
pub const FIRST_RESERVED_ASPECT: u8 = 5;
const SIZE_VALUE_BITS: usize = 12;
const SIZE_EXTENSION_SHIFT: u32 = 12;
const SIZE_EXTENSION_BITS: usize = 2;
const ASPECT_BITS: usize = 4;
const EXTENSION_ID_BITS: usize = 4;
const VIDEO_FORMAT_BITS: usize = 3;
const DISPLAY_SIZE_BITS: usize = 14;
const SEQUENCE_SECTION_MAX: usize = 256;
fn dar_ratio(aspect: u8) -> Option<(u64, u64)> {
match aspect {
ASPECT_DAR_4_3 => Some((4, 3)),
ASPECT_DAR_16_9 => Some((16, 9)),
ASPECT_DAR_2_21 => Some((221, 100)),
_ => None,
}
}
fn gcd(mut a: u64, mut b: u64) -> u64 {
while b != 0 {
(a, b) = (b, a % b);
}
a.max(1)
}
fn derived_sar(dar: (u64, u64), size_w: u32, size_h: u32) -> Option<(u16, u16)> {
let num = dar.0.checked_mul(u64::from(size_h))?;
let den = dar.1.checked_mul(u64::from(size_w))?;
if num == 0 || den == 0 {
return None;
}
let g = gcd(num, den);
let (w, h) = (num / g, den / g);
if w > u64::from(u16::MAX) || h > u64::from(u16::MAX) {
return None;
}
Some((u16::try_from(w).ok()?, u16::try_from(h).ok()?))
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SequenceHeaderGeometry {
pub coded_width: u32,
pub coded_height: u32,
pub aspect_ratio_information: u8,
pub display: Option<SequenceDisplayExtension>,
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SequenceDisplayExtension {
pub video_format: u8,
pub colour_description: Option<(u8, u8, u8)>,
pub display_horizontal_size: u32,
pub display_vertical_size: u32,
}
impl SequenceHeaderGeometry {
pub fn sample_aspect_ratio(&self) -> Option<(u16, u16)> {
match self.aspect_ratio_information {
ASPECT_SQUARE => Some((1, 1)),
aspect => {
let (size_w, size_h) = match &self.display {
Some(d) => (d.display_horizontal_size, d.display_vertical_size),
None => (self.coded_width, self.coded_height),
};
derived_sar(dar_ratio(aspect)?, size_w, size_h)
}
}
}
pub fn to_display_geometry(&self) -> DisplayGeometry {
let video_signal = self.display.as_ref().map(|d| VideoSignalType {
video_format: VideoFormat::from_wire(d.video_format)
.unwrap_or(VideoFormat::Unspecified),
full_range: false,
colour: d.colour_description.map(|(p, t, m)| ColourDescription {
primaries: ColourPrimaries::from_wire(p),
transfer: TransferCharacteristics::from_wire(t),
matrix: MatrixCoefficients::from_wire(m),
}),
});
DisplayGeometry {
coded_width: self.coded_width,
coded_height: self.coded_height,
output_rect: PictureRect::new(0, 0, self.coded_width, self.coded_height),
default_display_rect: None,
aspect_ratio: match (self.aspect_ratio_information, self.sample_aspect_ratio()) {
(ASPECT_SQUARE, _) => Some(AspectRatioIdc::Square),
(_, Some((sar_width, sar_height))) => Some(AspectRatioIdc::ExtendedSar {
sar_width,
sar_height,
}),
_ => None,
},
overscan_appropriate: None,
video_signal,
mpeg2: true,
}
}
}
fn find_any_start_code(data: &[u8], from: usize) -> Option<(usize, u8)> {
let at = find_start_code_prefix(data, from)?;
Some((at, *data.get(at + 3)?))
}
pub(crate) fn section_body(data: &[u8], start: usize) -> &[u8] {
let from = start + SEQUENCE_HEADER_CODE.len();
let end = match find_start_code_prefix(data, from) {
Some(next) => next,
None => data.len(),
};
data.get(from..end).unwrap_or_default()
}
fn parse_sequence_header_body(body: &[u8]) -> Result<(u32, u32, u8)> {
let mut r = BitReader::from_rbsp(body, "MPEG-2 sequence_header")?;
let width = r.read_bits(SIZE_VALUE_BITS, "horizontal_size_value")?;
let height = r.read_bits(SIZE_VALUE_BITS, "vertical_size_value")?;
let aspect = r.read_bits(ASPECT_BITS, "aspect_ratio_information")?;
Ok((
u32::try_from(width).unwrap_or(u32::MAX),
u32::try_from(height).unwrap_or(u32::MAX),
u8::try_from(aspect).unwrap_or(u8::MAX),
))
}
fn parse_sequence_extension_body(body: &[u8]) -> Result<(u32, u32)> {
let mut r = BitReader::from_rbsp(body, "MPEG-2 sequence_extension")?;
let id = r.read_bits(EXTENSION_ID_BITS, "extension_start_code_identifier")?;
debug_assert_eq!(u8::try_from(id).unwrap_or(u8::MAX), SEQUENCE_EXTENSION_ID);
let _ = r.read_bits(8, "profile_and_level_indication")?;
let _ = r.read_flag("progressive_sequence")?;
let _ = r.read_bits(2, "chroma_format")?;
let hx = r.read_bits(SIZE_EXTENSION_BITS, "horizontal_size_extension")?;
let vx = r.read_bits(SIZE_EXTENSION_BITS, "vertical_size_extension")?;
Ok((
u32::try_from(hx).unwrap_or(0),
u32::try_from(vx).unwrap_or(0),
))
}
fn parse_sequence_display_extension_body(body: &[u8]) -> Result<SequenceDisplayExtension> {
let mut r = BitReader::from_rbsp(body, "MPEG-2 sequence_display_extension")?;
let id = r.read_bits(EXTENSION_ID_BITS, "extension_start_code_identifier")?;
debug_assert_eq!(
u8::try_from(id).unwrap_or(u8::MAX),
SEQUENCE_DISPLAY_EXTENSION_ID
);
let video_format = r.read_bits(VIDEO_FORMAT_BITS, "video_format")?;
let colour = r.read_flag("colour_description")?;
let colour_description = if colour {
let p = r.read_bits(8, "colour_primaries")?;
let t = r.read_bits(8, "transfer_characteristics")?;
let m = r.read_bits(8, "matrix_coefficients")?;
Some((
u8::try_from(p).unwrap_or(u8::MAX),
u8::try_from(t).unwrap_or(u8::MAX),
u8::try_from(m).unwrap_or(u8::MAX),
))
} else {
None
};
let h = r.read_bits(DISPLAY_SIZE_BITS, "display_horizontal_size")?;
let _ = r.read_flag("marker_bit")?;
let v = r.read_bits(DISPLAY_SIZE_BITS, "display_vertical_size")?;
Ok(SequenceDisplayExtension {
video_format: u8::try_from(video_format).unwrap_or(u8::MAX),
colour_description,
display_horizontal_size: u32::try_from(h).unwrap_or(u32::MAX),
display_vertical_size: u32::try_from(v).unwrap_or(u32::MAX),
})
}
pub(crate) fn parse_sequence_at(es: &[u8], start: usize) -> Result<SequenceHeaderGeometry> {
let body_end = section_body(es, start).len();
let header_end = start + SEQUENCE_HEADER_CODE.len() + body_end;
let (width, height, aspect) =
parse_sequence_header_body(&es[start + SEQUENCE_HEADER_CODE.len()..header_end])?;
let mut geometry = SequenceHeaderGeometry {
coded_width: width,
coded_height: height,
aspect_ratio_information: aspect,
display: None,
};
let scan_end = start.saturating_add(SEQUENCE_SECTION_MAX);
let mut from = header_end;
while from < es.len() && from < scan_end {
let Some((at, code)) = find_any_start_code(es, from) else {
break;
};
if code != EXTENSION_START_CODE_BYTE || at >= scan_end {
break;
}
let body = section_body(es, at);
let Some(&id) = body.first() else {
break;
};
match id >> (8 - EXTENSION_ID_BITS) {
SEQUENCE_EXTENSION_ID => {
if let Ok((hx, vx)) = parse_sequence_extension_body(body) {
geometry.coded_width |= hx << SIZE_EXTENSION_SHIFT;
geometry.coded_height |= vx << SIZE_EXTENSION_SHIFT;
}
}
SEQUENCE_DISPLAY_EXTENSION_ID if geometry.display.is_none() => {
geometry.display = parse_sequence_display_extension_body(body).ok();
}
_ => {}
}
from = at + SEQUENCE_HEADER_CODE.len() + body.len().max(1);
}
Ok(geometry)
}
pub(crate) fn find_sequence_header(es: &[u8]) -> Option<usize> {
let mut from = 0usize;
while let Some((at, code)) = find_any_start_code(es, from) {
if code == SEQUENCE_HEADER_CODE_BYTE {
return Some(at);
}
from = at + 1;
}
None
}
pub(crate) fn afd_from_mpeg2_user_data(
payload: &[u8],
) -> Option<Option<crate::ir::ActiveFormatDescription>> {
let first = *payload.first()?;
let active_format_flag = first & crate::user_data::AFD_ACTIVE_FORMAT_FLAG_BIT != 0;
if !active_format_flag {
return Some(None);
}
let second = *payload.get(1)?;
Some(Some(crate::ir::ActiveFormatDescription::from_code(
second & crate::user_data::AFD_CODE_MASK,
)))
}
pub(crate) fn picture_user_data_payloads(au: &[u8]) -> (bool, alloc::vec::Vec<&[u8]>) {
let mut has_sequence_header = false;
let mut picture_seen = false;
let mut payloads = alloc::vec::Vec::new();
let mut from = 0usize;
while let Some((at, code)) = find_any_start_code(au, from) {
match code {
SEQUENCE_HEADER_CODE_BYTE => has_sequence_header = true,
PICTURE_START_CODE_BYTE | SLICE_FIRST..=SLICE_LAST => picture_seen = true,
USER_DATA_START_CODE_BYTE if picture_seen => {
payloads.push(section_body(au, at));
}
_ => {}
}
from = at + SEQUENCE_HEADER_CODE.len();
}
(has_sequence_header, payloads)
}
impl DisplayGeometry {
pub fn from_mpeg2_es(prefix: &[u8]) -> Result<Self> {
let start = find_sequence_header(prefix).ok_or(Error::InvalidInput(
"no MPEG-2 sequence_header (start code 0x000001B3) in elementary stream",
))?;
let sequence = parse_sequence_at(prefix, start)?;
Ok(sequence.to_display_geometry())
}
}
#[cfg(test)]
mod tests;