use crate::annexb::iter_length_prefixed_nals_with;
use crate::bitreader::BitReader;
use crate::hevc_config::HEVCDecoderConfigurationRecord;
use crate::ir::CodecConfig;
use crate::nal::sei_messages;
use crate::slice_header::{AvcParamSets, AvcSliceHeader};
use crate::sps::{
AvcPicTimingLayout, AvcSliceSps, decode_avc_slice_sps, decode_hevc_interlace_sps,
};
const AVC_NAL_TYPE_MASK: u8 = 0x1F;
const AVC_NAL_SLICE: u8 = 1;
const AVC_NAL_IDR: u8 = 5;
const AVC_NAL_SEI: u8 = 6;
const AVC_NAL_SPS: u8 = 7;
const AVC_NAL_PPS: u8 = 8;
const AVC_NAL_HEADER_LEN: usize = 1;
const HEVC_NAL_TYPE_SHIFT: u8 = 1;
const HEVC_NAL_TYPE_MASK: u8 = 0x3F;
const HEVC_NAL_SPS: u8 = 33;
const HEVC_NAL_PREFIX_SEI: u8 = 39;
const HEVC_NAL_FIRST_NON_VCL: u8 = 32;
const HEVC_NAL_HEADER_LEN: usize = 2;
const SEI_PAYLOAD_PIC_TIMING: u32 = 1;
const PIC_STRUCT_BITS: usize = 4;
const SOURCE_SCAN_TYPE_BITS: usize = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum VideoScan {
Progressive,
Paff,
Mbaff,
FieldSequence,
}
impl VideoScan {
pub fn name(&self) -> &'static str {
match self {
VideoScan::Progressive => "progressive",
VideoScan::Paff => "PAFF",
VideoScan::Mbaff => "MBAFF",
VideoScan::FieldSequence => "field_seq",
}
}
}
broadcast_common::impl_spec_display!(VideoScan);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum PicStruct {
Frame,
TopField,
BottomField,
TopBottom,
BottomTop,
TopBottomTop,
BottomTopBottom,
FrameDoubling,
FrameTripling,
TopPairedWithPreviousBottom,
BottomPairedWithPreviousTop,
TopPairedWithNextBottom,
BottomPairedWithNextTop,
}
impl PicStruct {
pub fn from_code(code: u8, hevc: bool) -> Option<Self> {
Some(match code {
0 => PicStruct::Frame,
1 => PicStruct::TopField,
2 => PicStruct::BottomField,
3 => PicStruct::TopBottom,
4 => PicStruct::BottomTop,
5 => PicStruct::TopBottomTop,
6 => PicStruct::BottomTopBottom,
7 => PicStruct::FrameDoubling,
8 => PicStruct::FrameTripling,
9 if hevc => PicStruct::TopPairedWithPreviousBottom,
10 if hevc => PicStruct::BottomPairedWithPreviousTop,
11 if hevc => PicStruct::TopPairedWithNextBottom,
12 if hevc => PicStruct::BottomPairedWithNextTop,
_ => return None,
})
}
pub fn code(&self) -> u8 {
match self {
PicStruct::Frame => 0,
PicStruct::TopField => 1,
PicStruct::BottomField => 2,
PicStruct::TopBottom => 3,
PicStruct::BottomTop => 4,
PicStruct::TopBottomTop => 5,
PicStruct::BottomTopBottom => 6,
PicStruct::FrameDoubling => 7,
PicStruct::FrameTripling => 8,
PicStruct::TopPairedWithPreviousBottom => 9,
PicStruct::BottomPairedWithPreviousTop => 10,
PicStruct::TopPairedWithNextBottom => 11,
PicStruct::BottomPairedWithNextTop => 12,
}
}
pub fn name(&self) -> &'static str {
match self {
PicStruct::Frame => "frame",
PicStruct::TopField => "top field",
PicStruct::BottomField => "bottom field",
PicStruct::TopBottom => "top, bottom",
PicStruct::BottomTop => "bottom, top",
PicStruct::TopBottomTop => "top, bottom, top repeated",
PicStruct::BottomTopBottom => "bottom, top, bottom repeated",
PicStruct::FrameDoubling => "frame doubling",
PicStruct::FrameTripling => "frame tripling",
PicStruct::TopPairedWithPreviousBottom => "top paired with previous bottom",
PicStruct::BottomPairedWithPreviousTop => "bottom paired with previous top",
PicStruct::TopPairedWithNextBottom => "top paired with next bottom",
PicStruct::BottomPairedWithNextTop => "bottom paired with next top",
}
}
fn bottom_first(&self) -> Option<bool> {
match self {
PicStruct::TopField
| PicStruct::TopBottom
| PicStruct::TopBottomTop
| PicStruct::TopPairedWithPreviousBottom
| PicStruct::TopPairedWithNextBottom => Some(false),
PicStruct::BottomField
| PicStruct::BottomTop
| PicStruct::BottomTopBottom
| PicStruct::BottomPairedWithPreviousTop
| PicStruct::BottomPairedWithNextTop => Some(true),
PicStruct::Frame | PicStruct::FrameDoubling | PicStruct::FrameTripling => None,
}
}
}
broadcast_common::impl_spec_display!(PicStruct);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum SourceScanType {
Interlaced,
Progressive,
Unknown,
}
impl SourceScanType {
fn from_code(code: u8) -> Self {
match code {
0 => SourceScanType::Interlaced,
1 => SourceScanType::Progressive,
_ => SourceScanType::Unknown,
}
}
pub fn name(&self) -> &'static str {
match self {
SourceScanType::Interlaced => "interlaced",
SourceScanType::Progressive => "progressive",
SourceScanType::Unknown => "unknown",
}
}
}
broadcast_common::impl_spec_display!(SourceScanType);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct PictureFacts {
pub field_pic: bool,
pub fields: u8,
pub bottom_field_first: Option<bool>,
pub pic_struct: Option<PicStruct>,
pub source_scan_type: Option<SourceScanType>,
}
impl CodecConfig {
pub fn video_scan(&self) -> Option<VideoScan> {
video_scan(self)
}
}
fn hevc_config_sps(rec: &HEVCDecoderConfigurationRecord) -> Option<&[u8]> {
rec.arrays
.iter()
.filter(|a| a.nal_unit_type == HEVC_NAL_SPS)
.flat_map(|a| a.nalus.iter())
.next()
.map(|n| n.0.as_slice())
}
fn first_sps(config: &CodecConfig) -> Option<&[u8]> {
match config {
CodecConfig::Avc { config, .. } => config.config.sps.first().map(|s| s.0.as_slice()),
CodecConfig::Hevc { config, .. } => hevc_config_sps(&config.config),
_ => None,
}
}
pub fn video_scan(config: &CodecConfig) -> Option<VideoScan> {
let sps = first_sps(config)?;
match config {
CodecConfig::Avc { .. } => {
let s = decode_avc_slice_sps(sps).ok()?;
Some(if s.frame_mbs_only {
VideoScan::Progressive
} else if s.mb_adaptive_frame_field {
VideoScan::Mbaff
} else {
VideoScan::Paff
})
}
CodecConfig::Hevc { .. } => {
let s = decode_hevc_interlace_sps(sps).ok()?;
Some(if s.field_seq {
VideoScan::FieldSequence
} else {
VideoScan::Progressive
})
}
_ => None,
}
}
pub fn video_picture_facts(config: &CodecConfig, sample: &[u8]) -> Option<PictureFacts> {
match config {
CodecConfig::Avc { config, .. } => {
let rec = &config.config;
let nals =
iter_length_prefixed_nals_with(sample, usize::from(rec.length_size_minus_one) + 1)
.ok()?;
let mut ps = AvcParamSets::new();
for sps in &rec.sps {
ps.observe_nal(&sps.0);
}
for pps in &rec.pps {
ps.observe_nal(&pps.0);
}
for nal in &nals {
if matches!(
nal.first().map(|b| b & AVC_NAL_TYPE_MASK),
Some(AVC_NAL_SPS | AVC_NAL_PPS)
) {
ps.observe_nal(nal);
}
}
avc_facts(&ps, &nals)
}
CodecConfig::Hevc { config, .. } => {
let rec = &config.config;
let nals =
iter_length_prefixed_nals_with(sample, usize::from(rec.length_size_minus_one) + 1)
.ok()?;
let mut sps_nal = hevc_config_sps(rec)?;
for nal in &nals {
if hevc_type(nal) == Some(HEVC_NAL_SPS) {
sps_nal = nal;
}
}
hevc_facts(sps_nal, &nals)
}
_ => None,
}
}
fn hevc_type(nal: &[u8]) -> Option<u8> {
nal.first()
.map(|b| (b >> HEVC_NAL_TYPE_SHIFT) & HEVC_NAL_TYPE_MASK)
}
fn avc_pic_struct(payload: &[u8], layout: AvcPicTimingLayout) -> Option<PicStruct> {
let mut r = BitReader::from_rbsp(payload, "H.264 pic_timing").ok()?;
if let Some((cpb, dpb)) = layout.delay_bits {
r.read_bits(usize::from(cpb), "cpb_removal_delay").ok()?;
r.read_bits(usize::from(dpb), "dpb_output_delay").ok()?;
}
let code = r.read_bits(PIC_STRUCT_BITS, "pic_struct").ok()?;
PicStruct::from_code(code as u8, false)
}
pub(crate) fn avc_facts(ps: &AvcParamSets, nals: &[&[u8]]) -> Option<PictureFacts> {
let mut first: Option<(AvcSliceHeader, AvcSliceSps)> = None;
let mut second_field = false;
for nal in nals {
if !matches!(
nal.first().map(|b| b & AVC_NAL_TYPE_MASK),
Some(AVC_NAL_SLICE | AVC_NAL_IDR)
) {
continue;
}
let Some((h, sps)) = ps.parse_slice(nal) else {
continue;
};
match &first {
None => first = Some((h, sps)),
Some((f, _)) => {
if f.field_pic_flag
&& h.field_pic_flag
&& f.bottom_field_flag != h.bottom_field_flag
{
second_field = true;
}
}
}
}
let (head, sps) = first?;
let pic_struct = sps.pic_timing.and_then(|layout| {
nals.iter()
.filter(|n| n.first().map(|b| b & AVC_NAL_TYPE_MASK) == Some(AVC_NAL_SEI))
.flat_map(|n| sei_messages(&n[AVC_NAL_HEADER_LEN..]))
.find(|(t, _)| *t == SEI_PAYLOAD_PIC_TIMING)
.and_then(|(_, p)| avc_pic_struct(&p, layout))
});
let bottom_field_first = if head.field_pic_flag {
Some(head.bottom_field_flag)
} else {
pic_struct.and_then(|p| p.bottom_first())
};
Some(PictureFacts {
field_pic: head.field_pic_flag,
fields: match (head.field_pic_flag, second_field) {
(false, _) => 0,
(true, false) => 1,
(true, true) => 2,
},
bottom_field_first,
pic_struct,
source_scan_type: None,
})
}
pub(crate) fn hevc_facts(sps_nal: &[u8], nals: &[&[u8]]) -> Option<PictureFacts> {
let sps = decode_hevc_interlace_sps(sps_nal).ok()?;
if !nals
.iter()
.any(|n| hevc_type(n).is_some_and(|t| t < HEVC_NAL_FIRST_NON_VCL))
{
return None;
}
let mut pic_struct = None;
let mut source_scan_type = None;
if sps.frame_field_info_present {
let payload = nals
.iter()
.filter(|n| hevc_type(n) == Some(HEVC_NAL_PREFIX_SEI) && n.len() > HEVC_NAL_HEADER_LEN)
.flat_map(|n| sei_messages(&n[HEVC_NAL_HEADER_LEN..]))
.find(|(t, _)| *t == SEI_PAYLOAD_PIC_TIMING);
if let Some((_, p)) = payload
&& let Ok(mut r) = BitReader::from_rbsp(&p, "H.265 pic_timing")
{
pic_struct = r
.read_bits(PIC_STRUCT_BITS, "pic_struct")
.ok()
.and_then(|c| PicStruct::from_code(c as u8, true));
source_scan_type = r
.read_bits(SOURCE_SCAN_TYPE_BITS, "source_scan_type")
.ok()
.map(|c| SourceScanType::from_code(c as u8));
}
}
Some(PictureFacts {
field_pic: sps.field_seq,
fields: u8::from(sps.field_seq),
bottom_field_first: pic_struct.and_then(|p| p.bottom_first()),
pic_struct,
source_scan_type,
})
}