From af262db01163d6c6bdac3dbfab961052648965c1 Mon Sep 17 00:00:00 2001 From: Deepnarayan Sett Date: Sun, 17 Aug 2025 11:33:44 +0530 Subject: [PATCH 1/6] AVC Module: ported AVC Module to Rust --- src/lib_ccx/avc_functions.c | 7 + src/rust/build.rs | 4 + src/rust/lib_ccxr/src/common/constants.rs | 1 + src/rust/lib_ccxr/src/encoder/txt_helpers.rs | 4 +- src/rust/src/avc/common_types.rs | 312 ++++++++ src/rust/src/avc/core.rs | 505 +++++++++++++ src/rust/src/avc/mod.rs | 82 ++ src/rust/src/avc/nal.rs | 642 ++++++++++++++++ src/rust/src/avc/sei.rs | 334 +++++++++ src/rust/src/decoder/mod.rs | 7 +- src/rust/src/decoder/service_decoder.rs | 33 +- src/rust/src/lib.rs | 739 ++++++++++--------- 12 files changed, 2290 insertions(+), 380 deletions(-) create mode 100644 src/rust/src/avc/common_types.rs create mode 100644 src/rust/src/avc/core.rs create mode 100644 src/rust/src/avc/mod.rs create mode 100644 src/rust/src/avc/nal.rs create mode 100644 src/rust/src/avc/sei.rs diff --git a/src/lib_ccx/avc_functions.c b/src/lib_ccx/avc_functions.c index e59d206e9..b7e489b35 100644 --- a/src/lib_ccx/avc_functions.c +++ b/src/lib_ccx/avc_functions.c @@ -147,8 +147,14 @@ void do_NAL(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, unsigned // Process inbuf bytes in buffer holding and AVC (H.264) video stream. // The number of processed bytes is returned. +#ifndef DISABLE_RUST +size_t ccxr_process_avc(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, unsigned char *avcbuf, size_t avcbuflen, struct cc_subtitle *sub); +#endif size_t process_avc(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, unsigned char *avcbuf, size_t avcbuflen, struct cc_subtitle *sub) { +#ifndef DISABLE_RUST + return ccxr_process_avc(enc_ctx, dec_ctx, avcbuf, avcbuflen, sub); +#else unsigned char *buffer_position = avcbuf; unsigned char *NAL_start; unsigned char *NAL_stop; @@ -250,6 +256,7 @@ size_t process_avc(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, u } return avcbuflen; +#endif } #define ZEROBYTES_SHORTSTARTCODE 2 diff --git a/src/rust/build.rs b/src/rust/build.rs index a2005dd65..6ba9d6252 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -13,6 +13,10 @@ fn main() { "version", "set_binary_mode", "net_send_header", // shall be removed after NET + "realloc", + "anchor_hdcc", + "process_hdcc", + "store_hdcc", "write_spumux_footer", "write_spumux_header", ]); diff --git a/src/rust/lib_ccxr/src/common/constants.rs b/src/rust/lib_ccxr/src/common/constants.rs index f6d865cbe..9eb15d665 100644 --- a/src/rust/lib_ccxr/src/common/constants.rs +++ b/src/rust/lib_ccxr/src/common/constants.rs @@ -155,6 +155,7 @@ pub const UTF8_MAX_BYTES: usize = 6; pub const XMLRPC_CHUNK_SIZE: usize = 64 * 1024; // 64 Kb per chunk, to avoid too many realloc() // AVC NAL types +#[derive(Debug, Eq, PartialEq)] pub enum AvcNalType { Unspecified0 = 0, CodedSliceNonIdrPicture1 = 1, diff --git a/src/rust/lib_ccxr/src/encoder/txt_helpers.rs b/src/rust/lib_ccxr/src/encoder/txt_helpers.rs index a1986b9ee..8b2af4ff8 100644 --- a/src/rust/lib_ccxr/src/encoder/txt_helpers.rs +++ b/src/rust/lib_ccxr/src/encoder/txt_helpers.rs @@ -282,10 +282,10 @@ pub fn get_str_basic( info!("WARNING: Encoding is not yet supported\n"); out_buffer.clear(); out_buffer.push(0); - return 0; + 0 } else { out_buffer.push(0); - return len; + len } } diff --git a/src/rust/src/avc/common_types.rs b/src/rust/src/avc/common_types.rs new file mode 100644 index 000000000..fea08cfe2 --- /dev/null +++ b/src/rust/src/avc/common_types.rs @@ -0,0 +1,312 @@ +use crate::avc::FromCType; +use crate::bindings::*; +use crate::common::CType; + +pub const ZEROBYTES_SHORTSTARTCODE: i32 = 2; +pub const AVC_CC_DATA_INITIAL_SIZE: usize = 1024; +pub const MAXBFRAMES: i32 = 50; +// NAL unit types from H.264 standard +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum NalUnitType { + Unspecified = 0, + CodedSliceNonIdr = 1, + CodedSliceDataPartitionA = 2, + CodedSliceDataPartitionB = 3, + CodedSliceDataPartitionC = 4, + CodedSliceIdr = 5, + Sei = 6, + SequenceParameterSet = 7, + PictureParameterSet = 8, + AccessUnitDelimiter = 9, + EndOfSequence = 10, + EndOfStream = 11, + FillerData = 12, + SequenceParameterSetExtension = 13, + PrefixNalUnit = 14, + SubsetSequenceParameterSet = 15, + DepthParameterSet = 16, + // 17-18 reserved + CodedSliceAuxiliary = 19, + CodedSliceExtension = 20, + // 21-23 reserved + // 24-31 unspecified +} + +impl From for NalUnitType { + fn from(value: u8) -> Self { + match value { + 0 => NalUnitType::Unspecified, + 1 => NalUnitType::CodedSliceNonIdr, + 2 => NalUnitType::CodedSliceDataPartitionA, + 3 => NalUnitType::CodedSliceDataPartitionB, + 4 => NalUnitType::CodedSliceDataPartitionC, + 5 => NalUnitType::CodedSliceIdr, + 6 => NalUnitType::Sei, + 7 => NalUnitType::SequenceParameterSet, + 8 => NalUnitType::PictureParameterSet, + 9 => NalUnitType::AccessUnitDelimiter, + 10 => NalUnitType::EndOfSequence, + 11 => NalUnitType::EndOfStream, + 12 => NalUnitType::FillerData, + 13 => NalUnitType::SequenceParameterSetExtension, + 14 => NalUnitType::PrefixNalUnit, + 15 => NalUnitType::SubsetSequenceParameterSet, + 16 => NalUnitType::DepthParameterSet, + 19 => NalUnitType::CodedSliceAuxiliary, + 20 => NalUnitType::CodedSliceExtension, + _ => NalUnitType::Unspecified, + } + } +} + +// Rustified version of the avc_ctx struct +#[derive(Debug, Clone)] +pub struct AvcContextRust { + pub cc_count: u8, + pub cc_data: Vec, + pub cc_databufsize: usize, + pub cc_buffer_saved: bool, + + pub got_seq_para: bool, + pub nal_ref_idc: u32, + pub seq_parameter_set_id: i64, + pub log2_max_frame_num: i32, + pub pic_order_cnt_type: i32, + pub log2_max_pic_order_cnt_lsb: i32, + pub frame_mbs_only_flag: bool, + + // Statistics for debugging + pub num_nal_unit_type_7: i64, + pub num_vcl_hrd: i64, + pub num_nal_hrd: i64, + pub num_jump_in_frames: i64, + pub num_unexpected_sei_length: i64, + + pub ccblocks_in_avc_total: i32, + pub ccblocks_in_avc_lost: i32, + + pub frame_num: i64, + pub lastframe_num: i64, + pub currref: i32, + pub maxidx: i32, + pub lastmaxidx: i32, + + // Used to find tref zero in PTS mode + pub minidx: i32, + pub lastminidx: i32, + + // Used to remember the max temporal reference number (poc mode) + pub maxtref: i32, + pub last_gop_maxtref: i32, + + // Used for PTS ordering of CC blocks + pub currefpts: i64, + pub last_pic_order_cnt_lsb: i64, + pub last_slice_pts: i64, +} + +impl Default for AvcContextRust { + fn default() -> Self { + AvcContextRust { + cc_count: 0, + cc_data: Vec::with_capacity(1024), + cc_databufsize: 1024, + cc_buffer_saved: true, + + got_seq_para: false, + nal_ref_idc: 0, + seq_parameter_set_id: 0, + log2_max_frame_num: 0, + pic_order_cnt_type: 0, + log2_max_pic_order_cnt_lsb: 0, + frame_mbs_only_flag: false, + + num_nal_unit_type_7: 0, + num_vcl_hrd: 0, + num_nal_hrd: 0, + num_jump_in_frames: 0, + num_unexpected_sei_length: 0, + + ccblocks_in_avc_total: 0, + ccblocks_in_avc_lost: 0, + + frame_num: -1, + lastframe_num: -1, + currref: 0, + maxidx: -1, + lastmaxidx: -1, + + minidx: 10000, + lastminidx: 10000, + + maxtref: 0, + last_gop_maxtref: 0, + + currefpts: 0, + last_pic_order_cnt_lsb: -1, + last_slice_pts: -1, + } + } +} + +impl FromCType for AvcContextRust { + unsafe fn from_ctype(ctx: avc_ctx) -> Option { + // Convert cc_data from C pointer to Vec + let cc_data = if !ctx.cc_data.is_null() && ctx.cc_databufsize > 0 { + std::slice::from_raw_parts(ctx.cc_data, ctx.cc_databufsize as usize).to_vec() + } else { + Vec::with_capacity(1024) + }; + + Some(AvcContextRust { + cc_count: ctx.cc_count, + cc_data, + cc_databufsize: ctx.cc_databufsize as usize, + cc_buffer_saved: ctx.cc_buffer_saved != 0, + + got_seq_para: ctx.got_seq_para != 0, + nal_ref_idc: ctx.nal_ref_idc, + seq_parameter_set_id: ctx.seq_parameter_set_id, + log2_max_frame_num: ctx.log2_max_frame_num, + pic_order_cnt_type: ctx.pic_order_cnt_type, + log2_max_pic_order_cnt_lsb: ctx.log2_max_pic_order_cnt_lsb, + frame_mbs_only_flag: ctx.frame_mbs_only_flag != 0, + + num_nal_unit_type_7: ctx.num_nal_unit_type_7, + num_vcl_hrd: ctx.num_vcl_hrd, + num_nal_hrd: ctx.num_nal_hrd, + num_jump_in_frames: ctx.num_jump_in_frames, + num_unexpected_sei_length: ctx.num_unexpected_sei_length, + + ccblocks_in_avc_total: ctx.ccblocks_in_avc_total, + ccblocks_in_avc_lost: ctx.ccblocks_in_avc_lost, + + frame_num: ctx.frame_num, + lastframe_num: ctx.lastframe_num, + currref: ctx.currref, + maxidx: ctx.maxidx, + lastmaxidx: ctx.lastmaxidx, + + minidx: ctx.minidx, + lastminidx: ctx.lastminidx, + + maxtref: ctx.maxtref, + last_gop_maxtref: ctx.last_gop_maxtref, + + currefpts: ctx.currefpts, + last_pic_order_cnt_lsb: ctx.last_pic_order_cnt_lsb, + last_slice_pts: ctx.last_slice_pts, + }) + } +} + +impl CType for AvcContextRust { + unsafe fn to_ctype(&self) -> avc_ctx { + // Allocate cc_data buffer + let cc_data_ptr = if !self.cc_data.is_empty() { + let data_box = self.cc_data.clone().into_boxed_slice(); + Box::into_raw(data_box) as *mut u8 + } else { + std::ptr::null_mut() + }; + + avc_ctx { + cc_count: self.cc_count, + cc_data: cc_data_ptr, + cc_databufsize: self.cc_databufsize as i64, + cc_buffer_saved: if self.cc_buffer_saved { 1 } else { 0 }, + + got_seq_para: if self.got_seq_para { 1 } else { 0 }, + nal_ref_idc: self.nal_ref_idc, + seq_parameter_set_id: self.seq_parameter_set_id, + log2_max_frame_num: self.log2_max_frame_num, + pic_order_cnt_type: self.pic_order_cnt_type, + log2_max_pic_order_cnt_lsb: self.log2_max_pic_order_cnt_lsb, + frame_mbs_only_flag: if self.frame_mbs_only_flag { 1 } else { 0 }, + + num_nal_unit_type_7: self.num_nal_unit_type_7, + num_vcl_hrd: self.num_vcl_hrd, + num_nal_hrd: self.num_nal_hrd, + num_jump_in_frames: self.num_jump_in_frames, + num_unexpected_sei_length: self.num_unexpected_sei_length, + + ccblocks_in_avc_total: self.ccblocks_in_avc_total, + ccblocks_in_avc_lost: self.ccblocks_in_avc_lost, + + frame_num: self.frame_num, + lastframe_num: self.lastframe_num, + currref: self.currref, + maxidx: self.maxidx, + lastmaxidx: self.lastmaxidx, + + minidx: self.minidx, + lastminidx: self.lastminidx, + + maxtref: self.maxtref, + last_gop_maxtref: self.last_gop_maxtref, + + currefpts: self.currefpts, + last_pic_order_cnt_lsb: self.last_pic_order_cnt_lsb, + last_slice_pts: self.last_slice_pts, + } + } +} + +// SEI payload types +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum SeiPayloadType { + BufferingPeriod = 0, + PicTiming = 1, + PanScanRect = 2, + FillerPayload = 3, + UserDataRegisteredItuTT35 = 4, + UserDataUnregistered = 5, + RecoveryPoint = 6, + DecRefPicMarkingRepetition = 7, + SparePic = 8, + SceneInfo = 9, + SubSeqInfo = 10, + SubSeqLayerCharacteristics = 11, + SubSeqCharacteristics = 12, + FullFrameFreeze = 13, + FullFrameFreezeRelease = 14, + FullFrameSnapshot = 15, + ProgressiveRefinementSegmentStart = 16, + ProgressiveRefinementSegmentEnd = 17, + MotionConstrainedSliceGroupSet = 18, + FilmGrainCharacteristics = 19, + DeblockingFilterDisplayPreference = 20, + StereoVideoInfo = 21, +} + +impl From for SeiPayloadType { + fn from(value: u32) -> Self { + match value { + 0 => SeiPayloadType::BufferingPeriod, + 1 => SeiPayloadType::PicTiming, + 2 => SeiPayloadType::PanScanRect, + 3 => SeiPayloadType::FillerPayload, + 4 => SeiPayloadType::UserDataRegisteredItuTT35, + 5 => SeiPayloadType::UserDataUnregistered, + 6 => SeiPayloadType::RecoveryPoint, + 7 => SeiPayloadType::DecRefPicMarkingRepetition, + 8 => SeiPayloadType::SparePic, + 9 => SeiPayloadType::SceneInfo, + 10 => SeiPayloadType::SubSeqInfo, + 11 => SeiPayloadType::SubSeqLayerCharacteristics, + 12 => SeiPayloadType::SubSeqCharacteristics, + 13 => SeiPayloadType::FullFrameFreeze, + 14 => SeiPayloadType::FullFrameFreezeRelease, + 15 => SeiPayloadType::FullFrameSnapshot, + 16 => SeiPayloadType::ProgressiveRefinementSegmentStart, + 17 => SeiPayloadType::ProgressiveRefinementSegmentEnd, + 18 => SeiPayloadType::MotionConstrainedSliceGroupSet, + 19 => SeiPayloadType::FilmGrainCharacteristics, + 20 => SeiPayloadType::DeblockingFilterDisplayPreference, + 21 => SeiPayloadType::StereoVideoInfo, + _ => SeiPayloadType::UserDataUnregistered, // Default fallback + } + } +} diff --git a/src/rust/src/avc/core.rs b/src/rust/src/avc/core.rs new file mode 100644 index 000000000..bd373fb54 --- /dev/null +++ b/src/rust/src/avc/core.rs @@ -0,0 +1,505 @@ +use crate::avc::common_types::*; +use crate::avc::nal::*; +use crate::avc::sei::*; +use crate::avc::FromCType; +use crate::bindings::{cc_subtitle, encoder_ctx, lib_cc_decode, realloc}; +use lib_ccxr::common::AvcNalType; +use lib_ccxr::util::log::DebugMessageFlag; +use lib_ccxr::{debug, info}; +use std::os::raw::c_void; +use std::slice; + +/// Portable round function replacement for roundportable in C +pub fn round_portable(x: f64) -> f64 { + (x + 0.5).floor() +} + +/// Initialize AVC context in Rust +pub fn init_avc_rust() -> AvcContextRust { + AvcContextRust::default() +} + +/// Deinitialize AVC context and print statistics if needed +/// # Safety +/// This function is unsafe because it operates on raw pointers +pub unsafe fn dinit_avc_rust(ctx: &mut AvcContextRust) { + if ctx.ccblocks_in_avc_lost > 0 { + info!( + "Total caption blocks received: {}\n", + ctx.ccblocks_in_avc_total + ); + info!("Total caption blocks lost: {}\n", ctx.ccblocks_in_avc_lost); + } + // Vec will be automatically dropped +} + +/// Process NAL unit data +/// # Safety +/// This function is unsafe because it processes raw NAL data +pub unsafe fn do_nal( + enc_ctx: &mut encoder_ctx, + dec_ctx: &mut lib_cc_decode, + nal_start: &mut [u8], + nal_length: i64, + sub: &mut cc_subtitle, +) -> Result<(), Box> { + if nal_start.is_empty() { + return Ok(()); + } + + let nal_unit_type = + AvcNalType::from_ctype(nal_start[0] & 0x1F).unwrap_or(AvcNalType::Unspecified0); + + // Calculate NAL_stop pointer equivalent + let original_length = nal_length as usize; + if original_length > nal_start.len() { + info!( + "NAL length ({}) exceeds buffer size ({})", + original_length, + nal_start.len() + ); + return Ok(()); + } + + // Get the working slice (skip first byte for remove_03emu) + let mut working_buffer = nal_start[1..original_length].to_vec(); + + // Call remove_03emu equivalent - assuming you have a Rust version + let processed_length = match remove_03emu(&mut working_buffer) { + Some(len) => len, + None => { + info!( + "Notice: NAL of type {:?} had to be skipped because remove_03emu failed.", + nal_unit_type + ); + return Ok(()); + } + }; + + // Truncate buffer to actual processed length + working_buffer.truncate(processed_length); + + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!( + "BEGIN NAL unit type: {:?} length {} ref_idc: {} - Buffered captions before: {}", + nal_unit_type, + working_buffer.len(), + (*dec_ctx.avc_ctx).nal_ref_idc, + if (*dec_ctx.avc_ctx).cc_buffer_saved != 0 { 0 } else { 1 } + )); + + match nal_unit_type { + AvcNalType::AccessUnitDelimiter9 => { + // Found Access Unit Delimiter + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Found Access Unit Delimiter"); + } + + AvcNalType::SequenceParameterSet7 => { + // Found sequence parameter set + // We need this to parse NAL type 1 (CodedSliceNonIdrPicture1) + (*dec_ctx.avc_ctx).num_nal_unit_type_7 += 1; + + // Convert to Rust context, process, then update C context + let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); + seq_parameter_set_rbsp(&mut ctx_rust, &working_buffer)?; + + // Update essential fields in C context + (*dec_ctx.avc_ctx).seq_parameter_set_id = ctx_rust.seq_parameter_set_id; + (*dec_ctx.avc_ctx).log2_max_frame_num = ctx_rust.log2_max_frame_num; + (*dec_ctx.avc_ctx).pic_order_cnt_type = ctx_rust.pic_order_cnt_type; + (*dec_ctx.avc_ctx).log2_max_pic_order_cnt_lsb = ctx_rust.log2_max_pic_order_cnt_lsb; + (*dec_ctx.avc_ctx).frame_mbs_only_flag = + if ctx_rust.frame_mbs_only_flag { 1 } else { 0 }; + (*dec_ctx.avc_ctx).num_nal_hrd = ctx_rust.num_nal_hrd; + (*dec_ctx.avc_ctx).num_vcl_hrd = ctx_rust.num_vcl_hrd; + + (*dec_ctx.avc_ctx).got_seq_para = 1; + } + + AvcNalType::CodedSliceNonIdrPicture1 | AvcNalType::CodedSliceIdrPicture + if (*dec_ctx.avc_ctx).got_seq_para != 0 => + { + // Found coded slice of a non-IDR picture or IDR picture + // We only need the slice header data, no need to implement + // slice_layer_without_partitioning_rbsp( ); + slice_header(enc_ctx, dec_ctx, &mut working_buffer, &nal_unit_type, sub)?; + } + + AvcNalType::Sei if (*dec_ctx.avc_ctx).got_seq_para != 0 => { + // Found SEI (used for subtitles) + // set_fts(ctx->timing); // FIXME - check this!!! + + // Convert to Rust context, process, then update C context + let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); + let old_cc_count = ctx_rust.cc_count; + + sei_rbsp(&mut ctx_rust, &working_buffer); + + // If new subtitle data was found, update the C context directly + if ctx_rust.cc_count > old_cc_count { + // Make sure C context has enough space + let required_size = (ctx_rust.cc_count as usize * 3) + 1; + if required_size > (*dec_ctx.avc_ctx).cc_databufsize as usize { + let new_size = required_size * 2; // Some headroom + let new_ptr = realloc((*dec_ctx.avc_ctx).cc_data as *mut c_void, new_size as _) + as *mut u8; + if new_ptr.is_null() { + return Err("Failed to realloc cc_data".into()); + } + (*dec_ctx.avc_ctx).cc_data = new_ptr; + (*dec_ctx.avc_ctx).cc_databufsize = new_size as i64; + } + + // Copy the data directly to C context + if !(*dec_ctx.avc_ctx).cc_data.is_null() { + let c_data = std::slice::from_raw_parts_mut( + (*dec_ctx.avc_ctx).cc_data, + (*dec_ctx.avc_ctx).cc_databufsize as usize, + ); + let rust_data_len = ctx_rust.cc_data.len().min(c_data.len()); + c_data[..rust_data_len].copy_from_slice(&ctx_rust.cc_data[..rust_data_len]); + } + + // Update the essential fields + (*dec_ctx.avc_ctx).cc_count = ctx_rust.cc_count; + (*dec_ctx.avc_ctx).cc_buffer_saved = if ctx_rust.cc_buffer_saved { 1 } else { 0 }; + (*dec_ctx.avc_ctx).ccblocks_in_avc_total = ctx_rust.ccblocks_in_avc_total; + (*dec_ctx.avc_ctx).ccblocks_in_avc_lost = ctx_rust.ccblocks_in_avc_lost; + } + } + + AvcNalType::PictureParameterSet if (*dec_ctx.avc_ctx).got_seq_para != 0 => { + // Found Picture parameter set + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Found Picture parameter set"); + } + + _ => { + // Handle other NAL unit types or do nothing + } + } + + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!( + "END NAL unit type: {:?} length {} ref_idc: {} - Buffered captions after: {}", + &nal_unit_type, + working_buffer.len(), + (*dec_ctx.avc_ctx).nal_ref_idc, + if (*dec_ctx.avc_ctx).cc_buffer_saved != 0 { 0 } else { 1 } + )); + + Ok(()) +} + +/// Remove emulation prevention bytes (0x000003 sequences) +pub fn remove_emulation_prevention_bytes(input: &[u8]) -> Vec { + let mut output = Vec::with_capacity(input.len()); + let mut i = 0; + let mut consecutive_zeros = 0; + + while i < input.len() { + let byte = input[i]; + + if consecutive_zeros == 2 && byte == 0x03 { + // Skip emulation prevention byte + consecutive_zeros = 0; + } else { + output.push(byte); + if byte == 0x00 { + consecutive_zeros += 1; + } else { + consecutive_zeros = 0; + } + } + i += 1; + } + + output +} + +/// Copied for reference decoder, see if it behaves different that Volker's code +fn EBSPtoRBSP(stream_buffer: &mut [u8], end_bytepos: usize, begin_bytepos: usize) -> isize { + let mut count = 0i32; + + if end_bytepos < begin_bytepos { + return end_bytepos as isize; + } + + let mut j = begin_bytepos; + let mut i = begin_bytepos; + + while i < end_bytepos { + // starting from begin_bytepos to avoid header information + // in NAL unit, 0x000000, 0x000001 or 0x000002 shall not occur at any byte-aligned position + if count == ZEROBYTES_SHORTSTARTCODE && stream_buffer[i] < 0x03 { + return -1; + } + + if count == ZEROBYTES_SHORTSTARTCODE && stream_buffer[i] == 0x03 { + // check the 4th byte after 0x000003, except when cabac_zero_word is used, + // in which case the last three bytes of this NAL unit must be 0x000003 + if (i < end_bytepos - 1) && (stream_buffer[i + 1] > 0x03) { + return -1; + } + + // if cabac_zero_word is used, the final byte of this NAL unit(0x03) is discarded, + // and the last two bytes of RBSP must be 0x0000 + if i == end_bytepos - 1 { + return j as isize; + } + + i += 1; + count = 0; + } + + stream_buffer[j] = stream_buffer[i]; + + if stream_buffer[i] == 0x00 { + count += 1; + } else { + count = 0; + } + + j += 1; + i += 1; + } + + j as isize +} + +fn remove_03emu(buffer: &mut [u8]) -> Option { + let num = buffer.len(); + let newsize = EBSPtoRBSP(buffer, num, 0); + + if newsize == -1 { + None + } else { + Some(newsize as usize) + } +} +/// Copy CC data to buffer +pub fn copy_ccdata_to_buffer(ctx: &mut AvcContextRust, source: &[u8], new_cc_count: usize) { + ctx.ccblocks_in_avc_total += 1; + if !ctx.cc_buffer_saved { + info!("Warning: Probably loss of CC data, unsaved buffer being rewritten"); + ctx.ccblocks_in_avc_lost += 1; + } + + // Copy the cc data + let start_idx = ctx.cc_count as usize * 3; + let copy_len = new_cc_count * 3 + 1; + + if start_idx + copy_len <= ctx.cc_data.len() { + ctx.cc_data[start_idx..start_idx + copy_len].copy_from_slice(&source[..copy_len]); + ctx.cc_count += new_cc_count as u8; + ctx.cc_buffer_saved = false; + } +} + +/// Simple hex dump function for debugging +/// # Safety +/// This function is safe as it only reads data +pub fn hex_dump(data: &[u8]) { + for (i, chunk) in data.chunks(16).enumerate() { + print!("{:08X} | ", i * 16); + + // Print hex bytes + for byte in chunk { + print!("{:02X} ", byte); + } + + // Pad if less than 16 bytes + for _ in chunk.len()..16 { + print!(" "); + } + + print!(" | "); + + // Print ASCII representation + for byte in chunk { + if *byte >= 32 && *byte <= 126 { + print!("{}", *byte as char); + } else { + print!("."); + } + } + + println!(); + } +} +/// Temporarily placed here, import from ts_core when ts module is merged +/// # Safety +/// This function is unsafe because it may dereference a raw pointer. +pub unsafe fn dump(start: *const u8, l: i32, abs_start: u64, clear_high_bit: u32) { + let data = slice::from_raw_parts(start, l as usize); + + let mut x = 0; + while x < l { + info!("{:08} | ", x + abs_start as i32); + + for j in 0..16 { + if x + j < l { + info!("{:02X} ", data[(x + j) as usize]); + } else { + info!(" "); + } + } + info!(" | "); + + for j in 0..16 { + if x + j < l && data[(x + j) as usize] >= b' ' { + let ch = data[(x + j) as usize] & (if clear_high_bit != 0 { 0x7F } else { 0xFF }); + info!("{}", ch as char); + } else { + info!(" "); + } + } + info!("\n"); + x += 16; + } +} +#[derive(Debug)] +pub enum AvcError { + InsufficientBuffer(String), + BrokenStream(String), + ForbiddenZeroBit(String), + Other(String), +} +fn find_next_zero(slice: &[u8]) -> Option { + slice.iter().position(|&b| b == 0x00) +} +/// # Safety +/// This function is unsafe because it dereferences raw pointers and calls `dump` and `do_nal`. +pub unsafe fn process_avc( + enc_ctx: &mut encoder_ctx, + dec_ctx: &mut lib_cc_decode, + avcbuf: &mut [u8], + sub: &mut cc_subtitle, +) -> Result { + let avcbuflen = avcbuf.len(); + + // At least 5 bytes are needed for a NAL unit + if avcbuflen <= 5 { + return Err(AvcError::InsufficientBuffer( + "NAL unit needs at least 5 bytes in the buffer to process AVC video stream".to_string(), + )); + } + + // Warning there should be only leading zeros, nothing else + if !(avcbuf[0] == 0x00 && avcbuf[1] == 0x00) { + return Err(AvcError::BrokenStream( + "Leading bytes are non-zero".to_string(), + )); + } + + let mut buffer_position = 2usize; + let mut firstloop = true; + + // Loop over NAL units + while buffer_position < avcbuflen.saturating_sub(2) { + let mut zeropad = 0; + + // Find next NAL_start + while buffer_position < avcbuflen { + if avcbuf[buffer_position] == 0x01 { + break; + } else if firstloop && avcbuf[buffer_position] != 0x00 { + return Err(AvcError::BrokenStream( + "Leading bytes are non-zero".to_string(), + )); + } + buffer_position += 1; + zeropad += 1; + } + + firstloop = false; + + if buffer_position >= avcbuflen { + break; + } + + let nal_start_pos = buffer_position + 1; + let mut nal_stop_pos = avcbuflen; + + buffer_position += 1; + let restlen = avcbuflen.saturating_sub(buffer_position + 2); + + // Use optimized zero search + if restlen > 0 { + if let Some(zero_offset) = + find_next_zero(&avcbuf[buffer_position..buffer_position + restlen]) + { + let zero_pos = buffer_position + zero_offset; + + if zero_pos + 2 < avcbuflen { + if avcbuf[zero_pos + 1] == 0x00 && (avcbuf[zero_pos + 2] | 0x01) == 0x01 { + nal_stop_pos = zero_pos; + buffer_position = zero_pos + 2; + } else { + // Continue searching from after this zero + buffer_position = zero_pos + 1; + // Recursive search for next start code + while buffer_position < avcbuflen.saturating_sub(2) { + if let Some(next_zero_offset) = find_next_zero( + &avcbuf[buffer_position..avcbuflen.saturating_sub(2)], + ) { + let next_zero_pos = buffer_position + next_zero_offset; + if next_zero_pos + 2 < avcbuflen { + if avcbuf[next_zero_pos + 1] == 0x00 + && (avcbuf[next_zero_pos + 2] | 0x01) == 0x01 + { + nal_stop_pos = next_zero_pos; + buffer_position = next_zero_pos + 2; + break; + } + } else { + nal_stop_pos = avcbuflen; + buffer_position = avcbuflen; + break; + } + buffer_position = next_zero_pos + 1; + } else { + nal_stop_pos = avcbuflen; + buffer_position = avcbuflen; + break; + } + } + } + } else { + nal_stop_pos = avcbuflen; + buffer_position = avcbuflen; + } + } else { + nal_stop_pos = avcbuflen; + buffer_position = avcbuflen; + } + } else { + nal_stop_pos = avcbuflen; + buffer_position = avcbuflen; + } + + if nal_start_pos >= avcbuflen { + break; + } + + if (avcbuf[nal_start_pos] & 0x80) != 0 { + let dump_start = nal_start_pos.saturating_sub(4); + let dump_len = std::cmp::min(10, avcbuflen - dump_start); + dump(avcbuf[dump_start..].as_ptr(), dump_len as i32, 0, 0); + + return Err(AvcError::ForbiddenZeroBit( + "forbidden_zero_bit not zero".to_string(), + )); + } + + (*dec_ctx.avc_ctx).nal_ref_idc = (avcbuf[nal_start_pos] >> 5) as u32; + + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("process_avc: zeropad {}", zeropad)); + + let nal_length = (nal_stop_pos - nal_start_pos) as i64; + let mut nal_slice = avcbuf[nal_start_pos..nal_stop_pos].to_vec(); + + if let Err(e) = do_nal(enc_ctx, dec_ctx, &mut nal_slice, nal_length, sub) { + info!("Error processing NAL unit: {}", e); + } + } + + Ok(avcbuflen) +} diff --git a/src/rust/src/avc/mod.rs b/src/rust/src/avc/mod.rs new file mode 100644 index 000000000..ee9adc923 --- /dev/null +++ b/src/rust/src/avc/mod.rs @@ -0,0 +1,82 @@ +use crate::avc::core::*; +use crate::bindings::{cc_subtitle, encoder_ctx, lib_cc_decode}; +use lib_ccxr::common::AvcNalType; +use lib_ccxr::info; + +pub mod common_types; +pub mod core; +pub mod nal; +pub mod sei; + +pub trait FromCType { + // remove after demuxer is merged, just import then from `ctorust.rs` + /// # Safety + /// This function is unsafe because it uses raw pointers to get data from C types. + unsafe fn from_ctype(c_value: T) -> Option + where + Self: Sized; +} +impl FromCType for AvcNalType { + unsafe fn from_ctype(value: u8) -> Option { + let returnvalue = match value { + 0 => AvcNalType::Unspecified0, + 1 => AvcNalType::CodedSliceNonIdrPicture1, + 2 => AvcNalType::CodedSlicePartitionA, + 3 => AvcNalType::CodedSlicePartitionB, + 4 => AvcNalType::CodedSlicePartitionC, + 5 => AvcNalType::CodedSliceIdrPicture, + 6 => AvcNalType::Sei, + 7 => AvcNalType::SequenceParameterSet7, + 8 => AvcNalType::PictureParameterSet, + 9 => AvcNalType::AccessUnitDelimiter9, + 10 => AvcNalType::EndOfSequence, + 11 => AvcNalType::EndOfStream, + 12 => AvcNalType::FillerData, + 13 => AvcNalType::SequenceParameterSetExtension, + 14 => AvcNalType::PrefixNalUnit, + 15 => AvcNalType::SubsetSequenceParameterSet, + 16 => AvcNalType::Reserved16, + 17 => AvcNalType::Reserved17, + 18 => AvcNalType::Reserved18, + 19 => AvcNalType::CodedSliceAuxiliaryPicture, + 20 => AvcNalType::CodedSliceExtension, + 21 => AvcNalType::Reserved21, + 22 => AvcNalType::Reserved22, + 23 => AvcNalType::Reserved23, + 24 => AvcNalType::Unspecified24, + 25 => AvcNalType::Unspecified25, + 26 => AvcNalType::Unspecified26, + 27 => AvcNalType::Unspecified27, + 28 => AvcNalType::Unspecified28, + 29 => AvcNalType::Unspecified29, + 30 => AvcNalType::Unspecified30, + 31 => AvcNalType::Unspecified31, + _ => AvcNalType::Unspecified0, // Default fallback + }; + Some(returnvalue) + } +} + +/// # Safety +/// This function is unsafe because it dereferences raw pointers from C and calls `process_avc`. +#[no_mangle] +pub unsafe extern "C" fn ccxr_process_avc( + enc_ctx: *mut encoder_ctx, + dec_ctx: *mut lib_cc_decode, + avcbuf: *mut u8, + avcbuflen: usize, + sub: *mut cc_subtitle, +) -> usize { + if avcbuf.is_null() || avcbuflen == 0 { + return 0; + } + + // Create a safe slice from the raw pointer + let avc_slice = std::slice::from_raw_parts_mut(avcbuf, avcbuflen); + + // Call the safe Rust version + process_avc(&mut *enc_ctx, &mut *dec_ctx, avc_slice, &mut *sub).unwrap_or_else(|e| { + info!("Error in process_avc: {:?}", e); + 0 // Return 0 to indicate error + }) +} diff --git a/src/rust/src/avc/nal.rs b/src/rust/src/avc/nal.rs new file mode 100644 index 000000000..018ba6dda --- /dev/null +++ b/src/rust/src/avc/nal.rs @@ -0,0 +1,642 @@ +use crate::avc::common_types::*; +use crate::avc::core::round_portable; +use crate::bindings::{ + anchor_hdcc, cc_subtitle, encoder_ctx, lib_cc_decode, process_hdcc, store_hdcc, +}; +use crate::libccxr_exports::time::{ + ccxr_print_debug_timing, ccxr_print_mstime_static, ccxr_set_fts, +}; +use crate::{ccx_options, current_fps, total_frames_count, MPEG_CLOCK_FREQ}; +use lib_ccxr::common::{AvcNalType, BitStreamRust, BitstreamError, FRAMERATES_VALUES, SLICE_TYPES}; +use lib_ccxr::util::log::DebugMessageFlag; +use lib_ccxr::{debug, info}; +use std::os::raw::{c_char, c_long}; + +/// Process sequence parameter set RBSP +pub fn seq_parameter_set_rbsp( + ctx: &mut AvcContextRust, + seqbuf: &[u8], +) -> Result<(), BitstreamError> { + // Calculate buffer length from pointer difference + let mut q1 = BitStreamRust::new(seqbuf)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "SEQUENCE PARAMETER SET (bitlen: {})\n", q1.bits_left); + + let tmp = q1.read_bits(8)?; + let profile_idc = tmp; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "profile_idc= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set0_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set1_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set2_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set3_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set4_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set5_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(2)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "reserved= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(8)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "level_idc= {:4} ({:#X})\n", tmp, tmp); + + ctx.seq_parameter_set_id = q1.read_exp_golomb_unsigned()? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_parameter_set_id= {:4} ({:#X})\n", ctx.seq_parameter_set_id, ctx.seq_parameter_set_id); + + if profile_idc == 100 + || profile_idc == 110 + || profile_idc == 122 + || profile_idc == 244 + || profile_idc == 44 + || profile_idc == 83 + || profile_idc == 86 + || profile_idc == 118 + || profile_idc == 128 + { + let chroma_format_idc = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_format_idc= {:4} ({:#X})\n", chroma_format_idc, chroma_format_idc); + + if chroma_format_idc == 3 { + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "separate_colour_plane_flag= {:4} ({:#X})\n", tmp, tmp); + } + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bit_depth_luma_minus8= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bit_depth_chroma_minus8= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "qpprime_y_zero_transform_bypass_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_scaling_matrix_present_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp == 1 { + // WVI: untested, just copied from specs. + for i in 0..if chroma_format_idc != 3 { 8 } else { 12 } { + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_scaling_list_present_flag[{}]= {:4} ({:#X})\n", i, tmp, tmp); + + if tmp != 0 { + // We use a "dummy"/slimmed-down replacement here. Actual/full code can be found in the spec (ISO/IEC 14496-10:2012(E)) chapter 7.3.2.1.1.1 - Scaling list syntax + if i < 6 { + // Scaling list size 16 + // TODO: replace with full scaling list implementation? + let mut next_scale = 8i64; + let mut last_scale = 8i64; + for _j in 0..16 { + if next_scale != 0 { + let delta_scale = q1.read_exp_golomb()?; + next_scale = (last_scale + delta_scale + 256) % 256; + } + last_scale = if next_scale == 0 { + last_scale + } else { + next_scale + }; + } + // END of TODO + } else { + // Scaling list size 64 + // TODO: replace with full scaling list implementation? + let mut next_scale = 8i64; + let mut last_scale = 8i64; + for _j in 0..64 { + if next_scale != 0 { + let delta_scale = q1.read_exp_golomb()?; + next_scale = (last_scale + delta_scale + 256) % 256; + } + last_scale = if next_scale == 0 { + last_scale + } else { + next_scale + }; + } + // END of TODO + } + } + } + } + } + + ctx.log2_max_frame_num = q1.read_exp_golomb_unsigned()? as i32; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "log2_max_frame_num4_minus4= {:4} ({:#X})\n", ctx.log2_max_frame_num, ctx.log2_max_frame_num); + ctx.log2_max_frame_num += 4; // 4 is added due to the formula. + + ctx.pic_order_cnt_type = q1.read_exp_golomb_unsigned()? as i32; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_order_cnt_type= {:4} ({:#X})\n", ctx.pic_order_cnt_type, ctx.pic_order_cnt_type); + + if ctx.pic_order_cnt_type == 0 { + ctx.log2_max_pic_order_cnt_lsb = q1.read_exp_golomb_unsigned()? as i32; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "log2_max_pic_order_cnt_lsb_minus4= {:4} ({:#X})\n", ctx.log2_max_pic_order_cnt_lsb, ctx.log2_max_pic_order_cnt_lsb); + ctx.log2_max_pic_order_cnt_lsb += 4; // 4 is added due to formula. + } else if ctx.pic_order_cnt_type == 1 { + // CFS: Untested, just copied from specs. + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "delta_pic_order_always_zero_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_non_ref_pic= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_top_to_bottom_field {:4} ({:#X})\n", tmp, tmp); + + let num_ref_frame_in_pic_order_cnt_cycle = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "num_ref_frame_in_pic_order_cnt_cycle {:4} ({:#X})\n", num_ref_frame_in_pic_order_cnt_cycle, num_ref_frame_in_pic_order_cnt_cycle); + + for i in 0..num_ref_frame_in_pic_order_cnt_cycle { + let tmp = q1.read_exp_golomb()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_ref_frame [{} / {}] = {:4} ({:#X})\n", i, num_ref_frame_in_pic_order_cnt_cycle, tmp, tmp); + } + } else { + // Nothing needs to be parsed when pic_order_cnt_type == 2 + } + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "max_num_ref_frames= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "gaps_in_frame_num_value_allowed_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_width_in_mbs_minus1= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_height_in_map_units_minus1= {:4} ({:#X})\n", tmp, tmp); + + ctx.frame_mbs_only_flag = q1.read_bits(1)? != 0; + + if !ctx.frame_mbs_only_flag { + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "mb_adaptive_fr_fi_flag= {:4} ({:#X})\n", tmp, tmp); + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "direct_8x8_inference_f= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_cropping_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_left_offset= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_right_offset= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_top_offset= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_bottom_offset= {:4} ({:#X})\n", tmp, tmp); + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "vui_parameters_present= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "\nVUI parameters\n"); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "aspect_ratio_info_pres= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + let tmp = q1.read_bits(8)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "aspect_ratio_idc= {:4} ({:#X})\n", tmp, tmp); + + if tmp == 255 { + let tmp = q1.read_bits(16)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "sar_width= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(16)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "sar_height= {:4} ({:#X})\n", tmp, tmp); + } + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "overscan_info_pres_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "overscan_appropriate_flag= {:4} ({:#X})\n", tmp, tmp); + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_signal_type_present_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + let tmp = q1.read_bits(3)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_format= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_full_range_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "colour_description_present_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + let tmp = q1.read_bits(8)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "colour_primaries= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(8)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "transfer_characteristics= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(8)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "matrix_coefficients= {:4} ({:#X})\n", tmp, tmp); + } + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_loc_info_present_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_sample_loc_type_top_field= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_exp_golomb_unsigned()?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_sample_loc_type_bottom_field= {:4} ({:#X})\n", tmp, tmp); + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "timing_info_present_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + let tmp = q1.read_bits(32)?; + let num_units_in_tick = tmp; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "num_units_in_tick= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(32)?; + let time_scale = tmp; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "time_scale= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + let fixed_frame_rate_flag = tmp != 0; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "fixed_frame_rate_flag= {:4} ({:#X})\n", tmp, tmp); + + // Change: use num_units_in_tick and time_scale to calculate FPS. (ISO/IEC 14496-10:2012(E), page 397 & further) + if fixed_frame_rate_flag { + let clock_tick = num_units_in_tick as f64 / time_scale as f64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "clock_tick= {}\n", clock_tick); + + unsafe { + current_fps = time_scale as f64 / (2.0 * num_units_in_tick as f64); + } // Based on formula D-2, p. 359 of the ISO/IEC 14496-10:2012(E) spec. + } + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "nal_hrd_parameters_present_flag= {:4} ({:#X})\n", tmp, tmp); + + if tmp != 0 { + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "nal_hrd. Not implemented for now. Hopefully not needed. Skipping rest of NAL\n"); + ctx.num_nal_hrd += 1; + return Ok(()); + } + + let tmp1 = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "vcl_hrd_parameters_present_flag= {:#X}\n", tmp1); + + if tmp != 0 { + // TODO. + println!( + "vcl_hrd. Not implemented for now. Hopefully not needed. Skipping rest of NAL" + ); + ctx.num_vcl_hrd += 1; + } + + if tmp != 0 || tmp1 != 0 { + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "low_delay_hrd_flag= {:4} ({:#X})\n", tmp, tmp); + return Ok(()); + } + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_struct_present_flag= {:4} ({:#X})\n", tmp, tmp); + + let tmp = q1.read_bits(1)?; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bitstream_restriction_flag= {:4} ({:#X})\n", tmp, tmp); + + // .. + // The hope was to find the GOP length in max_dec_frame_buffering, but + // it was not set in the testfile. Ignore the rest here, it's + // currently not needed. + } + + Ok(()) +} +/// # Safety +/// This function is unsafe because it dereferences raw pointers and calls C functions. +pub unsafe fn slice_header( + enc_ctx: &mut encoder_ctx, + dec_ctx: &mut lib_cc_decode, + heabuf: &mut [u8], + nal_unit_type: &AvcNalType, + sub: &mut cc_subtitle, +) -> Result<(), Box> { + let mut tmp: i64; + + let mut bottom_field_flag: i64 = 0; + let mut pic_order_cnt_lsb: i64 = -1; + + let field_pic_flag: i64; // Moved here because it's needed for pic_order_cnt_type==2 + + // Initialize bitstream + let mut q1 = match BitStreamRust::new(heabuf) { + Ok(bs) => bs, + Err(_) => { + info!("Skipping slice header due to failure in init_bitstream."); + return Ok(()); + } + }; + + let ird_pic_flag: i32 = if *nal_unit_type == AvcNalType::CodedSliceIdrPicture { + 1 + } else { + 0 + }; + + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "\nSLICE HEADER\n"); + + tmp = q1.read_exp_golomb_unsigned()? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("first_mb_in_slice= {:4} ({:#X})", tmp, tmp)); + + let slice_type: i64 = q1.read_exp_golomb_unsigned()? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("slice_type= {:4X}", slice_type)); + + tmp = q1.read_exp_golomb_unsigned()? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("pic_parameter_set_id= {:4} ({:#X})", tmp, tmp)); + + (*dec_ctx.avc_ctx).lastframe_num = (*dec_ctx.avc_ctx).frame_num; + let max_frame_num: i32 = (1 << (*dec_ctx.avc_ctx).log2_max_frame_num) - 1; + + // Needs log2_max_frame_num_minus4 + 4 bits + (*dec_ctx.avc_ctx).frame_num = + q1.read_bits((*dec_ctx.avc_ctx).log2_max_frame_num as u32)? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("frame_num= {:4X}", (*dec_ctx.avc_ctx).frame_num)); + + if (*dec_ctx.avc_ctx).frame_mbs_only_flag == 0 { + field_pic_flag = q1.read_bits(1)? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("field_pic_flag= {:4X}", field_pic_flag)); + + if field_pic_flag != 0 { + // bottom_field_flag + bottom_field_flag = q1.read_bits(1)? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("bottom_field_flag= {:4X}", bottom_field_flag)); + + // When bottom_field_flag is set the video is interlaced, + // override current_fps. + current_fps = FRAMERATES_VALUES[dec_ctx.current_frame_rate as usize]; + } + } + + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("ird_pic_flag= {:4}", ird_pic_flag)); + + if *nal_unit_type == AvcNalType::CodedSliceIdrPicture { + tmp = q1.read_exp_golomb_unsigned()? as i64; + debug!( msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("idr_pic_id= {:4} ({:#X})", tmp, tmp)); + // TODO + } + + if (*dec_ctx.avc_ctx).pic_order_cnt_type == 0 { + pic_order_cnt_lsb = + q1.read_bits((*dec_ctx.avc_ctx).log2_max_pic_order_cnt_lsb as u32)? as i64; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("pic_order_cnt_lsb= {:4X}", pic_order_cnt_lsb)); + } + + if (*dec_ctx.avc_ctx).pic_order_cnt_type == 1 { + panic!("In slice_header: AVC: ctx->avc_ctx->pic_order_cnt_type == 1 not yet supported."); + } + + // Ignore slice with same pic order or pts + if ccx_options.usepicorder != 0 { + if (*dec_ctx.avc_ctx).last_pic_order_cnt_lsb == pic_order_cnt_lsb { + return Ok(()); + } + (*dec_ctx.avc_ctx).last_pic_order_cnt_lsb = pic_order_cnt_lsb; + } else { + if (*dec_ctx.timing).current_pts == (*dec_ctx.avc_ctx).last_slice_pts { + return Ok(()); + } + (*dec_ctx.avc_ctx).last_slice_pts = (*dec_ctx.timing).current_pts; + } + + // The rest of the data in slice_header() is currently unused. + + // A reference pic (I or P is always the last displayed picture of a POC + // sequence. B slices can be reference pics, so ignore ctx->avc_ctx->nal_ref_idc. + let mut isref = 0; + match slice_type { + // P-SLICES + 0 | 5 | + // I-SLICES + 2 | 7 => { + isref = 1; + } + _ => {} + } + + let maxrefcnt = (1 << (*dec_ctx.avc_ctx).log2_max_pic_order_cnt_lsb) - 1; + + // If we saw a jump set maxidx, lastmaxidx to -1 + let mut dif = (*dec_ctx.avc_ctx).frame_num - (*dec_ctx.avc_ctx).lastframe_num; + if dif == -(max_frame_num as i64) { + dif = 0; + } + if (*dec_ctx.avc_ctx).lastframe_num > -1 && !(0..=1).contains(&dif) { + (*dec_ctx.avc_ctx).num_jump_in_frames += 1; + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("\nJump in frame numbers ({}/{})\n", + (*dec_ctx.avc_ctx).frame_num, (*dec_ctx.avc_ctx).lastframe_num)); + // This will prohibit setting current_tref on potential jumps. + (*dec_ctx.avc_ctx).maxidx = -1; + (*dec_ctx.avc_ctx).lastmaxidx = -1; + } + + // Sometimes two P-slices follow each other, see garbled_dishHD.mpg, + // in this case we only treat the first as a reference pic + if isref == 1 && dec_ctx.frames_since_last_gop <= 3 { + // Used to be == 1, but the sample file + // 2014 SugarHouse Casino Mummers Parade Fancy Brigades_new.ts was garbled + // Probably doing a proper PTS sort would be a better solution. + isref = 0; + debug!(msg_type = DebugMessageFlag::TIME; "Ignoring this reference pic.\n"); + } + + // if slices are buffered - flush + if isref == 1 { + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("\nReference pic! [{}]\n", SLICE_TYPES[slice_type as usize])); + debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!("\nReference pic! [{}] maxrefcnt: {:3}\n", + SLICE_TYPES[slice_type as usize], maxrefcnt)); + + // Flush buffered cc blocks before doing the housekeeping + if dec_ctx.has_ccdata_buffered != 0 { + process_hdcc(enc_ctx, dec_ctx, sub); + } + + dec_ctx.last_gop_length = dec_ctx.frames_since_last_gop; + dec_ctx.frames_since_last_gop = 0; + (*dec_ctx.avc_ctx).last_gop_maxtref = (*dec_ctx.avc_ctx).maxtref; + (*dec_ctx.avc_ctx).maxtref = 0; + (*dec_ctx.avc_ctx).lastmaxidx = (*dec_ctx.avc_ctx).maxidx; + (*dec_ctx.avc_ctx).maxidx = 0; + (*dec_ctx.avc_ctx).lastminidx = (*dec_ctx.avc_ctx).minidx; + (*dec_ctx.avc_ctx).minidx = 10000; + + if ccx_options.usepicorder != 0 { + // Use pic_order_cnt_lsb + + // Make sure that current_index never wraps for curidx values that + // are smaller than currref + (*dec_ctx.avc_ctx).currref = pic_order_cnt_lsb as i32; + if (*dec_ctx.avc_ctx).currref < maxrefcnt / 3 { + (*dec_ctx.avc_ctx).currref += maxrefcnt + 1; + } + + // If we wrapped around lastmaxidx might be larger than + // the current index - fix this. + if (*dec_ctx.avc_ctx).lastmaxidx > (*dec_ctx.avc_ctx).currref + maxrefcnt / 2 { + // implies lastmaxidx > 0 + (*dec_ctx.avc_ctx).lastmaxidx -= maxrefcnt + 1; + } + } else { + // Use PTS ordering + (*dec_ctx.avc_ctx).currefpts = (*dec_ctx.timing).current_pts; + (*dec_ctx.avc_ctx).currref = 0; + } + + anchor_hdcc(dec_ctx, (*dec_ctx.avc_ctx).currref); + } + + let mut current_index = if ccx_options.usepicorder != 0 { + // Use pic_order_cnt_lsb + // Wrap (add max index value) current_index if needed. + if (*dec_ctx.avc_ctx).currref as i64 - pic_order_cnt_lsb > (maxrefcnt / 2) as i64 { + (pic_order_cnt_lsb + (maxrefcnt + 1) as i64) as i32 + } else { + pic_order_cnt_lsb as i32 + } + } else { + // Use PTS ordering - calculate index position from PTS difference and + // frame rate + // The 2* accounts for a discrepancy between current and actual FPS + // seen in some files (CCSample2.mpg) + let pts_diff = (*dec_ctx.timing).current_pts - (*dec_ctx.avc_ctx).currefpts; + let fps_factor = MPEG_CLOCK_FREQ as f64 / current_fps; + round_portable(2.0 * pts_diff as f64 / fps_factor) as i32 + }; + + if !ccx_options.usepicorder != 0 && current_index.abs() >= MAXBFRAMES { + // Probably a jump in the timeline. Warn and handle gracefully. + info!( + "\nFound large gap({}) in PTS! Trying to recover ...\n", + current_index + ); + current_index = 0; + } + + // Track maximum index for this GOP + if current_index > (*dec_ctx.avc_ctx).maxidx { + (*dec_ctx.avc_ctx).maxidx = current_index; + } + + if ccx_options.usepicorder != 0 { + // Calculate tref + if (*dec_ctx.avc_ctx).lastmaxidx > 0 { + (*dec_ctx.timing).current_tref = current_index - (*dec_ctx.avc_ctx).lastmaxidx - 1; + // Set maxtref + if (*dec_ctx.timing).current_tref > (*dec_ctx.avc_ctx).maxtref { + (*dec_ctx.avc_ctx).maxtref = (*dec_ctx.timing).current_tref; + } + // Now an ugly workaround where pic_order_cnt_lsb increases in + // steps of two. The 1.5 is an approximation, it should be: + // last_gop_maxtref+1 == last_gop_length*2 + if (*dec_ctx.avc_ctx).last_gop_maxtref as f64 > dec_ctx.last_gop_length as f64 * 1.5 { + (*dec_ctx.timing).current_tref /= 2; + } + } else { + (*dec_ctx.timing).current_tref = 0; + } + + if (*dec_ctx.timing).current_tref < 0 { + info!("current_tref is negative!?"); + } + } else { + // Track minimum index for this GOP + if current_index < (*dec_ctx.avc_ctx).minidx { + (*dec_ctx.avc_ctx).minidx = current_index; + } + + (*dec_ctx.timing).current_tref = 1; + if current_index == (*dec_ctx.avc_ctx).lastminidx { + // This implies that the minimal index (assuming its number is + // fairly constant) sets the temporal reference to zero - needed to set sync_pts. + (*dec_ctx.timing).current_tref = 0; + } + if (*dec_ctx.avc_ctx).lastmaxidx == -1 { + // Set temporal reference to zero on minimal index and in the first GOP + // to avoid setting a wrong fts_offset + (*dec_ctx.timing).current_tref = 0; + } + } + + ccxr_set_fts(dec_ctx.timing); // Keep frames_since_ref_time==0, use current_tref + + debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!(" picordercnt:{:3} tref:{:3} idx:{:3} refidx:{:3} lmaxidx:{:3} maxtref:{:3}\n", + pic_order_cnt_lsb, (*dec_ctx.timing).current_tref, + current_index, (*dec_ctx.avc_ctx).currref, (*dec_ctx.avc_ctx).lastmaxidx, (*dec_ctx.avc_ctx).maxtref)); + let mut buf = [c_char::from(0i8); 64]; + debug!( + msg_type = DebugMessageFlag::TIME; + "{}", + &format!( + " sync_pts:{} ({:8})", + std::ffi::CStr::from_ptr(ccxr_print_mstime_static( + ((*dec_ctx.timing).sync_pts / ((MPEG_CLOCK_FREQ as i64) / 1000i64)) as c_long, + buf.as_mut_ptr() + )) + .to_str() + .unwrap_or(""), + (*dec_ctx.timing).sync_pts as u32 + ) + ); + + debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!(" - {} since GOP: {:2}", + SLICE_TYPES[slice_type as usize], + dec_ctx.frames_since_last_gop as u32)); + + debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!(" b:{} frame# {}\n", bottom_field_flag, (*dec_ctx.avc_ctx).frame_num)); + + // sync_pts is (was) set when current_tref was zero + if (*dec_ctx.avc_ctx).lastmaxidx > -1 && (*dec_ctx.timing).current_tref == 0 { + debug!(msg_type = DebugMessageFlag::TIME; "\nNew temporal reference:\n"); + ccxr_print_debug_timing(dec_ctx.timing); + } + + total_frames_count += 1; + dec_ctx.frames_since_last_gop += 1; + + store_hdcc( + enc_ctx, + dec_ctx, + (*dec_ctx.avc_ctx).cc_data, + (*dec_ctx.avc_ctx).cc_count as i32, + current_index, + (*dec_ctx.timing).fts_now, + sub, + ); + + (*dec_ctx.avc_ctx).cc_buffer_saved = 1; // store_hdcc supposedly saves the CC buffer to a sequence buffer + (*dec_ctx.avc_ctx).cc_count = 0; + + Ok(()) +} diff --git a/src/rust/src/avc/sei.rs b/src/rust/src/avc/sei.rs new file mode 100644 index 000000000..dce3b0229 --- /dev/null +++ b/src/rust/src/avc/sei.rs @@ -0,0 +1,334 @@ +use crate::avc::common_types::*; +use crate::avc::core::{copy_ccdata_to_buffer, dump}; +use lib_ccxr::util::log::{DebugMessageFlag, ExitCause}; +use lib_ccxr::{debug, fatal, info}; + +pub fn sei_rbsp(ctx: &mut AvcContextRust, seibuf: &[u8]) { + if seibuf.is_empty() { + return; + } + + let seiend_idx = seibuf.len(); + let mut tbuf_idx = 0; + + while tbuf_idx < seiend_idx - 1 { + // Use -1 because of trailing marker + let remaining_slice = &seibuf[tbuf_idx..]; + let consumed = sei_message(ctx, remaining_slice); + if consumed == 0 { + // Prevent infinite loop if sei_message doesn't consume any bytes + break; + } + tbuf_idx += consumed; + } + + if tbuf_idx == seiend_idx - 1 { + if seibuf[tbuf_idx] != 0x80 { + info!("Strange rbsp_trailing_bits value: {:02X}", seibuf[tbuf_idx]); + } else { + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; ""); + } + } else { + // TODO: This really really looks bad + info!("WARNING: Unexpected SEI unit length...trying to continue."); + // temp_debug = 1; // This appears to be a global debug flag - omitted in Rust version + info!("Failed block (at sei_rbsp) was:"); + + // Call dump function using the existing unsafe dump function + unsafe { + dump(seibuf.as_ptr(), seibuf.len() as i32, 0, 0); + } + + ctx.num_unexpected_sei_length += 1; + } +} +// This combines sei_message() and sei_payload(). +pub fn sei_message(ctx: &mut AvcContextRust, seibuf: &[u8]) -> usize { + let mut seibuf_idx = 0; + + if seibuf.is_empty() { + return 0; + } + + let mut payload_type = 0; + while seibuf_idx < seibuf.len() && seibuf[seibuf_idx] == 0xff { + payload_type += 255; + seibuf_idx += 1; + } + + if seibuf_idx >= seibuf.len() { + return seibuf_idx; + } + + payload_type += seibuf[seibuf_idx] as i32; + seibuf_idx += 1; + + let mut payload_size = 0; + while seibuf_idx < seibuf.len() && seibuf[seibuf_idx] == 0xff { + payload_size += 255; + seibuf_idx += 1; + } + + if seibuf_idx >= seibuf.len() { + return seibuf_idx; + } + + payload_size += seibuf[seibuf_idx] as i32; + seibuf_idx += 1; + + let mut broken = false; + let payload_start = seibuf_idx; + seibuf_idx += payload_size as usize; + + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Payload type: {} size: {} - ", payload_type, payload_size); + + if seibuf_idx > seibuf.len() { + // TODO: What do we do here? + broken = true; + if payload_type == 4 { + debug!(msg_type = DebugMessageFlag::VERBOSE; "Warning: Subtitles payload seems incorrect (too long), continuing but it doesn't look good.."); + } else { + debug!(msg_type = DebugMessageFlag::VERBOSE; "Warning: Non-subtitles payload seems incorrect (too long), continuing but it doesn't look good.."); + } + } + debug!(msg_type = DebugMessageFlag::VERBOSE; ""); + + // Ignore all except user_data_registered_itu_t_t35() payload + if !broken && payload_type == 4 && payload_start + payload_size as usize <= seibuf.len() { + let payload_data = &seibuf[payload_start..payload_start + payload_size as usize]; + user_data_registered_itu_t_t35(ctx, payload_data); + } + + seibuf_idx +} +pub fn user_data_registered_itu_t_t35(ctx: &mut AvcContextRust, userbuf: &[u8]) { + if userbuf.len() < 3 { + return; + } + + let mut tbuf_idx = 0; + let user_data_len: usize; + let local_cc_count: usize; + + let itu_t_t35_country_code = userbuf[tbuf_idx]; + tbuf_idx += 1; + + let itu_t_35_provider_code = (userbuf[tbuf_idx] as u16) * 256 + (userbuf[tbuf_idx + 1] as u16); + tbuf_idx += 2; + + // ANSI/SCTE 128 2008: + // itu_t_t35_country_code == 0xB5 + // itu_t_35_provider_code == 0x0031 + // see spec for details - no example -> no support + + // Example files (sample.ts, ...): + // itu_t_t35_country_code == 0xB5 + // itu_t_35_provider_code == 0x002F + // user_data_type_code == 0x03 (cc_data) + // user_data_len == next byte (length after this byte up to (incl) marker.) + // cc_data struct (CEA-708) + // marker == 0xFF + + if itu_t_t35_country_code != 0xB5 { + info!("Not a supported user data SEI"); + info!(" itu_t_35_country_code: {:02x}", itu_t_t35_country_code); + return; + } + + match itu_t_35_provider_code { + 0x0031 => { + // ANSI/SCTE 128 + debug!(msg_type = DebugMessageFlag::VERBOSE; "Caption block in ANSI/SCTE 128..."); + if tbuf_idx + 4 <= userbuf.len() + && userbuf[tbuf_idx] == 0x47 + && userbuf[tbuf_idx + 1] == 0x41 + && userbuf[tbuf_idx + 2] == 0x39 + && userbuf[tbuf_idx + 3] == 0x34 + { + // ATSC1_data() - GA94 + + debug!(msg_type = DebugMessageFlag::VERBOSE; "ATSC1_data()..."); + tbuf_idx += 4; + + if tbuf_idx >= userbuf.len() { + return; + } + + let user_data_type_code = userbuf[tbuf_idx]; + tbuf_idx += 1; + + match user_data_type_code { + 0x03 => { + debug!(msg_type = DebugMessageFlag::VERBOSE; "cc_data (finally)!"); + /* + cc_count = 2; // Forced test + process_cc_data_flag = (*tbuf & 2) >> 1; + mandatory_1 = (*tbuf & 1); + mandatory_0 = (*tbuf & 4) >>2; + if (!mandatory_1 || mandatory_0) + { + printf ("Essential tests not passed.\n"); + break; + } + */ + if tbuf_idx >= userbuf.len() { + return; + } + + local_cc_count = (userbuf[tbuf_idx] & 0x1F) as usize; + + /* + let process_cc_data_flag = (userbuf[tbuf_idx] & 0x40) >> 6; + if (!process_cc_data_flag) + { + mprint ("process_cc_data_flag == 0, skipping this caption block.\n"); + break; + } */ + /* + The following tests are not passed in Comcast's sample videos. *tbuf here is always 0x41. + if (! (*tbuf & 0x80)) // First bit must be 1 + { + printf ("Fixed bit should be 1, but it's 0 - skipping this caption block.\n"); + break; + } + if (*tbuf & 0x20) // Third bit must be 0 + { + printf ("Fixed bit should be 0, but it's 1 - skipping this caption block.\n"); + break; + } */ + tbuf_idx += 1; + /* + Another test that the samples ignore. They contain 00! + if (*tbuf!=0xFF) + { + printf ("Fixed value should be 0xFF, but it's %02X - skipping this caption block.\n", *tbuf); + } */ + // OK, all checks passed! + tbuf_idx += 1; + let cc_tmp_data_start = tbuf_idx; + + /* TODO: I don't think we have user_data_len here + if (cc_count*3+3 != user_data_len) + fatal(CCX_COMMON_EXIT_BUG_BUG, + "Syntax problem: user_data_len != cc_count*3+3."); */ + + // Enough room for CC captions? + if cc_tmp_data_start + local_cc_count * 3 >= userbuf.len() { + fatal!(cause = ExitCause::Bug; "Syntax problem: Too many caption blocks."); + } + + if cc_tmp_data_start + local_cc_count * 3 < userbuf.len() + && userbuf[cc_tmp_data_start + local_cc_count * 3] != 0xFF + { + // See GitHub Issue #1001 for the related change + info!("\rWarning! Syntax problem: Final 0xFF marker missing. Continuing..."); + return; // Skip Block + } + + // Save the data and process once we know the sequence number + if ((ctx.cc_count as usize + local_cc_count) * 3) + 1 > ctx.cc_databufsize { + let new_size = ((ctx.cc_count as usize + local_cc_count) * 6) + 1; + ctx.cc_data.resize(new_size, 0); + ctx.cc_databufsize = new_size; + } + + // Copy new cc data into cc_data + let cc_tmp_data = &userbuf[cc_tmp_data_start..]; + copy_ccdata_to_buffer(ctx, cc_tmp_data, local_cc_count); + } + 0x06 => { + debug!(msg_type = DebugMessageFlag::VERBOSE; "bar_data (unsupported for now)"); + } + _ => { + debug!(msg_type = DebugMessageFlag::VERBOSE; "SCTE/ATSC reserved."); + } + } + } else if tbuf_idx + 4 <= userbuf.len() + && userbuf[tbuf_idx] == 0x44 + && userbuf[tbuf_idx + 1] == 0x54 + && userbuf[tbuf_idx + 2] == 0x47 + && userbuf[tbuf_idx + 3] == 0x31 + { // afd_data() - DTG1 + // Active Format Description Data. Actually unrelated to captions. Left + // here in case we want to do some reporting eventually. From specs: + // "Active Format Description (AFD) should be included in video user + // data whenever the rectangular picture area containing useful + // information does not extend to the full height or width of the coded + // frame. AFD data may also be included in user data when the + // rectangular picture area containing + // useful information extends to the fullheight and width of the + // coded frame." + } else { + debug!(msg_type = DebugMessageFlag::VERBOSE; "SCTE/ATSC reserved."); + } + } + 0x002F => { + debug!(msg_type = DebugMessageFlag::VERBOSE; "ATSC1_data() - provider_code = 0x002F"); + + if tbuf_idx >= userbuf.len() { + return; + } + + let user_data_type_code = userbuf[tbuf_idx]; + if user_data_type_code != 0x03 { + debug!(msg_type = DebugMessageFlag::VERBOSE; + "Not supported user_data_type_code: {:02x}", user_data_type_code); + return; + } + tbuf_idx += 1; + + if tbuf_idx >= userbuf.len() { + return; + } + + user_data_len = userbuf[tbuf_idx] as usize; + tbuf_idx += 1; + + if tbuf_idx >= userbuf.len() { + return; + } + + local_cc_count = (userbuf[tbuf_idx] & 0x1F) as usize; + let process_cc_data_flag = (userbuf[tbuf_idx] & 0x40) >> 6; + + if process_cc_data_flag == 0 { + info!("process_cc_data_flag == 0, skipping this caption block."); + return; + } + + let cc_tmp_data_start = tbuf_idx + 2; + + if local_cc_count * 3 + 3 != user_data_len { + fatal!(cause = ExitCause::Bug; "Syntax problem: user_data_len != cc_count*3+3."); + } + + // Enough room for CC captions? + if cc_tmp_data_start + local_cc_count * 3 >= userbuf.len() { + fatal!(cause = ExitCause::Bug; "Syntax problem: Too many caption blocks."); + } + + if cc_tmp_data_start + local_cc_count * 3 < userbuf.len() + && userbuf[cc_tmp_data_start + local_cc_count * 3] != 0xFF + { + fatal!(cause = ExitCause::Bug; "Syntax problem: Final 0xFF marker missing."); + } + + // Save the data and process once we know the sequence number + if (((local_cc_count + ctx.cc_count as usize) * 3) + 1) > ctx.cc_databufsize { + let new_size = ((local_cc_count + ctx.cc_count as usize) * 6) + 1; + ctx.cc_data.resize(new_size, 0); + ctx.cc_databufsize = new_size; + } + + // Copy new cc data into cc_data - replace command below. + let cc_tmp_data = &userbuf[cc_tmp_data_start..]; + copy_ccdata_to_buffer(ctx, cc_tmp_data, local_cc_count); + + // dump(tbuf,user_data_len-1,0); + } + _ => { + info!("Not a supported user data SEI"); + info!(" itu_t_35_provider_code: {:04x}", itu_t_35_provider_code); + } + } +} diff --git a/src/rust/src/decoder/mod.rs b/src/rust/src/decoder/mod.rs index 325a72fc3..5534129ed 100644 --- a/src/rust/src/decoder/mod.rs +++ b/src/rust/src/decoder/mod.rs @@ -45,7 +45,10 @@ impl<'a> Dtvcc<'a> { /// Create a new dtvcc context pub fn new(ctx: &'a mut dtvcc_ctx) -> Self { let report = unsafe { &mut *ctx.report }; - let encoder = unsafe { &mut *(ctx.encoder as *mut encoder_ctx) }; + let mut encoder = Box::into_raw(Box::new(encoder_ctx::default())); + if !ctx.encoder.is_null() { + encoder = unsafe { &mut *(ctx.encoder as *mut encoder_ctx) }; + } let timing = unsafe { &mut *ctx.timing }; Self { @@ -59,7 +62,7 @@ impl<'a> Dtvcc<'a> { packet_length: ctx.current_packet_length as u8, is_header_parsed: is_true(ctx.is_current_packet_header_parsed), last_sequence: ctx.last_sequence, - encoder, + encoder: unsafe { &mut *encoder }, no_rollup: is_true(ctx.no_rollup), timing, } diff --git a/src/rust/src/decoder/service_decoder.rs b/src/rust/src/decoder/service_decoder.rs index 599db619c..e4259171a 100644 --- a/src/rust/src/decoder/service_decoder.rs +++ b/src/rust/src/decoder/service_decoder.rs @@ -882,15 +882,19 @@ impl dtvcc_service_decoder { tv.cc_count += 1; let sn = tv.service_number; let writer_ctx = &mut encoder.dtvcc_writers[(sn - 1) as usize]; - tv.update_time_hide(timing.get_visible_end(3)); + let transcript_settings = if !encoder.transcript_settings.is_null() { + &*encoder.transcript_settings + } else { + &ccx_encoders_transcript_format::default() + }; let mut writer = Writer::new( &mut encoder.cea_708_counter, encoder.subs_delay, encoder.write_format, writer_ctx, encoder.no_font_color, - &*encoder.transcript_settings, + transcript_settings, encoder.no_bom, ); tv.writer_output(&mut writer).unwrap(); @@ -1185,6 +1189,13 @@ impl dtvcc_service_decoder { } /// Flush the decoder of any remaining subtitles pub fn flush(&self, encoder: &mut encoder_ctx) { + let transcript_settings = unsafe { + if !encoder.transcript_settings.is_null() { + &*encoder.transcript_settings + } else { + &ccx_encoders_transcript_format::default() + } + }; unsafe { let tv = &mut (*self.tv); let sn = tv.service_number; @@ -1196,7 +1207,7 @@ impl dtvcc_service_decoder { encoder.write_format, writer_ctx, encoder.no_font_color, - &*encoder.transcript_settings, + transcript_settings, encoder.no_bom, ); writer.write_done(); @@ -1209,7 +1220,12 @@ impl dtvcc_service_decoder { extern "C" fn ccxr_flush_decoder(dtvcc: *mut dtvcc_ctx, decoder: *mut dtvcc_service_decoder) { debug!("dtvcc_decoder_flush: Flushing decoder"); let timing = unsafe { &mut *((*dtvcc).timing) }; - let encoder = unsafe { &mut *((*dtvcc).encoder as *mut encoder_ctx) }; + let mut encoder = Box::into_raw(Box::new(encoder_ctx::default())); + unsafe { + if !(*dtvcc).encoder.is_null() { + encoder = &mut *((*dtvcc).encoder as *mut encoder_ctx); + } + } let decoder = unsafe { &mut *decoder }; let mut screen_content_changed = false; @@ -1222,10 +1238,13 @@ extern "C" fn ccxr_flush_decoder(dtvcc: *mut dtvcc_ctx, decoder: *mut dtvcc_serv decoder.windows[i as usize].visible = 0 } } - if screen_content_changed { - decoder.screen_print(encoder, timing); + unsafe { + let encoder_ctx = &mut *encoder; + if screen_content_changed { + decoder.screen_print(encoder_ctx, timing); + } + decoder.flush(encoder_ctx); } - decoder.flush(encoder); } #[cfg(test)] diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 218ff59f6..d6a3111a7 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -1,369 +1,370 @@ -//! Rust library for CCExtractor -//! -//! Currently we are in the process of porting the 708 decoder to rust. See [decoder] - -// Allow C naming style -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -/// CCExtractor C bindings generated by bindgen -#[allow(clippy::all)] -pub mod bindings { - include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -} - -pub mod args; -pub mod common; -pub mod decoder; -pub mod encoder; -#[cfg(feature = "hardsubx_ocr")] -pub mod hardsubx; -pub mod libccxr_exports; -pub mod parser; -pub mod utils; - -#[cfg(windows)] -use std::os::windows::io::{FromRawHandle, RawHandle}; - -use args::Args; -use bindings::*; -use cfg_if::cfg_if; -use clap::{error::ErrorKind, Parser}; -use common::{copy_from_rust, CType, CType2}; -use decoder::Dtvcc; -use lib_ccxr::{common::Options, teletext::TeletextConfig, util::log::ExitCause}; -use parser::OptionsExt; -use utils::is_true; - -use env_logger::{builder, Target}; -use log::{warn, LevelFilter}; -use std::{ - ffi::CStr, - io::Write, - os::raw::{c_char, c_double, c_int, c_long, c_uint}, -}; - -// Mock data for rust unit tests -cfg_if! { - if #[cfg(test)] { - static mut cb_708: c_int = 0; - static mut cb_field1: c_int = 0; - static mut cb_field2: c_int = 0; - static mut current_fps: c_double = 30.0; - static mut usercolor_rgb: [c_int; 8] = [0; 8]; - static mut FILEBUFFERSIZE: c_int = 0; - static mut MPEG_CLOCK_FREQ: c_int = 90000; - - static mut frames_since_ref_time: c_int = 0; - static mut total_frames_count: c_uint = 0; - static mut fts_at_gop_start: c_long = 0; - static mut gop_rollover: c_int = 0; - static mut pts_big_change: c_uint = 0; - - static mut tlt_config: ccx_s_teletext_config = unsafe { std::mem::zeroed() }; - static mut ccx_options: ccx_s_options = unsafe { std::mem::zeroed() }; - static mut gop_time: gop_time_code = unsafe { std::mem::zeroed() }; - static mut first_gop_time: gop_time_code = unsafe { std::mem::zeroed() }; - static mut ccx_common_timing_settings: ccx_common_timing_settings_t = unsafe { std::mem::zeroed() }; - static mut capitalization_list: word_list = unsafe { std::mem::zeroed() }; - static mut profane: word_list = unsafe { std::mem::zeroed() }; - - unsafe extern "C" fn version(_location: *const c_char) {} - unsafe extern "C" fn set_binary_mode() {} - } -} - -// External C symbols (only when not testing) -#[cfg(not(test))] -extern "C" { - static mut cb_708: c_int; - static mut cb_field1: c_int; - static mut cb_field2: c_int; - static mut current_fps: c_double; - static mut usercolor_rgb: [c_int; 8]; - static mut FILEBUFFERSIZE: c_int; - static mut MPEG_CLOCK_FREQ: c_int; - static mut tlt_config: ccx_s_teletext_config; - static mut ccx_options: ccx_s_options; - static mut frames_since_ref_time: c_int; - static mut total_frames_count: c_uint; - static mut gop_time: gop_time_code; - static mut first_gop_time: gop_time_code; - static mut fts_at_gop_start: c_long; - static mut gop_rollover: c_int; - static mut ccx_common_timing_settings: ccx_common_timing_settings_t; - static mut capitalization_list: word_list; - static mut profane: word_list; - static mut pts_big_change: c_uint; - - fn version(location: *const c_char); - fn set_binary_mode(); -} - -/// Initialize env logger with custom format, using stdout as target -#[no_mangle] -pub extern "C" fn ccxr_init_logger() { - builder() - .format(|buf, record| writeln!(buf, "[CEA-708] {}", record.args())) - .filter_level(LevelFilter::Debug) - .target(Target::Stdout) - .init(); -} - -/// Process cc_data -/// -/// # Safety -/// dec_ctx should not be a null pointer -/// data should point to cc_data of length cc_count -#[no_mangle] -extern "C" fn ccxr_process_cc_data( - dec_ctx: *mut lib_cc_decode, - data: *const ::std::os::raw::c_uchar, - cc_count: c_int, -) -> c_int { - let mut ret = -1; - let mut cc_data: Vec = (0..cc_count * 3) - .map(|x| unsafe { *data.add(x as usize) }) - .collect(); - let dec_ctx = unsafe { &mut *dec_ctx }; - let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc }; - let mut dtvcc = Dtvcc::new(dtvcc_ctx); - for cc_block in cc_data.chunks_exact_mut(3) { - if !validate_cc_pair(cc_block) { - continue; - } - let success = do_cb(dec_ctx, &mut dtvcc, cc_block); - if success { - ret = 0; - } - } - ret -} - -/// Returns `true` if cc_block pair is valid -/// -/// For CEA-708 data, only cc_valid is checked -/// For CEA-608 data, parity is also checked -pub fn validate_cc_pair(cc_block: &mut [u8]) -> bool { - let cc_valid = (cc_block[0] & 4) >> 2; - let cc_type = cc_block[0] & 3; - if cc_valid == 0 { - return false; - } - if cc_type == 0 || cc_type == 1 { - // For CEA-608 data we verify parity. - if verify_parity(cc_block[2]) { - // If the second byte doesn't pass parity, ignore pair - return false; - } - if verify_parity(cc_block[1]) { - // If the first byte doesn't pass parity, - // we replace it with a solid blank and process the pair. - cc_block[1] = 0x7F; - } - } - true -} - -/// Returns `true` if data has odd parity -/// -/// CC uses odd parity (i.e., # of 1's in byte is odd.) -pub fn verify_parity(data: u8) -> bool { - if data.count_ones() & 1 == 1 { - return true; - } - false -} - -/// Process CC data according to its type -pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> bool { - let cc_valid = (cc_block[0] & 4) >> 2; - let cc_type = cc_block[0] & 3; - let mut timeok = true; - - if ctx.write_format != ccx_output_format::CCX_OF_DVDRAW - && ctx.write_format != ccx_output_format::CCX_OF_RAW - && (cc_block[0] == 0xFA || cc_block[0] == 0xFC || cc_block[0] == 0xFD) - && (cc_block[1] & 0x7F) == 0 - && (cc_block[2] & 0x7F) == 0 - { - return true; - } - - if cc_valid == 1 || cc_type == 3 { - ctx.cc_stats[cc_type as usize] += 1; - match cc_type { - // Type 0 and 1 are for CEA-608 data. Handled by C code, do nothing - 0 | 1 => {} - // Type 2 and 3 are for CEA-708 data. - 2 | 3 => { - let current_time = if ctx.timing.is_null() { - 0 - } else { - unsafe { (*ctx.timing).get_fts(ctx.current_field as u8) } - }; - ctx.current_field = 3; - - // Check whether current time is within start and end bounds - if is_true(ctx.extraction_start.set) - && current_time < ctx.extraction_start.time_in_ms - { - timeok = false; - } - if is_true(ctx.extraction_end.set) && current_time > ctx.extraction_end.time_in_ms { - timeok = false; - ctx.processed_enough = 1; - } - - if timeok && ctx.write_format != ccx_output_format::CCX_OF_RAW { - dtvcc.process_cc_data(cc_valid, cc_type, cc_block[1], cc_block[2]); - } - unsafe { cb_708 += 1 } - } - _ => warn!("Invalid cc_type"), - } - } - true -} - -#[cfg(windows)] -#[no_mangle] -extern "C" fn ccxr_close_handle(handle: RawHandle) { - use std::fs::File; - - if handle.is_null() { - return; - } - unsafe { - // File will close automatically (due to Drop) once it goes out of scope - let _file = File::from_raw_handle(handle); - } -} - -/// # Safety -/// Safe if argv is a valid pointer -/// -/// Parse parameters from argv and argc -#[no_mangle] -pub unsafe extern "C" fn ccxr_parse_parameters(argc: c_int, argv: *mut *mut c_char) -> c_int { - // Convert argv to Vec and pass it to parse_parameters - let args = std::slice::from_raw_parts(argv, argc as usize) - .iter() - .map(|&arg| { - CStr::from_ptr(arg) - .to_str() - .expect("Invalid UTF-8 sequence in argument") - .to_owned() - }) - .collect::>(); - - if args.len() <= 1 { - return ExitCause::NoInputFiles.exit_code(); - } - - let args: Args = match Args::try_parse_from(args) { - Ok(args) => args, - Err(e) => { - // Not all errors are actual errors, some are just help or version - // So handle them accordingly - match e.kind() { - ErrorKind::DisplayHelp => { - // Print the help string - println!("{e}"); - return ExitCause::WithHelp.exit_code(); - } - ErrorKind::DisplayVersion => { - version(*argv); - return ExitCause::WithHelp.exit_code(); - } - ErrorKind::UnknownArgument => { - println!("Unknown Argument"); - println!("{e}"); - return ExitCause::MalformedParameter.exit_code(); - } - _ => { - println!("{e}"); - return ExitCause::Failure.exit_code(); - } - } - } - }; - - let mut _capitalization_list: Vec = Vec::new(); - let mut _profane: Vec = Vec::new(); - - let mut opt = Options::default(); - let mut _tlt_config = TeletextConfig::default(); - - opt.parse_parameters( - &args, - &mut _tlt_config, - &mut _capitalization_list, - &mut _profane, - ); - tlt_config = _tlt_config.to_ctype(&opt); - - // Convert the rust struct (CcxOptions) to C struct (ccx_s_options), so that it can be used by the C code - copy_from_rust(&raw mut ccx_options, opt); - - if !_capitalization_list.is_empty() { - capitalization_list = _capitalization_list.to_ctype(); - } - if !_profane.is_empty() { - profane = _profane.to_ctype(); - } - - ExitCause::Ok.exit_code() -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_verify_parity() { - // Odd parity - assert!(verify_parity(0b1010001)); - - // Even parity - assert!(!verify_parity(0b1000001)); - } - - #[test] - fn test_validate_cc_pair() { - // Valid CEA-708 data - let mut cc_block = [0x97, 0x1F, 0x3C]; - assert!(validate_cc_pair(&mut cc_block)); - - // Invalid CEA-708 data - let mut cc_block = [0x93, 0x1F, 0x3C]; - assert!(!validate_cc_pair(&mut cc_block)); - - // Valid CEA-608 data - let mut cc_block = [0x15, 0x2F, 0x7D]; - assert!(validate_cc_pair(&mut cc_block)); - // Check for replaced bit when 1st byte doesn't pass parity - assert_eq!(cc_block[1], 0x7F); - - // Invalid CEA-608 data - let mut cc_block = [0x15, 0x2F, 0x5E]; - assert!(!validate_cc_pair(&mut cc_block)); - } - - #[test] - fn test_do_cb() { - let mut dtvcc_ctx = crate::decoder::test::initialize_dtvcc_ctx(); - - let mut dtvcc = Dtvcc::new(&mut dtvcc_ctx); - - let mut decoder_ctx = lib_cc_decode::default(); - let cc_block = [0x97, 0x1F, 0x3C]; - - assert!(do_cb(&mut decoder_ctx, &mut dtvcc, &cc_block)); - assert_eq!(decoder_ctx.current_field, 3); - assert_eq!(decoder_ctx.cc_stats[3], 1); - assert_eq!(decoder_ctx.processed_enough, 0); - assert_eq!(unsafe { cb_708 }, 11); - } -} +//! Rust library for CCExtractor +//! +//! Currently we are in the process of porting the 708 decoder to rust. See [decoder] + +// Allow C naming style +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +/// CCExtractor C bindings generated by bindgen +#[allow(clippy::all)] +pub mod bindings { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} + +pub mod args; +pub mod avc; +pub mod common; +pub mod decoder; +pub mod encoder; +#[cfg(feature = "hardsubx_ocr")] +pub mod hardsubx; +pub mod libccxr_exports; +pub mod parser; +pub mod utils; + +#[cfg(windows)] +use std::os::windows::io::{FromRawHandle, RawHandle}; + +use args::Args; +use bindings::*; +use cfg_if::cfg_if; +use clap::{error::ErrorKind, Parser}; +use common::{copy_from_rust, CType, CType2}; +use decoder::Dtvcc; +use lib_ccxr::{common::Options, teletext::TeletextConfig, util::log::ExitCause}; +use parser::OptionsExt; +use utils::is_true; + +use env_logger::{builder, Target}; +use log::{warn, LevelFilter}; +use std::{ + ffi::CStr, + io::Write, + os::raw::{c_char, c_double, c_int, c_long, c_uint}, +}; + +// Mock data for rust unit tests +cfg_if! { + if #[cfg(test)] { + static mut cb_708: c_int = 0; + static mut cb_field1: c_int = 0; + static mut cb_field2: c_int = 0; + static mut current_fps: c_double = 30.0; + static mut usercolor_rgb: [c_int; 8] = [0; 8]; + static mut FILEBUFFERSIZE: c_int = 0; + static mut MPEG_CLOCK_FREQ: c_int = 90000; + + static mut frames_since_ref_time: c_int = 0; + static mut total_frames_count: c_uint = 0; + static mut fts_at_gop_start: c_long = 0; + static mut gop_rollover: c_int = 0; + static mut pts_big_change: c_uint = 0; + + static mut tlt_config: ccx_s_teletext_config = unsafe { std::mem::zeroed() }; + static mut ccx_options: ccx_s_options = unsafe { std::mem::zeroed() }; + static mut gop_time: gop_time_code = unsafe { std::mem::zeroed() }; + static mut first_gop_time: gop_time_code = unsafe { std::mem::zeroed() }; + static mut ccx_common_timing_settings: ccx_common_timing_settings_t = unsafe { std::mem::zeroed() }; + static mut capitalization_list: word_list = unsafe { std::mem::zeroed() }; + static mut profane: word_list = unsafe { std::mem::zeroed() }; + + unsafe extern "C" fn version(_location: *const c_char) {} + unsafe extern "C" fn set_binary_mode() {} + } +} + +// External C symbols (only when not testing) +#[cfg(not(test))] +extern "C" { + static mut cb_708: c_int; + static mut cb_field1: c_int; + static mut cb_field2: c_int; + static mut current_fps: c_double; + static mut usercolor_rgb: [c_int; 8]; + static mut FILEBUFFERSIZE: c_int; + static mut MPEG_CLOCK_FREQ: c_int; + static mut tlt_config: ccx_s_teletext_config; + static mut ccx_options: ccx_s_options; + static mut frames_since_ref_time: c_int; + static mut total_frames_count: c_uint; + static mut gop_time: gop_time_code; + static mut first_gop_time: gop_time_code; + static mut fts_at_gop_start: c_long; + static mut gop_rollover: c_int; + static mut ccx_common_timing_settings: ccx_common_timing_settings_t; + static mut capitalization_list: word_list; + static mut profane: word_list; + static mut pts_big_change: c_uint; + + fn version(location: *const c_char); + fn set_binary_mode(); +} + +/// Initialize env logger with custom format, using stdout as target +#[no_mangle] +pub extern "C" fn ccxr_init_logger() { + builder() + .format(|buf, record| writeln!(buf, "[CEA-708] {}", record.args())) + .filter_level(LevelFilter::Debug) + .target(Target::Stdout) + .init(); +} + +/// Process cc_data +/// +/// # Safety +/// dec_ctx should not be a null pointer +/// data should point to cc_data of length cc_count +#[no_mangle] +extern "C" fn ccxr_process_cc_data( + dec_ctx: *mut lib_cc_decode, + data: *const ::std::os::raw::c_uchar, + cc_count: c_int, +) -> c_int { + let mut ret = -1; + let mut cc_data: Vec = (0..cc_count * 3) + .map(|x| unsafe { *data.add(x as usize) }) + .collect(); + let dec_ctx = unsafe { &mut *dec_ctx }; + let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc }; + let mut dtvcc = Dtvcc::new(dtvcc_ctx); + for cc_block in cc_data.chunks_exact_mut(3) { + if !validate_cc_pair(cc_block) { + continue; + } + let success = do_cb(dec_ctx, &mut dtvcc, cc_block); + if success { + ret = 0; + } + } + ret +} + +/// Returns `true` if cc_block pair is valid +/// +/// For CEA-708 data, only cc_valid is checked +/// For CEA-608 data, parity is also checked +pub fn validate_cc_pair(cc_block: &mut [u8]) -> bool { + let cc_valid = (cc_block[0] & 4) >> 2; + let cc_type = cc_block[0] & 3; + if cc_valid == 0 { + return false; + } + if cc_type == 0 || cc_type == 1 { + // For CEA-608 data we verify parity. + if verify_parity(cc_block[2]) { + // If the second byte doesn't pass parity, ignore pair + return false; + } + if verify_parity(cc_block[1]) { + // If the first byte doesn't pass parity, + // we replace it with a solid blank and process the pair. + cc_block[1] = 0x7F; + } + } + true +} + +/// Returns `true` if data has odd parity +/// +/// CC uses odd parity (i.e., # of 1's in byte is odd.) +pub fn verify_parity(data: u8) -> bool { + if data.count_ones() & 1 == 1 { + return true; + } + false +} + +/// Process CC data according to its type +pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> bool { + let cc_valid = (cc_block[0] & 4) >> 2; + let cc_type = cc_block[0] & 3; + let mut timeok = true; + + if ctx.write_format != ccx_output_format::CCX_OF_DVDRAW + && ctx.write_format != ccx_output_format::CCX_OF_RAW + && (cc_block[0] == 0xFA || cc_block[0] == 0xFC || cc_block[0] == 0xFD) + && (cc_block[1] & 0x7F) == 0 + && (cc_block[2] & 0x7F) == 0 + { + return true; + } + + if cc_valid == 1 || cc_type == 3 { + ctx.cc_stats[cc_type as usize] += 1; + match cc_type { + // Type 0 and 1 are for CEA-608 data. Handled by C code, do nothing + 0 | 1 => {} + // Type 2 and 3 are for CEA-708 data. + 2 | 3 => { + let current_time = if ctx.timing.is_null() { + 0 + } else { + unsafe { (*ctx.timing).get_fts(ctx.current_field as u8) } + }; + ctx.current_field = 3; + + // Check whether current time is within start and end bounds + if is_true(ctx.extraction_start.set) + && current_time < ctx.extraction_start.time_in_ms + { + timeok = false; + } + if is_true(ctx.extraction_end.set) && current_time > ctx.extraction_end.time_in_ms { + timeok = false; + ctx.processed_enough = 1; + } + + if timeok && ctx.write_format != ccx_output_format::CCX_OF_RAW { + dtvcc.process_cc_data(cc_valid, cc_type, cc_block[1], cc_block[2]); + } + unsafe { cb_708 += 1 } + } + _ => warn!("Invalid cc_type"), + } + } + true +} + +#[cfg(windows)] +#[no_mangle] +extern "C" fn ccxr_close_handle(handle: RawHandle) { + use std::fs::File; + + if handle.is_null() { + return; + } + unsafe { + // File will close automatically (due to Drop) once it goes out of scope + let _file = File::from_raw_handle(handle); + } +} + +/// # Safety +/// Safe if argv is a valid pointer +/// +/// Parse parameters from argv and argc +#[no_mangle] +pub unsafe extern "C" fn ccxr_parse_parameters(argc: c_int, argv: *mut *mut c_char) -> c_int { + // Convert argv to Vec and pass it to parse_parameters + let args = std::slice::from_raw_parts(argv, argc as usize) + .iter() + .map(|&arg| { + CStr::from_ptr(arg) + .to_str() + .expect("Invalid UTF-8 sequence in argument") + .to_owned() + }) + .collect::>(); + + if args.len() <= 1 { + return ExitCause::NoInputFiles.exit_code(); + } + + let args: Args = match Args::try_parse_from(args) { + Ok(args) => args, + Err(e) => { + // Not all errors are actual errors, some are just help or version + // So handle them accordingly + match e.kind() { + ErrorKind::DisplayHelp => { + // Print the help string + println!("{e}"); + return ExitCause::WithHelp.exit_code(); + } + ErrorKind::DisplayVersion => { + version(*argv); + return ExitCause::WithHelp.exit_code(); + } + ErrorKind::UnknownArgument => { + println!("Unknown Argument"); + println!("{e}"); + return ExitCause::MalformedParameter.exit_code(); + } + _ => { + println!("{e}"); + return ExitCause::Failure.exit_code(); + } + } + } + }; + + let mut _capitalization_list: Vec = Vec::new(); + let mut _profane: Vec = Vec::new(); + + let mut opt = Options::default(); + let mut _tlt_config = TeletextConfig::default(); + + opt.parse_parameters( + &args, + &mut _tlt_config, + &mut _capitalization_list, + &mut _profane, + ); + tlt_config = _tlt_config.to_ctype(&opt); + + // Convert the rust struct (CcxOptions) to C struct (ccx_s_options), so that it can be used by the C code + copy_from_rust(&raw mut ccx_options, opt); + + if !_capitalization_list.is_empty() { + capitalization_list = _capitalization_list.to_ctype(); + } + if !_profane.is_empty() { + profane = _profane.to_ctype(); + } + + ExitCause::Ok.exit_code() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_verify_parity() { + // Odd parity + assert!(verify_parity(0b1010001)); + + // Even parity + assert!(!verify_parity(0b1000001)); + } + + #[test] + fn test_validate_cc_pair() { + // Valid CEA-708 data + let mut cc_block = [0x97, 0x1F, 0x3C]; + assert!(validate_cc_pair(&mut cc_block)); + + // Invalid CEA-708 data + let mut cc_block = [0x93, 0x1F, 0x3C]; + assert!(!validate_cc_pair(&mut cc_block)); + + // Valid CEA-608 data + let mut cc_block = [0x15, 0x2F, 0x7D]; + assert!(validate_cc_pair(&mut cc_block)); + // Check for replaced bit when 1st byte doesn't pass parity + assert_eq!(cc_block[1], 0x7F); + + // Invalid CEA-608 data + let mut cc_block = [0x15, 0x2F, 0x5E]; + assert!(!validate_cc_pair(&mut cc_block)); + } + + #[test] + fn test_do_cb() { + let mut dtvcc_ctx = crate::decoder::test::initialize_dtvcc_ctx(); + + let mut dtvcc = Dtvcc::new(&mut dtvcc_ctx); + + let mut decoder_ctx = lib_cc_decode::default(); + let cc_block = [0x97, 0x1F, 0x3C]; + + assert!(do_cb(&mut decoder_ctx, &mut dtvcc, &cc_block)); + assert_eq!(decoder_ctx.current_field, 3); + assert_eq!(decoder_ctx.cc_stats[3], 1); + assert_eq!(decoder_ctx.processed_enough, 0); + assert_eq!(unsafe { cb_708 }, 11); + } +} From c2d36a9e1aec187b70d90422f70d265fd02b3489 Mon Sep 17 00:00:00 2001 From: Deepnarayan Sett Date: Sun, 17 Aug 2025 11:57:58 +0530 Subject: [PATCH 2/6] AVC module: Minor semantic changes --- src/rust/src/avc/common_types.rs | 109 +++++++++-------- src/rust/src/avc/core.rs | 47 ++----- src/rust/src/avc/nal.rs | 202 +++++++++++++++---------------- 3 files changed, 164 insertions(+), 194 deletions(-) diff --git a/src/rust/src/avc/common_types.rs b/src/rust/src/avc/common_types.rs index fea08cfe2..e0f6967d1 100644 --- a/src/rust/src/avc/common_types.rs +++ b/src/rust/src/avc/common_types.rs @@ -5,62 +5,7 @@ use crate::common::CType; pub const ZEROBYTES_SHORTSTARTCODE: i32 = 2; pub const AVC_CC_DATA_INITIAL_SIZE: usize = 1024; pub const MAXBFRAMES: i32 = 50; -// NAL unit types from H.264 standard -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum NalUnitType { - Unspecified = 0, - CodedSliceNonIdr = 1, - CodedSliceDataPartitionA = 2, - CodedSliceDataPartitionB = 3, - CodedSliceDataPartitionC = 4, - CodedSliceIdr = 5, - Sei = 6, - SequenceParameterSet = 7, - PictureParameterSet = 8, - AccessUnitDelimiter = 9, - EndOfSequence = 10, - EndOfStream = 11, - FillerData = 12, - SequenceParameterSetExtension = 13, - PrefixNalUnit = 14, - SubsetSequenceParameterSet = 15, - DepthParameterSet = 16, - // 17-18 reserved - CodedSliceAuxiliary = 19, - CodedSliceExtension = 20, - // 21-23 reserved - // 24-31 unspecified -} - -impl From for NalUnitType { - fn from(value: u8) -> Self { - match value { - 0 => NalUnitType::Unspecified, - 1 => NalUnitType::CodedSliceNonIdr, - 2 => NalUnitType::CodedSliceDataPartitionA, - 3 => NalUnitType::CodedSliceDataPartitionB, - 4 => NalUnitType::CodedSliceDataPartitionC, - 5 => NalUnitType::CodedSliceIdr, - 6 => NalUnitType::Sei, - 7 => NalUnitType::SequenceParameterSet, - 8 => NalUnitType::PictureParameterSet, - 9 => NalUnitType::AccessUnitDelimiter, - 10 => NalUnitType::EndOfSequence, - 11 => NalUnitType::EndOfStream, - 12 => NalUnitType::FillerData, - 13 => NalUnitType::SequenceParameterSetExtension, - 14 => NalUnitType::PrefixNalUnit, - 15 => NalUnitType::SubsetSequenceParameterSet, - 16 => NalUnitType::DepthParameterSet, - 19 => NalUnitType::CodedSliceAuxiliary, - 20 => NalUnitType::CodedSliceExtension, - _ => NalUnitType::Unspecified, - } - } -} -// Rustified version of the avc_ctx struct #[derive(Debug, Clone)] pub struct AvcContextRust { pub cc_count: u8, @@ -310,3 +255,57 @@ impl From for SeiPayloadType { } } } +// NAL unit types from H.264 standard +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum NalUnitType { + Unspecified = 0, + CodedSliceNonIdr = 1, + CodedSliceDataPartitionA = 2, + CodedSliceDataPartitionB = 3, + CodedSliceDataPartitionC = 4, + CodedSliceIdr = 5, + Sei = 6, + SequenceParameterSet = 7, + PictureParameterSet = 8, + AccessUnitDelimiter = 9, + EndOfSequence = 10, + EndOfStream = 11, + FillerData = 12, + SequenceParameterSetExtension = 13, + PrefixNalUnit = 14, + SubsetSequenceParameterSet = 15, + DepthParameterSet = 16, + // 17-18 reserved + CodedSliceAuxiliary = 19, + CodedSliceExtension = 20, + // 21-23 reserved + // 24-31 unspecified +} + +impl From for NalUnitType { + fn from(value: u8) -> Self { + match value { + 0 => NalUnitType::Unspecified, + 1 => NalUnitType::CodedSliceNonIdr, + 2 => NalUnitType::CodedSliceDataPartitionA, + 3 => NalUnitType::CodedSliceDataPartitionB, + 4 => NalUnitType::CodedSliceDataPartitionC, + 5 => NalUnitType::CodedSliceIdr, + 6 => NalUnitType::Sei, + 7 => NalUnitType::SequenceParameterSet, + 8 => NalUnitType::PictureParameterSet, + 9 => NalUnitType::AccessUnitDelimiter, + 10 => NalUnitType::EndOfSequence, + 11 => NalUnitType::EndOfStream, + 12 => NalUnitType::FillerData, + 13 => NalUnitType::SequenceParameterSetExtension, + 14 => NalUnitType::PrefixNalUnit, + 15 => NalUnitType::SubsetSequenceParameterSet, + 16 => NalUnitType::DepthParameterSet, + 19 => NalUnitType::CodedSliceAuxiliary, + 20 => NalUnitType::CodedSliceExtension, + _ => NalUnitType::Unspecified, + } + } +} diff --git a/src/rust/src/avc/core.rs b/src/rust/src/avc/core.rs index bd373fb54..d0f434ef6 100644 --- a/src/rust/src/avc/core.rs +++ b/src/rust/src/avc/core.rs @@ -3,36 +3,18 @@ use crate::avc::nal::*; use crate::avc::sei::*; use crate::avc::FromCType; use crate::bindings::{cc_subtitle, encoder_ctx, lib_cc_decode, realloc}; +use crate::libccxr_exports::time::ccxr_set_fts; use lib_ccxr::common::AvcNalType; use lib_ccxr::util::log::DebugMessageFlag; use lib_ccxr::{debug, info}; use std::os::raw::c_void; use std::slice; -/// Portable round function replacement for roundportable in C +/// Portable round function pub fn round_portable(x: f64) -> f64 { (x + 0.5).floor() } -/// Initialize AVC context in Rust -pub fn init_avc_rust() -> AvcContextRust { - AvcContextRust::default() -} - -/// Deinitialize AVC context and print statistics if needed -/// # Safety -/// This function is unsafe because it operates on raw pointers -pub unsafe fn dinit_avc_rust(ctx: &mut AvcContextRust) { - if ctx.ccblocks_in_avc_lost > 0 { - info!( - "Total caption blocks received: {}\n", - ctx.ccblocks_in_avc_total - ); - info!("Total caption blocks lost: {}\n", ctx.ccblocks_in_avc_lost); - } - // Vec will be automatically dropped -} - /// Process NAL unit data /// # Safety /// This function is unsafe because it processes raw NAL data @@ -64,7 +46,6 @@ pub unsafe fn do_nal( // Get the working slice (skip first byte for remove_03emu) let mut working_buffer = nal_start[1..original_length].to_vec(); - // Call remove_03emu equivalent - assuming you have a Rust version let processed_length = match remove_03emu(&mut working_buffer) { Some(len) => len, None => { @@ -79,13 +60,13 @@ pub unsafe fn do_nal( // Truncate buffer to actual processed length working_buffer.truncate(processed_length); - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!( + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "BEGIN NAL unit type: {:?} length {} ref_idc: {} - Buffered captions before: {}", nal_unit_type, working_buffer.len(), (*dec_ctx.avc_ctx).nal_ref_idc, if (*dec_ctx.avc_ctx).cc_buffer_saved != 0 { 0 } else { 1 } - )); + ); match nal_unit_type { AvcNalType::AccessUnitDelimiter9 => { @@ -98,11 +79,9 @@ pub unsafe fn do_nal( // We need this to parse NAL type 1 (CodedSliceNonIdrPicture1) (*dec_ctx.avc_ctx).num_nal_unit_type_7 += 1; - // Convert to Rust context, process, then update C context let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); seq_parameter_set_rbsp(&mut ctx_rust, &working_buffer)?; - // Update essential fields in C context (*dec_ctx.avc_ctx).seq_parameter_set_id = ctx_rust.seq_parameter_set_id; (*dec_ctx.avc_ctx).log2_max_frame_num = ctx_rust.log2_max_frame_num; (*dec_ctx.avc_ctx).pic_order_cnt_type = ctx_rust.pic_order_cnt_type; @@ -126,17 +105,14 @@ pub unsafe fn do_nal( AvcNalType::Sei if (*dec_ctx.avc_ctx).got_seq_para != 0 => { // Found SEI (used for subtitles) - // set_fts(ctx->timing); // FIXME - check this!!! + ccxr_set_fts(enc_ctx.timing); - // Convert to Rust context, process, then update C context let mut ctx_rust = AvcContextRust::from_ctype(*dec_ctx.avc_ctx).unwrap(); let old_cc_count = ctx_rust.cc_count; sei_rbsp(&mut ctx_rust, &working_buffer); - // If new subtitle data was found, update the C context directly if ctx_rust.cc_count > old_cc_count { - // Make sure C context has enough space let required_size = (ctx_rust.cc_count as usize * 3) + 1; if required_size > (*dec_ctx.avc_ctx).cc_databufsize as usize { let new_size = required_size * 2; // Some headroom @@ -149,9 +125,8 @@ pub unsafe fn do_nal( (*dec_ctx.avc_ctx).cc_databufsize = new_size as i64; } - // Copy the data directly to C context if !(*dec_ctx.avc_ctx).cc_data.is_null() { - let c_data = std::slice::from_raw_parts_mut( + let c_data = slice::from_raw_parts_mut( (*dec_ctx.avc_ctx).cc_data, (*dec_ctx.avc_ctx).cc_databufsize as usize, ); @@ -177,14 +152,13 @@ pub unsafe fn do_nal( } } - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!( + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "END NAL unit type: {:?} length {} ref_idc: {} - Buffered captions after: {}", - &nal_unit_type, + nal_unit_type, working_buffer.len(), (*dec_ctx.avc_ctx).nal_ref_idc, if (*dec_ctx.avc_ctx).cc_buffer_saved != 0 { 0 } else { 1 } - )); - + ); Ok(()) } @@ -491,8 +465,7 @@ pub unsafe fn process_avc( (*dec_ctx.avc_ctx).nal_ref_idc = (avcbuf[nal_start_pos] >> 5) as u32; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("process_avc: zeropad {}", zeropad)); - + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "process_avc: zeropad {}", zeropad); let nal_length = (nal_stop_pos - nal_start_pos) as i64; let mut nal_slice = avcbuf[nal_start_pos..nal_stop_pos].to_vec(); diff --git a/src/rust/src/avc/nal.rs b/src/rust/src/avc/nal.rs index 018ba6dda..9c5fc5dbf 100644 --- a/src/rust/src/avc/nal.rs +++ b/src/rust/src/avc/nal.rs @@ -19,38 +19,38 @@ pub fn seq_parameter_set_rbsp( ) -> Result<(), BitstreamError> { // Calculate buffer length from pointer difference let mut q1 = BitStreamRust::new(seqbuf)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "SEQUENCE PARAMETER SET (bitlen: {})\n", q1.bits_left); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "SEQUENCE PARAMETER SET (bitlen: {})", q1.bits_left); let tmp = q1.read_bits(8)?; let profile_idc = tmp; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "profile_idc= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "profile_idc= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set0_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set0_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set1_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set1_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set2_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set2_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set3_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set3_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set4_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set4_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set5_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "constraint_set5_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(2)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "reserved= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "reserved= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(8)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "level_idc= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "level_idc= {:4} ({:#X})", tmp, tmp); ctx.seq_parameter_set_id = q1.read_exp_golomb_unsigned()? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_parameter_set_id= {:4} ({:#X})\n", ctx.seq_parameter_set_id, ctx.seq_parameter_set_id); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_parameter_set_id= {:4} ({:#X})", ctx.seq_parameter_set_id, ctx.seq_parameter_set_id); if profile_idc == 100 || profile_idc == 110 @@ -63,30 +63,30 @@ pub fn seq_parameter_set_rbsp( || profile_idc == 128 { let chroma_format_idc = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_format_idc= {:4} ({:#X})\n", chroma_format_idc, chroma_format_idc); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_format_idc= {:4} ({:#X})", chroma_format_idc, chroma_format_idc); if chroma_format_idc == 3 { let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "separate_colour_plane_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "separate_colour_plane_flag= {:4} ({:#X})", tmp, tmp); } let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bit_depth_luma_minus8= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bit_depth_luma_minus8= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bit_depth_chroma_minus8= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bit_depth_chroma_minus8= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "qpprime_y_zero_transform_bypass_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "qpprime_y_zero_transform_bypass_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_scaling_matrix_present_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_scaling_matrix_present_flag= {:4} ({:#X})", tmp, tmp); if tmp == 1 { // WVI: untested, just copied from specs. for i in 0..if chroma_format_idc != 3 { 8 } else { 12 } { let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_scaling_list_present_flag[{}]= {:4} ({:#X})\n", i, tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "seq_scaling_list_present_flag[{}]= {:4} ({:#X})", i, tmp, tmp); if tmp != 0 { // We use a "dummy"/slimmed-down replacement here. Actual/full code can be found in the spec (ISO/IEC 14496-10:2012(E)) chapter 7.3.2.1.1.1 - Scaling list syntax @@ -131,163 +131,163 @@ pub fn seq_parameter_set_rbsp( } ctx.log2_max_frame_num = q1.read_exp_golomb_unsigned()? as i32; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "log2_max_frame_num4_minus4= {:4} ({:#X})\n", ctx.log2_max_frame_num, ctx.log2_max_frame_num); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "log2_max_frame_num4_minus4= {:4} ({:#X})", ctx.log2_max_frame_num, ctx.log2_max_frame_num); ctx.log2_max_frame_num += 4; // 4 is added due to the formula. ctx.pic_order_cnt_type = q1.read_exp_golomb_unsigned()? as i32; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_order_cnt_type= {:4} ({:#X})\n", ctx.pic_order_cnt_type, ctx.pic_order_cnt_type); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_order_cnt_type= {:4} ({:#X})", ctx.pic_order_cnt_type, ctx.pic_order_cnt_type); if ctx.pic_order_cnt_type == 0 { ctx.log2_max_pic_order_cnt_lsb = q1.read_exp_golomb_unsigned()? as i32; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "log2_max_pic_order_cnt_lsb_minus4= {:4} ({:#X})\n", ctx.log2_max_pic_order_cnt_lsb, ctx.log2_max_pic_order_cnt_lsb); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "log2_max_pic_order_cnt_lsb_minus4= {:4} ({:#X})", ctx.log2_max_pic_order_cnt_lsb, ctx.log2_max_pic_order_cnt_lsb); ctx.log2_max_pic_order_cnt_lsb += 4; // 4 is added due to formula. } else if ctx.pic_order_cnt_type == 1 { // CFS: Untested, just copied from specs. let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "delta_pic_order_always_zero_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "delta_pic_order_always_zero_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_non_ref_pic= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_non_ref_pic= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_top_to_bottom_field {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_top_to_bottom_field {:4} ({:#X})", tmp, tmp); let num_ref_frame_in_pic_order_cnt_cycle = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "num_ref_frame_in_pic_order_cnt_cycle {:4} ({:#X})\n", num_ref_frame_in_pic_order_cnt_cycle, num_ref_frame_in_pic_order_cnt_cycle); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "num_ref_frame_in_pic_order_cnt_cycle {:4} ({:#X})", num_ref_frame_in_pic_order_cnt_cycle, num_ref_frame_in_pic_order_cnt_cycle); for i in 0..num_ref_frame_in_pic_order_cnt_cycle { let tmp = q1.read_exp_golomb()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_ref_frame [{} / {}] = {:4} ({:#X})\n", i, num_ref_frame_in_pic_order_cnt_cycle, tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "offset_for_ref_frame [{} / {}] = {:4} ({:#X})", i, num_ref_frame_in_pic_order_cnt_cycle, tmp, tmp); } } else { // Nothing needs to be parsed when pic_order_cnt_type == 2 } let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "max_num_ref_frames= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "max_num_ref_frames= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "gaps_in_frame_num_value_allowed_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "gaps_in_frame_num_value_allowed_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_width_in_mbs_minus1= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_width_in_mbs_minus1= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_height_in_map_units_minus1= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_height_in_map_units_minus1= {:4} ({:#X})", tmp, tmp); ctx.frame_mbs_only_flag = q1.read_bits(1)? != 0; if !ctx.frame_mbs_only_flag { let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "mb_adaptive_fr_fi_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "mb_adaptive_fr_fi_flag= {:4} ({:#X})", tmp, tmp); } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "direct_8x8_inference_f= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "direct_8x8_inference_f= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_cropping_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_cropping_flag= {:4} ({:#X})", tmp, tmp); if tmp != 0 { let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_left_offset= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_left_offset= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_right_offset= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_right_offset= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_top_offset= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_top_offset= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_bottom_offset= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_crop_bottom_offset= {:4} ({:#X})", tmp, tmp); } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "vui_parameters_present= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "vui_parameters_present= {:4} ({:#X})", tmp, tmp); if tmp != 0 { - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "\nVUI parameters\n"); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "VUI parameters"); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "aspect_ratio_info_pres= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "aspect_ratio_info_pres= {:4} ({:#X})", tmp, tmp); if tmp != 0 { let tmp = q1.read_bits(8)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "aspect_ratio_idc= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "aspect_ratio_idc= {:4} ({:#X})", tmp, tmp); if tmp == 255 { let tmp = q1.read_bits(16)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "sar_width= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "sar_width= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(16)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "sar_height= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "sar_height= {:4} ({:#X})", tmp, tmp); } } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "overscan_info_pres_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "overscan_info_pres_flag= {:4} ({:#X})", tmp, tmp); if tmp != 0 { let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "overscan_appropriate_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "overscan_appropriate_flag= {:4} ({:#X})", tmp, tmp); } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_signal_type_present_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_signal_type_present_flag= {:4} ({:#X})", tmp, tmp); if tmp != 0 { let tmp = q1.read_bits(3)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_format= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_format= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_full_range_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "video_full_range_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "colour_description_present_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "colour_description_present_flag= {:4} ({:#X})", tmp, tmp); if tmp != 0 { let tmp = q1.read_bits(8)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "colour_primaries= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "colour_primaries= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(8)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "transfer_characteristics= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "transfer_characteristics= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(8)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "matrix_coefficients= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "matrix_coefficients= {:4} ({:#X})", tmp, tmp); } } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_loc_info_present_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_loc_info_present_flag= {:4} ({:#X})", tmp, tmp); if tmp != 0 { let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_sample_loc_type_top_field= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_sample_loc_type_top_field= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_exp_golomb_unsigned()?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_sample_loc_type_bottom_field= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "chroma_sample_loc_type_bottom_field= {:4} ({:#X})", tmp, tmp); } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "timing_info_present_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "timing_info_present_flag= {:4} ({:#X})", tmp, tmp); if tmp != 0 { let tmp = q1.read_bits(32)?; let num_units_in_tick = tmp; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "num_units_in_tick= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "num_units_in_tick= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(32)?; let time_scale = tmp; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "time_scale= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "time_scale= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; let fixed_frame_rate_flag = tmp != 0; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "fixed_frame_rate_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "fixed_frame_rate_flag= {:4} ({:#X})", tmp, tmp); // Change: use num_units_in_tick and time_scale to calculate FPS. (ISO/IEC 14496-10:2012(E), page 397 & further) if fixed_frame_rate_flag { let clock_tick = num_units_in_tick as f64 / time_scale as f64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "clock_tick= {}\n", clock_tick); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "clock_tick= {}", clock_tick); unsafe { current_fps = time_scale as f64 / (2.0 * num_units_in_tick as f64); @@ -296,16 +296,16 @@ pub fn seq_parameter_set_rbsp( } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "nal_hrd_parameters_present_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "nal_hrd_parameters_present_flag= {:4} ({:#X})", tmp, tmp); if tmp != 0 { - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "nal_hrd. Not implemented for now. Hopefully not needed. Skipping rest of NAL\n"); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "nal_hrd. Not implemented for now. Hopefully not needed. Skipping rest of NAL"); ctx.num_nal_hrd += 1; return Ok(()); } let tmp1 = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "vcl_hrd_parameters_present_flag= {:#X}\n", tmp1); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "vcl_hrd_parameters_present_flag= {:#X}", tmp1); if tmp != 0 { // TODO. @@ -317,15 +317,15 @@ pub fn seq_parameter_set_rbsp( if tmp != 0 || tmp1 != 0 { let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "low_delay_hrd_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "low_delay_hrd_flag= {:4} ({:#X})", tmp, tmp); return Ok(()); } let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_struct_present_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_struct_present_flag= {:4} ({:#X})", tmp, tmp); let tmp = q1.read_bits(1)?; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bitstream_restriction_flag= {:4} ({:#X})\n", tmp, tmp); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bitstream_restriction_flag= {:4} ({:#X})", tmp, tmp); // .. // The hope was to find the GOP length in max_dec_frame_buffering, but @@ -366,16 +366,16 @@ pub unsafe fn slice_header( 0 }; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "\nSLICE HEADER\n"); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "SLICE HEADER"); tmp = q1.read_exp_golomb_unsigned()? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("first_mb_in_slice= {:4} ({:#X})", tmp, tmp)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "first_mb_in_slice= {:4} ({:#X})", tmp, tmp); let slice_type: i64 = q1.read_exp_golomb_unsigned()? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("slice_type= {:4X}", slice_type)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "slice_type= {:4X}", slice_type); tmp = q1.read_exp_golomb_unsigned()? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("pic_parameter_set_id= {:4} ({:#X})", tmp, tmp)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_parameter_set_id= {:4} ({:#X})", tmp, tmp); (*dec_ctx.avc_ctx).lastframe_num = (*dec_ctx.avc_ctx).frame_num; let max_frame_num: i32 = (1 << (*dec_ctx.avc_ctx).log2_max_frame_num) - 1; @@ -383,16 +383,16 @@ pub unsafe fn slice_header( // Needs log2_max_frame_num_minus4 + 4 bits (*dec_ctx.avc_ctx).frame_num = q1.read_bits((*dec_ctx.avc_ctx).log2_max_frame_num as u32)? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("frame_num= {:4X}", (*dec_ctx.avc_ctx).frame_num)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "frame_num= {:4X}", (*dec_ctx.avc_ctx).frame_num); if (*dec_ctx.avc_ctx).frame_mbs_only_flag == 0 { field_pic_flag = q1.read_bits(1)? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("field_pic_flag= {:4X}", field_pic_flag)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "field_pic_flag= {:4X}", field_pic_flag); if field_pic_flag != 0 { // bottom_field_flag bottom_field_flag = q1.read_bits(1)? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("bottom_field_flag= {:4X}", bottom_field_flag)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "bottom_field_flag= {:4X}", bottom_field_flag); // When bottom_field_flag is set the video is interlaced, // override current_fps. @@ -400,18 +400,18 @@ pub unsafe fn slice_header( } } - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("ird_pic_flag= {:4}", ird_pic_flag)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "ird_pic_flag= {:4}", ird_pic_flag); if *nal_unit_type == AvcNalType::CodedSliceIdrPicture { tmp = q1.read_exp_golomb_unsigned()? as i64; - debug!( msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("idr_pic_id= {:4} ({:#X})", tmp, tmp)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "idr_pic_id= {:4} ({:#X})", tmp, tmp); // TODO } if (*dec_ctx.avc_ctx).pic_order_cnt_type == 0 { pic_order_cnt_lsb = q1.read_bits((*dec_ctx.avc_ctx).log2_max_pic_order_cnt_lsb as u32)? as i64; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("pic_order_cnt_lsb= {:4X}", pic_order_cnt_lsb)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "pic_order_cnt_lsb= {:4X}", pic_order_cnt_lsb); } if (*dec_ctx.avc_ctx).pic_order_cnt_type == 1 { @@ -455,8 +455,8 @@ pub unsafe fn slice_header( } if (*dec_ctx.avc_ctx).lastframe_num > -1 && !(0..=1).contains(&dif) { (*dec_ctx.avc_ctx).num_jump_in_frames += 1; - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("\nJump in frame numbers ({}/{})\n", - (*dec_ctx.avc_ctx).frame_num, (*dec_ctx.avc_ctx).lastframe_num)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Jump in frame numbers ({}/{})", + (*dec_ctx.avc_ctx).frame_num, (*dec_ctx.avc_ctx).lastframe_num); // This will prohibit setting current_tref on potential jumps. (*dec_ctx.avc_ctx).maxidx = -1; (*dec_ctx.avc_ctx).lastmaxidx = -1; @@ -469,14 +469,14 @@ pub unsafe fn slice_header( // 2014 SugarHouse Casino Mummers Parade Fancy Brigades_new.ts was garbled // Probably doing a proper PTS sort would be a better solution. isref = 0; - debug!(msg_type = DebugMessageFlag::TIME; "Ignoring this reference pic.\n"); + debug!(msg_type = DebugMessageFlag::TIME; "Ignoring this reference pic."); } // if slices are buffered - flush if isref == 1 { - debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "{}", &format!("\nReference pic! [{}]\n", SLICE_TYPES[slice_type as usize])); - debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!("\nReference pic! [{}] maxrefcnt: {:3}\n", - SLICE_TYPES[slice_type as usize], maxrefcnt)); + debug!(msg_type = DebugMessageFlag::VIDEO_STREAM; "Reference pic! [{}]", SLICE_TYPES[slice_type as usize]); + debug!(msg_type = DebugMessageFlag::TIME; "Reference pic! [{}] maxrefcnt: {:3}", + SLICE_TYPES[slice_type as usize], maxrefcnt); // Flush buffered cc blocks before doing the housekeeping if dec_ctx.has_ccdata_buffered != 0 { @@ -538,7 +538,7 @@ pub unsafe fn slice_header( if !ccx_options.usepicorder != 0 && current_index.abs() >= MAXBFRAMES { // Probably a jump in the timeline. Warn and handle gracefully. info!( - "\nFound large gap({}) in PTS! Trying to recover ...\n", + "Found large gap({}) in PTS! Trying to recover ...", current_index ); current_index = 0; @@ -591,34 +591,32 @@ pub unsafe fn slice_header( ccxr_set_fts(dec_ctx.timing); // Keep frames_since_ref_time==0, use current_tref - debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!(" picordercnt:{:3} tref:{:3} idx:{:3} refidx:{:3} lmaxidx:{:3} maxtref:{:3}\n", - pic_order_cnt_lsb, (*dec_ctx.timing).current_tref, - current_index, (*dec_ctx.avc_ctx).currref, (*dec_ctx.avc_ctx).lastmaxidx, (*dec_ctx.avc_ctx).maxtref)); + debug!(msg_type = DebugMessageFlag::TIME; " picordercnt:{:3} tref:{:3} idx:{:3} refidx:{:3} lmaxidx:{:3} maxtref:{:3}", + pic_order_cnt_lsb, (*dec_ctx.timing).current_tref, + current_index, (*dec_ctx.avc_ctx).currref, (*dec_ctx.avc_ctx).lastmaxidx, (*dec_ctx.avc_ctx).maxtref); + let mut buf = [c_char::from(0i8); 64]; debug!( msg_type = DebugMessageFlag::TIME; - "{}", - &format!( - " sync_pts:{} ({:8})", - std::ffi::CStr::from_ptr(ccxr_print_mstime_static( - ((*dec_ctx.timing).sync_pts / ((MPEG_CLOCK_FREQ as i64) / 1000i64)) as c_long, - buf.as_mut_ptr() - )) - .to_str() - .unwrap_or(""), - (*dec_ctx.timing).sync_pts as u32 - ) + " sync_pts:{} ({:8})", + std::ffi::CStr::from_ptr(ccxr_print_mstime_static( + ((*dec_ctx.timing).sync_pts / ((MPEG_CLOCK_FREQ as i64) / 1000i64)) as c_long, + buf.as_mut_ptr() + )) + .to_str() + .unwrap_or(""), + (*dec_ctx.timing).sync_pts as u32 ); - debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!(" - {} since GOP: {:2}", - SLICE_TYPES[slice_type as usize], - dec_ctx.frames_since_last_gop as u32)); + debug!(msg_type = DebugMessageFlag::TIME; " - {} since GOP: {:2}", + SLICE_TYPES[slice_type as usize], + dec_ctx.frames_since_last_gop as u32); - debug!(msg_type = DebugMessageFlag::TIME; "{}", &format!(" b:{} frame# {}\n", bottom_field_flag, (*dec_ctx.avc_ctx).frame_num)); + debug!(msg_type = DebugMessageFlag::TIME; " b:{} frame# {}", bottom_field_flag, (*dec_ctx.avc_ctx).frame_num); // sync_pts is (was) set when current_tref was zero if (*dec_ctx.avc_ctx).lastmaxidx > -1 && (*dec_ctx.timing).current_tref == 0 { - debug!(msg_type = DebugMessageFlag::TIME; "\nNew temporal reference:\n"); + debug!(msg_type = DebugMessageFlag::TIME; "New temporal reference:"); ccxr_print_debug_timing(dec_ctx.timing); } From bab35808277fc6b450d6e94971bae268ab9494e3 Mon Sep 17 00:00:00 2001 From: steel-bucket Date: Sun, 17 Aug 2025 14:25:13 +0530 Subject: [PATCH 3/6] AVC Module: Failing CI --- src/rust/src/avc/common_types.rs | 22 +++++++++++----------- src/rust/src/avc/core.rs | 6 +++--- src/rust/src/avc/nal.rs | 5 ++--- src/rust/src/lib.rs | 25 +++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/rust/src/avc/common_types.rs b/src/rust/src/avc/common_types.rs index e0f6967d1..1dd1b1e9c 100644 --- a/src/rust/src/avc/common_types.rs +++ b/src/rust/src/avc/common_types.rs @@ -118,11 +118,11 @@ impl FromCType for AvcContextRust { log2_max_pic_order_cnt_lsb: ctx.log2_max_pic_order_cnt_lsb, frame_mbs_only_flag: ctx.frame_mbs_only_flag != 0, - num_nal_unit_type_7: ctx.num_nal_unit_type_7, - num_vcl_hrd: ctx.num_vcl_hrd, - num_nal_hrd: ctx.num_nal_hrd, - num_jump_in_frames: ctx.num_jump_in_frames, - num_unexpected_sei_length: ctx.num_unexpected_sei_length, + num_nal_unit_type_7: ctx.num_nal_unit_type_7 as _, + num_vcl_hrd: ctx.num_vcl_hrd as _, + num_nal_hrd: ctx.num_nal_hrd as _, + num_jump_in_frames: ctx.num_jump_in_frames as _, + num_unexpected_sei_length: ctx.num_unexpected_sei_length as _, ccblocks_in_avc_total: ctx.ccblocks_in_avc_total, ccblocks_in_avc_lost: ctx.ccblocks_in_avc_lost, @@ -159,7 +159,7 @@ impl CType for AvcContextRust { avc_ctx { cc_count: self.cc_count, cc_data: cc_data_ptr, - cc_databufsize: self.cc_databufsize as i64, + cc_databufsize: self.cc_databufsize as _, cc_buffer_saved: if self.cc_buffer_saved { 1 } else { 0 }, got_seq_para: if self.got_seq_para { 1 } else { 0 }, @@ -170,11 +170,11 @@ impl CType for AvcContextRust { log2_max_pic_order_cnt_lsb: self.log2_max_pic_order_cnt_lsb, frame_mbs_only_flag: if self.frame_mbs_only_flag { 1 } else { 0 }, - num_nal_unit_type_7: self.num_nal_unit_type_7, - num_vcl_hrd: self.num_vcl_hrd, - num_nal_hrd: self.num_nal_hrd, - num_jump_in_frames: self.num_jump_in_frames, - num_unexpected_sei_length: self.num_unexpected_sei_length, + num_nal_unit_type_7: self.num_nal_unit_type_7 as _, + num_vcl_hrd: self.num_vcl_hrd as _, + num_nal_hrd: self.num_nal_hrd as _, + num_jump_in_frames: self.num_jump_in_frames as _, + num_unexpected_sei_length: self.num_unexpected_sei_length as _, ccblocks_in_avc_total: self.ccblocks_in_avc_total, ccblocks_in_avc_lost: self.ccblocks_in_avc_lost, diff --git a/src/rust/src/avc/core.rs b/src/rust/src/avc/core.rs index d0f434ef6..f587f26b2 100644 --- a/src/rust/src/avc/core.rs +++ b/src/rust/src/avc/core.rs @@ -88,8 +88,8 @@ pub unsafe fn do_nal( (*dec_ctx.avc_ctx).log2_max_pic_order_cnt_lsb = ctx_rust.log2_max_pic_order_cnt_lsb; (*dec_ctx.avc_ctx).frame_mbs_only_flag = if ctx_rust.frame_mbs_only_flag { 1 } else { 0 }; - (*dec_ctx.avc_ctx).num_nal_hrd = ctx_rust.num_nal_hrd; - (*dec_ctx.avc_ctx).num_vcl_hrd = ctx_rust.num_vcl_hrd; + (*dec_ctx.avc_ctx).num_nal_hrd = ctx_rust.num_nal_hrd as _; + (*dec_ctx.avc_ctx).num_vcl_hrd = ctx_rust.num_vcl_hrd as _; (*dec_ctx.avc_ctx).got_seq_para = 1; } @@ -122,7 +122,7 @@ pub unsafe fn do_nal( return Err("Failed to realloc cc_data".into()); } (*dec_ctx.avc_ctx).cc_data = new_ptr; - (*dec_ctx.avc_ctx).cc_databufsize = new_size as i64; + (*dec_ctx.avc_ctx).cc_databufsize = new_size as _; } if !(*dec_ctx.avc_ctx).cc_data.is_null() { diff --git a/src/rust/src/avc/nal.rs b/src/rust/src/avc/nal.rs index 9c5fc5dbf..279e12f2b 100644 --- a/src/rust/src/avc/nal.rs +++ b/src/rust/src/avc/nal.rs @@ -1,11 +1,10 @@ use crate::avc::common_types::*; use crate::avc::core::round_portable; -use crate::bindings::{ - anchor_hdcc, cc_subtitle, encoder_ctx, lib_cc_decode, process_hdcc, store_hdcc, -}; +use crate::bindings::{cc_subtitle, encoder_ctx, lib_cc_decode}; use crate::libccxr_exports::time::{ ccxr_print_debug_timing, ccxr_print_mstime_static, ccxr_set_fts, }; +use crate::{anchor_hdcc, process_hdcc, store_hdcc}; use crate::{ccx_options, current_fps, total_frames_count, MPEG_CLOCK_FREQ}; use lib_ccxr::common::{AvcNalType, BitStreamRust, BitstreamError, FRAMERATES_VALUES, SLICE_TYPES}; use lib_ccxr::util::log::DebugMessageFlag; diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index d6a3111a7..0f3e6511a 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -39,6 +39,7 @@ use utils::is_true; use env_logger::{builder, Target}; use log::{warn, LevelFilter}; +use std::os::raw::c_uchar; use std::{ ffi::CStr, io::Write, @@ -72,6 +73,18 @@ cfg_if! { unsafe extern "C" fn version(_location: *const c_char) {} unsafe extern "C" fn set_binary_mode() {} + fn process_hdcc(enc_ctx: *mut encoder_ctx, ctx: *mut lib_cc_decode, sub: *mut cc_subtitle){} + fn store_hdcc( + enc_ctx: *mut encoder_ctx, + ctx: *mut lib_cc_decode, + cc_data: *mut c_uchar, + cc_count: c_int, + sequence_number: c_int, + current_fts_now: LLONG, + sub: *mut cc_subtitle, + ){} + fn anchor_hdcc(ctx: *mut lib_cc_decode, seq: c_int){} + } } @@ -100,6 +113,18 @@ extern "C" { fn version(location: *const c_char); fn set_binary_mode(); + fn process_hdcc(enc_ctx: *mut encoder_ctx, ctx: *mut lib_cc_decode, sub: *mut cc_subtitle); + fn store_hdcc( + enc_ctx: *mut encoder_ctx, + ctx: *mut lib_cc_decode, + cc_data: *mut c_uchar, + cc_count: c_int, + sequence_number: c_int, + current_fts_now: LLONG, + sub: *mut cc_subtitle, + ); + fn anchor_hdcc(ctx: *mut lib_cc_decode, seq: c_int); + } /// Initialize env logger with custom format, using stdout as target From 1e2c2e25548bd09eceeed3d3eb6d8d7d3b51890d Mon Sep 17 00:00:00 2001 From: Deepnarayan Sett Date: Tue, 2 Sep 2025 14:43:41 +0530 Subject: [PATCH 4/6] AVC Module: SIMD Optimisations --- src/rust/src/avc/core.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/rust/src/avc/core.rs b/src/rust/src/avc/core.rs index f587f26b2..90f5fd4b1 100644 --- a/src/rust/src/avc/core.rs +++ b/src/rust/src/avc/core.rs @@ -7,6 +7,8 @@ use crate::libccxr_exports::time::ccxr_set_fts; use lib_ccxr::common::AvcNalType; use lib_ccxr::util::log::DebugMessageFlag; use lib_ccxr::{debug, info}; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use std::arch::x86_64::*; use std::os::raw::c_void; use std::slice; @@ -336,6 +338,32 @@ pub enum AvcError { ForbiddenZeroBit(String), Other(String), } +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +fn find_next_zero(slice: &[u8]) -> Option { + let len = slice.len(); + let mut i = 0; + let ptr = slice.as_ptr(); + if is_x86_feature_detected!("sse2") { + unsafe { + let zero = _mm_set1_epi8(0); + while i + 16 <= len { + let chunk = _mm_loadu_si128(ptr.add(i) as *const __m128i); + let cmp = _mm_cmpeq_epi8(chunk, zero); + let mask = _mm_movemask_epi8(cmp); + if mask != 0 { + return Some(i + mask.trailing_zeros() as usize); + } + i += 16; + } + } + } + slice[i..] + .iter() + .position(|&b| b == 0x00) + .map(|pos| i + pos) +} + +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] fn find_next_zero(slice: &[u8]) -> Option { slice.iter().position(|&b| b == 0x00) } From f3f4cca3dec06e8847569ff74eb5a21aa7b31362 Mon Sep 17 00:00:00 2001 From: Deepnarayan Sett Date: Tue, 2 Sep 2025 14:44:20 +0530 Subject: [PATCH 5/6] AVC Module: Optimization in SEI --- src/rust/src/avc/sei.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rust/src/avc/sei.rs b/src/rust/src/avc/sei.rs index dce3b0229..340dbb4ba 100644 --- a/src/rust/src/avc/sei.rs +++ b/src/rust/src/avc/sei.rs @@ -228,7 +228,10 @@ pub fn user_data_registered_itu_t_t35(ctx: &mut AvcContextRust, userbuf: &[u8]) // Save the data and process once we know the sequence number if ((ctx.cc_count as usize + local_cc_count) * 3) + 1 > ctx.cc_databufsize { let new_size = ((ctx.cc_count as usize + local_cc_count) * 6) + 1; - ctx.cc_data.resize(new_size, 0); + unsafe { + ctx.cc_data.set_len(new_size); + } + ctx.cc_data.reserve(new_size); ctx.cc_databufsize = new_size; } From 2186738313d592c9ad801ce7d5acde75f3ef1303 Mon Sep 17 00:00:00 2001 From: Deepnarayan Sett Date: Tue, 2 Sep 2025 14:44:55 +0530 Subject: [PATCH 6/6] AVC Module: removed panic --- src/rust/src/avc/nal.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rust/src/avc/nal.rs b/src/rust/src/avc/nal.rs index 279e12f2b..36517190e 100644 --- a/src/rust/src/avc/nal.rs +++ b/src/rust/src/avc/nal.rs @@ -414,7 +414,9 @@ pub unsafe fn slice_header( } if (*dec_ctx.avc_ctx).pic_order_cnt_type == 1 { - panic!("In slice_header: AVC: ctx->avc_ctx->pic_order_cnt_type == 1 not yet supported."); + return Err( + "In slice_header: AVC: ctx->avc_ctx->pic_order_cnt_type == 1 not yet supported.".into(), + ); } // Ignore slice with same pic order or pts