diff --git a/src/lib_ccx/ccx_decoders_vbi.c b/src/lib_ccx/ccx_decoders_vbi.c index 3c20cf157..f59348f99 100644 --- a/src/lib_ccx/ccx_decoders_vbi.c +++ b/src/lib_ccx/ccx_decoders_vbi.c @@ -5,15 +5,44 @@ #include "utility.h" #include "stdlib.h" +#ifndef DISABLE_RUST + +extern void ccxr_delete_decoder_vbi( + struct ccx_decoder_vbi_ctx **arg); + +extern struct ccx_decoder_vbi_ctx *ccxr_init_decoder_vbi( + struct ccx_decoder_vbi_cfg *cfg); + +extern int ccxr_decode_vbi( + struct lib_cc_decode *dec_ctx, + uint8_t field, + unsigned char *buffer, + size_t len, + struct cc_subtitle *sub); + +#endif + void delete_decoder_vbi(struct ccx_decoder_vbi_ctx **arg) { +#ifndef DISABLE_RUST + ccxr_delete_decoder_vbi(arg); +#else struct ccx_decoder_vbi_ctx *ctx = *arg; vbi_raw_decoder_destroy(&ctx->zvbi_decoder); freep(arg); +#endif } + struct ccx_decoder_vbi_ctx *init_decoder_vbi(struct ccx_decoder_vbi_cfg *cfg) { +#ifndef DISABLE_RUST + struct ccx_decoder_vbi_ctx *vbi = ccxr_init_decoder_vbi(cfg); + if (vbi == NULL) + { + return NULL; + } +#else struct ccx_decoder_vbi_ctx *vbi; vbi = malloc(sizeof(*vbi)); @@ -61,10 +90,14 @@ struct ccx_decoder_vbi_ctx *init_decoder_vbi(struct ccx_decoder_vbi_cfg *cfg) vbi_raw_decoder_add_services(&vbi->zvbi_decoder, VBI_SLICED_CAPTION_525, /* strict */ 0); } return vbi; +#endif } int decode_vbi(struct lib_cc_decode *dec_ctx, uint8_t field, unsigned char *buffer, size_t len, struct cc_subtitle *sub) { +#ifndef DISABLE_RUST + return ccxr_decode_vbi(dec_ctx, field, buffer, len, sub); +#else int i = 0; unsigned int n_lines; vbi_sliced sliced[52]; @@ -96,4 +129,5 @@ int decode_vbi(struct lib_cc_decode *dec_ctx, uint8_t field, unsigned char *buff } } return CCX_OK; +#endif } diff --git a/src/rust/lib_ccxr/Cargo.toml b/src/rust/lib_ccxr/Cargo.toml index 6162aef3a..ef14c9f1b 100644 --- a/src/rust/lib_ccxr/Cargo.toml +++ b/src/rust/lib_ccxr/Cargo.toml @@ -15,6 +15,7 @@ strum = "0.26.3" strum_macros = "0.26.4" crc32fast = "1.4.2" num_enum = "0.6.1" +libc = { version = "0.2.138", features = ["extra_traits"] } # for FILE [features] default = [ diff --git a/src/rust/lib_ccxr/src/decoder_vbi/exit_codes.rs b/src/rust/lib_ccxr/src/decoder_vbi/exit_codes.rs new file mode 100644 index 000000000..283341901 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/exit_codes.rs @@ -0,0 +1,102 @@ +// Status codes +pub const CCX_OK: i64 = 0; +pub const CCX_FALSE: i64 = 0; +pub const CCX_TRUE: i64 = 1; +pub const CCX_EAGAIN: i64 = -100; +pub const CCX_EOF: i64 = -101; +pub const CCX_EINVAL: i64 = -102; +pub const CCX_ENOSUPP: i64 = -103; +pub const CCX_ENOMEM: i64 = -104; + +pub const NUM_BYTES_PER_PACKET: i64 = 35; // Class + type (repeated for convenience) + data + zero +pub const NUM_XDS_BUFFERS: i64 = 9; // CEA recommends no more than one level of interleaving. Play it safe + +pub const MAXBFRAMES: usize = 50; +pub const SORTBUF: usize = 2 * MAXBFRAMES + 1; + +// Time at which we switched to XDS mode, -1 means it hasn't happened yet +pub const TS_START_OF_XDS: i64 = -1; + +// Exit codes +pub const EXIT_OK: i64 = 0; +pub const EXIT_NO_INPUT_FILES: i64 = 2; +pub const EXIT_TOO_MANY_INPUT_FILES: i64 = 3; +pub const EXIT_INCOMPATIBLE_PARAMETERS: i64 = 4; +pub const EXIT_UNABLE_TO_DETERMINE_FILE_SIZE: i64 = 6; +pub const EXIT_MALFORMED_PARAMETER: i64 = 7; +pub const EXIT_READ_ERROR: i64 = 8; +pub const EXIT_NO_CAPTIONS: i64 = 10; +pub const EXIT_WITH_HELP: i64 = 11; +pub const EXIT_NOT_CLASSIFIED: i64 = 300; +pub const EXIT_ERROR_IN_CAPITALIZATION_FILE: i64 = 501; +pub const EXIT_BUFFER_FULL: i64 = 502; +pub const EXIT_MISSING_ASF_HEADER: i64 = 1001; +pub const EXIT_MISSING_RCWT_HEADER: i64 = 1002; + +// Common exit codes +pub const CCX_COMMON_EXIT_FILE_CREATION_FAILED: i64 = 5; +pub const CCX_COMMON_EXIT_UNSUPPORTED: i64 = 9; +pub const EXIT_NOT_ENOUGH_MEMORY: i64 = 500; +pub const CCX_COMMON_EXIT_BUG_BUG: i64 = 1000; + +// Define max width in characters/columns on the screen +pub const CCX_DECODER_608_SCREEN_ROWS: usize = 15; +pub const CCX_DECODER_608_SCREEN_WIDTH: usize = 32; + +//isdb, vbi common codes +pub const CCX_DTVCC_MAX_SERVICES: usize = 63; +pub const CCX_DTVCC_MAX_WINDOWS: usize = 8; +pub const CCX_DTVCC_MAX_ROWS: usize = 15; +pub const CCX_DTVCC_SCREENGRID_COLUMNS: usize = 210; +pub const CCX_DTVCC_MAX_PACKET_LENGTH: usize = 128; +pub const CCX_DTVCC_SCREENGRID_ROWS: usize = 75; + +pub const CCX_MESSAGES_QUIET: i32 = 0; +pub const CCX_MESSAGES_STDOUT: i32 = 1; +pub const CCX_MESSAGES_STDERR: i32 = 2; + +// vbi specific codes + +pub const _VBI3_RAW_DECODER_MAX_JOBS: usize = 8; + +pub const VBI_SLICED_CAPTION_525_F1: u32 = 0x00000020; +pub const VBI_SLICED_CAPTION_525_F2: u32 = 0x00000040; +pub const VBI_SLICED_CAPTION_525: u32 = VBI_SLICED_CAPTION_525_F1 | VBI_SLICED_CAPTION_525_F2; + +pub const VBI_SLICED_VBI_525: u32 = 0x40000000; +pub const VBI_SLICED_VBI_625: u32 = 0x20000000; +pub const _VBI3_RAW_DECODER_MAX_WAYS: usize = 8; + +pub const VBI_SLICED_TELETEXT_B_L10_625: u32 = 0x00000001; +pub const VBI_SLICED_TELETEXT_B_L25_625: u32 = 0x00000002; +pub const VBI_SLICED_TELETEXT_B: u32 = + VBI_SLICED_TELETEXT_B_L10_625 | VBI_SLICED_TELETEXT_B_L25_625; + +pub const VBI_SLICED_CAPTION_625_F1: u32 = 0x00000008; +pub const VBI_SLICED_CAPTION_625_F2: u32 = 0x00000010; +pub const VBI_SLICED_CAPTION_625: u32 = VBI_SLICED_CAPTION_625_F1 | VBI_SLICED_CAPTION_625_F2; + +pub const VBI_SLICED_VPS: u32 = 0x00000004; +pub const VBI_SLICED_VPS_F2: u32 = 0x00001000; + +pub const VBI_SLICED_WSS_625: u32 = 0x00000400; + +pub const CCX_DMT_PARSE: i32 = 1; + +pub const VBI_VIDEOSTD_SET_EMPTY: u64 = 0; +pub const VBI_VIDEOSTD_SET_PAL_BG: u64 = 1; +pub const VBI_VIDEOSTD_SET_625_50: u64 = 1; +pub const VBI_VIDEOSTD_SET_525_60: u64 = 2; +pub const VBI_VIDEOSTD_SET_ALL: u64 = 3; +pub const VBI_SLICED_TELETEXT_A: u32 = 0x00002000; +pub const VBI_SLICED_TELETEXT_C_625: u32 = 0x00004000; +pub const VBI_SLICED_TELETEXT_D_625: u32 = 0x00008000; +pub const VBI_SLICED_TELETEXT_B_525: u32 = 0x00010000; +pub const VBI_SLICED_TELETEXT_C_525: u32 = 0x00000100; +pub const VBI_SLICED_TELETEXT_D_525: u32 = 0x00020000; +pub const VBI_SLICED_2XCAPTION_525: u32 = 0x00000080; // VBI_SLICED_2xCAPTION_525 + +pub const DEF_THR_FRAC: u32 = 9; +pub const LP_AVG: u32 = 4; +pub const RAW_DECODER_PATTERN_DUMP: bool = false; +pub const VBI_SLICED_TELETEXT_BD_525: u32 = 0x00000200; diff --git a/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_decode.rs b/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_decode.rs new file mode 100644 index 000000000..1b6c302d1 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_decode.rs @@ -0,0 +1,1158 @@ +use crate::common::OutputFormat; // ccxoutputformat +use crate::time::TimingContext; // ccxcommontimingctx +use crate::util::log::{error, info}; // all kinds of logging + +use crate::decoder_vbi::exit_codes::*; +use crate::decoder_vbi::functions_vbi_slice::*; +use crate::decoder_vbi::structs_ccdecode::*; +use crate::decoder_vbi::structs_isdb::*; +use crate::decoder_vbi::structs_vbi_decode::*; +use crate::decoder_vbi::structs_vbi_slice::*; +use crate::decoder_vbi::structs_xds::*; +use crate::decoder_vbi::vbi_service_table::*; + +use std::os::raw::c_void; +use std::ptr::null_mut; + +pub fn vbi_raw_decoder_destroy(rd: &mut VbiRawDecoder) { + unsafe { + if !rd.pattern.is_null() { + let rd3 = rd.pattern as *mut Vbi3RawDecoder; + vbi3_raw_decoder_delete(rd3); + } + rd.pattern = null_mut(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vbi_raw_decoder_destroy_null_pattern() { + let mut decoder = VbiRawDecoder::default(); + + vbi_raw_decoder_destroy(&mut decoder); + + assert!(decoder.pattern.is_null()); + } +} + +pub fn vbi_raw_decoder_init(rd: &mut VbiRawDecoder) { + assert!(!rd.pattern.is_null(), "Decoder cannot be null"); + + let rd3 = vbi3_raw_decoder_new(None); + assert!(rd3.is_some(), "Failed to initialize vbi3_raw_decoder"); + + rd.pattern = Box::into_raw(rd3.unwrap()) as *mut i8; +} + +pub fn vbi_raw_decoder_add_services(rd: &mut VbiRawDecoder, services: u32, strict: i32) -> u32 { + assert!(!rd.pattern.is_null(), "Decoder pattern cannot be null"); + + let rd3 = unsafe { &mut *(rd.pattern as *mut Vbi3RawDecoder) }; + let mut service_set = services; + + vbi3_raw_decoder_set_sampling_par(rd3, &*rd, strict); + + service_set = vbi3_raw_decoder_add_services(rd3, service_set, strict); + + service_set +} + +type VbiVideostdSet = u64; + +#[inline] +fn vbi_pixfmt_bytes_per_pixel(fmt: VbiPixfmt) -> u32 { + match fmt { + VbiPixfmt::Yuv420 => 1, + VbiPixfmt::Rgba32Le | VbiPixfmt::Rgba32Be | VbiPixfmt::Bgra32Le | VbiPixfmt::Bgra32Be => 4, + VbiPixfmt::Rgb24 | VbiPixfmt::Bgr24 => 3, + _ => 2, + } +} + +#[inline] +pub fn range_check(start: u32, count: u32, min: u32, max: u32) -> bool { + start >= min && (start + count) <= max && (start + count) >= start +} + +pub fn _vbi_videostd_set_from_scanning(scanning: i32) -> VbiVideostdSet { + match scanning { + 525 => VBI_VIDEOSTD_SET_525_60, + 625 => VBI_VIDEOSTD_SET_625_50, + _ => 0, + } +} + +pub fn _vbi_sampling_par_valid_log(sp: &VbiSamplingPar, _log: &VbiLogHook) -> VbiBool { + // log unused + assert!(!sp.pattern.is_null(), "Sampling parameters cannot be null"); + + match sp.sampling_format { + VbiPixfmt::Yuv420 => {} + _ => { + let bpp = vbi_pixfmt_bytes_per_pixel(sp.sampling_format); + if sp.bytes_per_line % bpp as i32 != 0 { + info!( + "bytes_per_line value {} is no multiple of the sample size {}.", + sp.bytes_per_line, bpp + ); + return 0; + } + } + } + + if sp.bytes_per_line == 0 { + info!("samples_per_line is zero."); + return 0; + } + + if sp.count[0] == 0 && sp.count[1] == 0 { + info!( + "Invalid VBI scan range {}-{} ({} lines), {}-{} ({} lines).", + sp.start[0], + sp.start[0] + sp.count[0] - 1, + sp.count[0], + sp.start[1], + sp.start[1] + sp.count[1] - 1, + sp.count[1] + ); + return 0; + } + + let videostd_set = _vbi_videostd_set_from_scanning(sp.scanning); + + if videostd_set & VBI_VIDEOSTD_SET_525_60 != 0 { + if videostd_set & VBI_VIDEOSTD_SET_625_50 != 0 { + info!("Ambiguous videostd_set 0x{:x}.", videostd_set); + return 0; + } + + if sp.start[0] != 0 && !range_check(sp.start[0] as u32, sp.count[0] as u32, 1, 262) { + info!( + "Invalid VBI scan range {}-{} ({} lines), {}-{} ({} lines).", + sp.start[0], + sp.start[0] + sp.count[0] - 1, + sp.count[0], + sp.start[1], + sp.start[1] + sp.count[1] - 1, + sp.count[1] + ); + return 0; + } + + if sp.start[1] != 0 && !range_check(sp.start[1] as u32, sp.count[1] as u32, 263, 525) { + info!( + "Invalid VBI scan range {}-{} ({} lines), {}-{} ({} lines).", + sp.start[0], + sp.start[0] + sp.count[0] - 1, + sp.count[0], + sp.start[1], + sp.start[1] + sp.count[1] - 1, + sp.count[1] + ); + return 0; + } + } else if videostd_set & VBI_VIDEOSTD_SET_625_50 != 0 { + if sp.start[0] != 0 && !range_check(sp.start[0] as u32, sp.count[0] as u32, 1, 311) { + info!( + "Invalid VBI scan range {}-{} ({} lines), {}-{} ({} lines).", + sp.start[0], + sp.start[0] + sp.count[0] - 1, + sp.count[0], + sp.start[1], + sp.start[1] + sp.count[1] - 1, + sp.count[1] + ); + return 0; + } + + if sp.start[1] != 0 && !range_check(sp.start[1] as u32, sp.count[1] as u32, 312, 625) { + info!( + "Invalid VBI scan range {}-{} ({} lines), {}-{} ({} lines).", + sp.start[0], + sp.start[0] + sp.count[0] - 1, + sp.count[0], + sp.start[1], + sp.start[1] + sp.count[1] - 1, + sp.count[1] + ); + return 0; + } + } else { + info!("Ambiguous videostd_set 0x{:x}.", videostd_set); + return 0; + } + + if sp.interlaced != 0 && (sp.count[0] != sp.count[1] || sp.count[0] == 0) { + info!( + "Line counts {}, {} must be equal and non-zero when raw VBI data is interlaced.", + sp.count[0], sp.count[1] + ); + return 0; + } + + 1 // true +} + +pub extern "C" fn null_function( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + _n_points: *mut u32, // mark unused + _raw: *const u8, // mark unused +) -> i32 { + let _ = (bs, buffer, points); // unused + info!("vbi3_bit_slicer_set_params() not called."); + + 0 // false +} + +pub fn lines_containing_data( + start: &mut [u32; 2], + count: &mut [u32; 2], + sp: &VbiRawDecoder, + par: &VbiServicePar, +) { + start[0] = 0; + start[1] = sp.count[0] as u32; + + count[0] = sp.count[0] as u32; + count[1] = sp.count[1] as u32; + + if sp.synchronous == 0 { + return; + } + + for field in 0..2 { + if par.first[field] == 0 || par.last[field] == 0 { + count[field] = 0; + continue; + } + + let mut first = sp.start[field] as u32; + let mut last = first + sp.count[field] as u32 - 1; + + if first > 0 && sp.count[field] > 0 { + assert!(par.first[field] <= par.last[field]); + + if par.first[field] > last || par.last[field] < first { + continue; + } + + first = first.max(par.first[field]); + last = last.min(par.last[field]); + + start[field] += first - sp.start[field] as u32; + count[field] = last + 1 - first; + } + } +} + +pub fn add_job_to_pattern( + rd: &mut VbiRawDecoder, + job_num: i32, + start: &[u32; 2], + count: &[u32; 2], +) -> bool { + let job_num = job_num + 1; + let scan_lines = rd.count[0] as u32 + rd.count[1] as u32; + + let _pattern_end = unsafe { + // unused variable + rd.pattern + .add(scan_lines as usize * _VBI3_RAW_DECODER_MAX_WAYS) + }; + + for field in 0..2 { + let mut pattern = unsafe { + rd.pattern + .add(start[field] as usize * _VBI3_RAW_DECODER_MAX_WAYS) + }; + + for _ in 0..count[field] { + let mut free = 0; + let mut dst = pattern; + let end = unsafe { pattern.add(_VBI3_RAW_DECODER_MAX_WAYS) }; + + for src in unsafe { std::slice::from_raw_parts(pattern, _VBI3_RAW_DECODER_MAX_WAYS) } { + if *src <= 0 { + free += 1; + } else { + free += (*src == job_num as i8) as u32; + unsafe { *dst = *src }; + dst = unsafe { dst.add(1) }; + } + } + + while dst < end { + unsafe { + *dst = 0; + dst = dst.add(1); + } + } + + if free <= 1 { + return false; + } + + pattern = unsafe { pattern.add(_VBI3_RAW_DECODER_MAX_WAYS) }; + } + } + + for field in 0..2 { + let mut pattern = unsafe { + rd.pattern + .add(start[field] as usize * _VBI3_RAW_DECODER_MAX_WAYS) + }; + + for _ in 0..count[field] { + let mut way = 0; + + while unsafe { *pattern.add(way) } > 0 { + if unsafe { *pattern.add(way) } == job_num as i8 { + break; + } + way += 1; + } + + unsafe { + *pattern.add(way) = job_num as i8; + *pattern.add(_VBI3_RAW_DECODER_MAX_WAYS - 1) = -128; + pattern = pattern.add(_VBI3_RAW_DECODER_MAX_WAYS); + } + } + } + + true +} + +fn _vbi_sampling_par_permit_service( + sp: &VbiSamplingPar, + par: &VbiServicePar, + strict: u32, + _log: &VbiLogHook, // unused variable +) -> VbiBool { + const UNKNOWN: u32 = 0; + let mut _field: usize; // unused variable + let _samples_per_line: u32; // unused variable + let videostd_set: VbiVideostdSet = _vbi_videostd_set_from_scanning(sp.scanning); + + assert!(!sp.pattern.is_null()); + assert!(!par.label.is_null()); + if par.videostd_set & videostd_set == 0 { + info!( + "Service 0x{:08x} ({:?}) requires videostd_set 0x{:x}, have 0x{:x}.", + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() }, + par.videostd_set, + videostd_set + ); + return CCX_FALSE as i32; + } + + if par.flags as u32 & VbiServiceParFlag::LineNum as u32 != 0 + && ((par.first[0] > 0 && sp.start[0] as u32 == UNKNOWN) + || (par.first[1] > 0 && sp.start[1] as u32 == UNKNOWN)) + { + info!( + "Service 0x{:08x} ({:?}) requires known line numbers.", + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() } + ); + return CCX_FALSE as i32; + } + + let mut rate = par.cri_rate.max(par.bit_rate); + + match par.id { + VBI_SLICED_WSS_625 => { + // effective bit rate is just 1/3 max_rate, so 1 * max_rate should suffice + } + _ => { + rate = (rate * 3) >> 1; + } + } + + if rate > sp.sampling_rate as u32 { + info!( + "Sampling rate {:.2} MHz too low for service 0x{:08x} ({:?}).", + sp.sampling_rate as f64 / 1e6, + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() } + ); + return CCX_FALSE as i32; + } + + let signal: f64 = par.cri_bits as f64 / par.cri_rate as f64 + + (par.frc_bits + par.payload) as f64 / par.bit_rate as f64; + + let samples_per_line: u32 = + sp.bytes_per_line as u32 / vbi_pixfmt_bytes_per_pixel(sp.sampling_format); + + if sp.offset > 0 && strict > 0 { + let sampling_rate = sp.sampling_rate as f64; + let offset = sp.offset as f64 / sampling_rate; + let end = ((sp.offset as u32 + samples_per_line) as f64) / sampling_rate; + + if offset > (par.offset as f64 / 1e3 - 0.5e-6) { + info!( + "Sampling starts at 0H + {:.2} us, too late for service 0x{:08x} ({:?}) at {:.2} us.", + offset * 1e6, + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() }, + par.offset as f64 / 1e3 + ); + return CCX_FALSE as i32; + } + + if end < (par.offset as f64 / 1e3 + signal + 0.5e-6) { + info!( + "Sampling ends too early at 0H + {:.2} us for service 0x{:08x} ({:?}) which ends at {:.2} us.", + end * 1e6, + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() }, + par.offset as f64 / 1e3 + signal * 1e6 + 0.5 + ); + return CCX_FALSE as i32; + } + } else { + let mut samples = samples_per_line as f64 / sp.sampling_rate as f64; + + if strict > 0 { + samples -= 1e-6; // headroom + } + + if samples < signal { + info!( + "Service 0x{:08x} ({:?}) signal length {:.2} us exceeds {:.2} us sampling length.", + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() }, + signal * 1e6, + samples * 1e6 + ); + return CCX_FALSE as i32; + } + } + + if par.flags as u32 & VbiServiceParFlag::FieldNum as u32 != 0 { + info!( + "Service 0x{:08x} ({}) requires synchronous field order.", + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() } + ); + return CCX_FALSE as i32; + } + + for field in 0..2 { + let start = sp.start[field] as u32; + let end = start + sp.count[field] as u32 - 1; + + if par.first[field] == 0 || par.last[field] == 0 { + // no data on this field + continue; + } + + if sp.count[field] == 0 { + info!( + "Service 0x{:08x} ({:?}) requires data from field {}.", + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() }, + field + 1 + ); + return CCX_FALSE as i32; + } + + if strict as i32 <= 0 || sp.start[field] == 0 { + continue; + } + + if strict == 1 && par.first[field] > par.last[field] { + // may succeed if not all scanning lines available for the service are actually used + continue; + } + + if start > par.first[field] || end < par.last[field] { + info!( + "Service 0x{:08x} ({:?}) requires lines {}-{}, have {}-{}.", + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() }, + par.first[field], + par.last[field], + start, + end + ); + return CCX_FALSE as i32; + } + } + + CCX_TRUE as i32 +} + +pub fn _vbi_sampling_par_check_services_log( + sp: &VbiSamplingPar, + services: VbiServiceSet, + strict: u32, + log: &VbiLogHook, +) -> VbiServiceSet { + assert!(!sp.pattern.is_null()); + + let mut rservices: VbiServiceSet = 0; + + for par in VBI_SERVICE_TABLE.iter() { + if par.id == 0 { + break; + } + + if par.id & services == 0 { + continue; + } + + if _vbi_sampling_par_permit_service(sp, par, strict, log) != 0 { + rservices |= par.id; + } + } + + rservices +} + +pub fn vbi3_raw_decoder_add_services( + rd: &mut Vbi3RawDecoder, + mut services: VbiServiceSet, + strict: i32, +) -> VbiServiceSet { + assert!(!rd.pattern.is_null()); + + services &= !(VBI_SLICED_VBI_525 | VBI_SLICED_VBI_625); + + if rd.services & services != 0 { + info!( + "Already decoding services 0x{:08x}.", + rd.services & services + ); + services &= !rd.services; + } + + if services == 0 { + info!("No services to add."); + return rd.services; + } + + if rd.pattern.is_null() { + let scan_lines = rd.sampling.count[0] + rd.sampling.count[1]; + let scan_ways = scan_lines as usize * _VBI3_RAW_DECODER_MAX_WAYS; + let size = scan_ways * std::mem::size_of::(); + + rd.pattern = unsafe { + std::alloc::alloc_zeroed(std::alloc::Layout::array::(size).unwrap()) as *mut i8 + }; + if rd.pattern.is_null() { + error!("Out of memory."); + return rd.services; + } + + unsafe { + std::ptr::write_bytes(rd.pattern, 0, scan_ways); + } + } + + let min_offset = if rd.sampling.scanning == 525 { + 7.9e-6 + } else { + 8.0e-6 + }; + + for par in VBI_SERVICE_TABLE.iter() { + if par.id == 0 { + break; + } + + if par.id & services == 0 { + continue; + } + + // Find suitable job index instead of getting a direct mutable reference + let job_index = rd.jobs.iter().position(|job| { + let id = job.id | par.id; + id & !VBI_SLICED_TELETEXT_B == 0 + || id & !VBI_SLICED_CAPTION_525 == 0 + || id & !VBI_SLICED_CAPTION_625 == 0 + || id & !(VBI_SLICED_VPS | VBI_SLICED_VPS_F2) == 0 + }); + + let job_index = if let Some(index) = job_index { + index + } else { + if rd.n_jobs as usize >= _VBI3_RAW_DECODER_MAX_JOBS { + error!( + "Set 0x{:08x} exceeds number of simultaneously decodable services ({}).", + services, _VBI3_RAW_DECODER_MAX_WAYS + ); + break; + } + let index = rd.n_jobs as usize; + rd.n_jobs += 1; + index + }; + + // Reset job ID + rd.jobs[job_index].id = 0; + + if _vbi_sampling_par_check_services_log(&rd.sampling, par.id, strict as u32, &rd.log) == 0 { + continue; + } + + let mut sample_offset = 0; + + if rd.sampling.offset > 0 && strict > 0 { + let offset = rd.sampling.offset as f64 / rd.sampling.sampling_rate as f64; + if offset < min_offset { + sample_offset = (min_offset * rd.sampling.sampling_rate as f64) as u32; + } + } + + let cri_end = u32::MAX; + + if !_vbi3_bit_slicer_init(&mut rd.jobs[job_index].slicer) { + panic!("bit_slicer_init failed"); + } + + if !vbi3_bit_slicer_set_params( + &mut rd.jobs[job_index].slicer, + rd.sampling.sampling_format, + rd.sampling.sampling_rate as u32, + sample_offset, + rd.sampling.bytes_per_line as u32, + par.cri_frc >> par.frc_bits, + par.cri_frc_mask >> par.frc_bits, + par.cri_bits, + par.cri_rate, + cri_end, + par.cri_frc & ((1 << par.frc_bits) - 1), + par.frc_bits, + par.payload, + par.bit_rate, + par.modulation, + ) { + panic!("bit_slicer_set_params failed"); + } + + let mut start = [0; 2]; + let mut count = [0; 2]; + lines_containing_data(&mut start, &mut count, &rd.sampling, par); + + // Use job_index instead of pointer arithmetic + if !add_job_to_pattern(&mut rd.sampling, job_index as i32, &start, &count) { + error!( + "Out of decoder pattern space for service 0x{:08x} ({}).", + par.id, + unsafe { std::ffi::CStr::from_ptr(par.label).to_string_lossy() } + ); + continue; + } + + // Update job ID using the index + rd.jobs[job_index].id |= par.id; + rd.services |= par.id; + } + + rd.services +} + +//---------------------- + +pub fn vbi_raw_decode(rd: &mut VbiRawDecoder, raw: &[u8], out: &mut [VbiSliced]) -> u32 { + assert!(!rd.pattern.is_null(), "rd.pattern cannot be null"); + assert!(!raw.is_empty(), "raw cannot be empty"); + assert!(!out.is_empty(), "out cannot be empty"); + + let rd3 = unsafe { &mut *(rd.pattern as *mut Vbi3RawDecoder) }; + let n_lines = (rd.count[0] + rd.count[1]) as u32; + + vbi3_raw_decoder_decode(rd3, out, n_lines, raw) +} + +pub fn decode_pattern( + rd: &mut Vbi3RawDecoder, + sliced: *mut VbiSliced, + pattern: *mut i8, + i: u32, + raw: &[u8], +) -> *mut VbiSliced { + let sp = rd.sampling; + let mut pat = pattern; + + loop { + let j = unsafe { *pat }; + + if j > 0 { + let job = &mut rd.jobs[(j - 1) as usize]; + + // Convert _Vbi3RawDecoderJob to VbiRawDecoderJob if necessary + let job = unsafe { &mut *(job as *mut _Vbi3RawDecoderJob as *mut VbiRawDecoderJob) }; + + if !slice(rd, sliced, job, i, raw) { + pat = unsafe { pat.add(1) }; + continue; + } + + unsafe { + (*sliced).id = job.id; + (*sliced).line = 0; + + if i >= sp.count[0] as u32 { + if sp.synchronous != 0 && sp.start[1] != 0 { + (*sliced).line = sp.start[1] as u32 + i - sp.count[0] as u32; + } + } else if sp.synchronous != 0 && sp.start[0] != 0 { + (*sliced).line = sp.start[0] as u32 + i; + } + } + + unsafe { + let sliced = sliced.add(1); + *pattern.add(_VBI3_RAW_DECODER_MAX_WAYS - 1) = -128; + return sliced; + } + } else if pat == pattern { + if rd.readjust == 0 { + let size = _VBI3_RAW_DECODER_MAX_WAYS - 1; + unsafe { + let j = *pattern; + std::ptr::copy(pattern.add(1), pattern, size); + *pattern.add(_VBI3_RAW_DECODER_MAX_WAYS - 1) = j; + } + } + break; + } else if unsafe { *pattern.add(_VBI3_RAW_DECODER_MAX_WAYS - 1) } < 0 { + break; + } + + unsafe { + std::ptr::swap(pat, pattern); + } + + break; + } + + sliced +} + +pub fn do_cb(ctx: &mut LibCcDecode, cc_block: &[u8], sub: &mut CcSubtitle) -> i32 { + let cc_valid = (cc_block[0] & 4) >> 2; + let cc_type = cc_block[0] & 3; + let mut timeok = true; + + if ctx.fix_padding != 0 && cc_valid == 0 && cc_type <= 1 && cc_block[1] == 0 && cc_block[2] == 0 + { + // Padding + let mut cc_block_mut = cc_block.to_vec(); + cc_block_mut[0] |= 4; // Set cc_valid + cc_block_mut[1] = 0x80; + cc_block_mut[2] = 0x80; + } + + if ctx.write_format != OutputFormat::Raw + && ctx.write_format != OutputFormat::DvdRaw + && (cc_block[0] == 0xFA || cc_block[0] == 0xFC || cc_block[0] == 0xFD) + && (cc_block[1] & 0x7F) == 0 + && (cc_block[2] & 0x7F) == 0 + { + return 1; + } + + if cc_valid != 0 || cc_type == 3 { + ctx.cc_stats[cc_type as usize] += 1; + + match cc_type { + 0 => { + ctx.current_field = 1; + ctx.saw_caption_block = 1; + + if let Some(start) = ctx.extraction_start { + if get_fts(ctx.timing, ctx.current_field) < start.millis() { + // used millis in place of time_in_ms + timeok = false; + } + } + if let Some(end) = ctx.extraction_end { + if get_fts(ctx.timing, ctx.current_field) > end.millis() { + // used millis in place of time_in_ms + timeok = false; + ctx.processed_enough = 1; + } + } + if timeok { + if ctx.write_format != OutputFormat::Rcwt { + printdata(ctx, Some(&cc_block[1..3]), None, sub); + } else { + writercwtdata(ctx, Some(cc_block), sub); + } + } + } + 1 => { + ctx.current_field = 2; + ctx.saw_caption_block = 1; + + if let Some(start) = ctx.extraction_start { + if get_fts(ctx.timing, ctx.current_field) < start.millis() { + // used millis in place of time_in_ms + timeok = false; + } + } + if let Some(end) = ctx.extraction_end { + if get_fts(ctx.timing, ctx.current_field) > end.millis() { + // used millis in place of time_in_ms + timeok = false; + ctx.processed_enough = 1; + } + } + if timeok { + if ctx.write_format != OutputFormat::Rcwt { + printdata(ctx, Some(&cc_block[1..3]), None, sub); + } else { + writercwtdata(ctx, Some(cc_block), sub); + } + } + } + 2 | 3 => { + ctx.current_field = 3; + + if let Some(start) = ctx.extraction_start { + if get_fts(ctx.timing, ctx.current_field) < start.millis() { + // used millis in place of time_in_ms + timeok = false; + } + } + if let Some(end) = ctx.extraction_end { + if get_fts(ctx.timing, ctx.current_field) > end.millis() { + // used millis in place of time_in_ms + timeok = false; + ctx.processed_enough = 1; + } + } + if timeok { + if ctx.write_format != OutputFormat::Rcwt { + // Do nothing - Rust 708 is used + } else { + writercwtdata(ctx, Some(cc_block), sub); + } + } + } + _ => { + panic!("Impossible value for cc_type. Please file a bug report."); + } + } + } else { + info!("Found !(cc_valid || cc_type == 3) - ignoring this block"); + } + + 1 +} + +const DVD_HEADER: [u8; 8] = [0x00, 0x00, 0x01, 0xb2, 0x43, 0x43, 0x01, 0xf8]; +const LC1: [u8; 1] = [0x8a]; +const LC2: [u8; 1] = [0x8f]; +const LC3: [u8; 2] = [0x16, 0xfe]; +const LC4: [u8; 2] = [0x1e, 0xfe]; +const LC5: [u8; 1] = [0xff]; +const LC6: [u8; 1] = [0xfe]; + +pub fn write_dvd_raw(data1: &[u8], data2: &[u8], sub: &mut CcSubtitle) { + static mut LOOP_COUNT: i32 = 1; + static mut DATA_COUNT: i32 = 0; + + unsafe { + if DATA_COUNT == 0 { + writeraw(&DVD_HEADER, sub); + if LOOP_COUNT == 1 { + writeraw(&LC1, sub); + } + if LOOP_COUNT == 2 { + writeraw(&LC2, sub); + } + if LOOP_COUNT == 3 { + writeraw(&LC3, sub); + if !data2.is_empty() { + writeraw(data2, sub); + } + } + if LOOP_COUNT > 3 { + writeraw(&LC4, sub); + if !data2.is_empty() { + writeraw(data2, sub); + } + } + } + + DATA_COUNT += 1; + writeraw(&LC5, sub); + if !data1.is_empty() { + writeraw(data1, sub); + } + + if (LOOP_COUNT == 1 && DATA_COUNT < 5) + || (LOOP_COUNT == 2 && DATA_COUNT < 8) + || (LOOP_COUNT == 3 && DATA_COUNT < 11) + || (LOOP_COUNT > 3 && DATA_COUNT < 15) + { + writeraw(&LC6, sub); + if !data2.is_empty() { + writeraw(data2, sub); + } + } else { + if LOOP_COUNT == 1 { + writeraw(&LC6, sub); + if !data2.is_empty() { + writeraw(data2, sub); + } + } + LOOP_COUNT += 1; + DATA_COUNT = 0; + } + } +} + +pub fn writeraw(data: &[u8], sub: &mut CcSubtitle) -> i32 { + // Don't do anything for empty data + if data.is_empty() { + return -1; + } + + // Resize the subtitle data buffer + let new_size = sub.nb_data as usize + data.len(); + let mut new_data = if sub.data.is_null() { + Vec::with_capacity(new_size) + } else { + unsafe { + let existing_data = Vec::from_raw_parts( + sub.data as *mut u8, + sub.nb_data as usize, + sub.nb_data as usize, + ); + let mut resized_data = existing_data; + resized_data.reserve(data.len()); + resized_data + } + }; + + // Append the new data + new_data.extend_from_slice(data); + + // Update subtitle metadata + sub.data = new_data.as_mut_ptr() as *mut std::ffi::c_void; + sub.datatype = SubDataType::Generic; + sub.got_output = true; + sub.nb_data += data.len() as u32; + sub.subtype = SubType::Raw; + + std::mem::forget(new_data); // Prevent Vec from being dropped + + 0 // EXIT SUCCESS +} + +pub unsafe extern "C" fn writeraw_wrapper( + data: *const u8, + len: i32, + _ctx: *mut c_void, // ctx unused + sub: *mut CcSubtitle, +) -> i32 { + if data.is_null() || sub.is_null() { + return -1; // Error: Null pointer + } + + let data_slice = std::slice::from_raw_parts(data, len as usize); + let sub_ref = &mut *sub; + + writeraw(data_slice, sub_ref) +} + +pub fn printdata( + ctx: &mut LibCcDecode, + data1: Option<&[u8]>, + data2: Option<&[u8]>, + sub: &mut CcSubtitle, +) { + if ctx.write_format == OutputFormat::DvdRaw { + if let Some(data1) = data1 { + write_dvd_raw(data1, data2.unwrap_or(&[]), sub); + } + } else { + // Broadcast raw or any non-raw + if let Some(data1) = data1 { + if ctx.extract != 2 { + ctx.current_field = 1; + if let Some(writedata) = ctx.writedata { + writedata( + data1.as_ptr(), + data1.len() as i32, + ctx as *mut _ as *mut c_void, + sub, + ); + } + } + } + if let Some(data2) = data2 { + ctx.current_field = 2; + if ctx.extract != 1 { + if let Some(writedata) = ctx.writedata { + writedata( + data2.as_ptr(), + data2.len() as i32, + ctx as *mut _ as *mut c_void, + sub, + ); + } + } else if ctx.writedata.map(|f| f as usize) != Some(writeraw_wrapper as usize) { + if let Some(writedata) = ctx.writedata { + writedata( + data2.as_ptr(), + data2.len() as i32, + ctx as *mut _ as *mut c_void, + sub, + ); + } + } + } + } +} + +pub fn writercwtdata(ctx: &mut LibCcDecode, data: Option<&[u8]>, sub: &mut CcSubtitle) { + static mut PREV_FTS: i64 = -1; + static mut CBCOUNT: u16 = 0; + static mut CBEMPTY: i32 = 0; + static mut CBBUFFER: [u8; 0xFFFF * 3] = [0; 0xFFFF * 3]; + static mut CBHEADER: [u8; 10] = [0; 10]; + + unsafe { + let curr_fts = get_fts(ctx.timing, ctx.current_field); + + let prev_fts: *mut i64 = &raw mut PREV_FTS; + let cbcount: *mut u16 = &raw mut CBCOUNT; + let cbempty: *mut i32 = &raw mut CBEMPTY; + let cbbuffer: *mut [u8; 0xFFFF * 3] = &raw mut CBBUFFER; + let cbheader: *mut [u8; 10] = &raw mut CBHEADER; + + // Handle writing stored data when timestamp changes, end of data, or buffer full + if (*prev_fts != curr_fts && *prev_fts != -1) || data.is_none() || *cbcount == 0xFFFF { + if *cbcount != 0xFFFF { + let store_cbcount = *cbcount; + + for cb in (0..(*cbcount) as i32).rev() { + let cc_valid = ((*cbbuffer)[3 * cb as usize] & 4) >> 2; + let cc_type = (*cbbuffer)[3 * cb as usize] & 3; + + // Skip NTSC padding packets or unused packets + if (cc_valid != 0 + && cc_type <= 1 + && ctx.fullbin == 0 + && (*cbbuffer)[3 * cb as usize + 1] == 0x80 + && (*cbbuffer)[3 * cb as usize + 2] == 0x80) + || !(cc_valid != 0 || cc_type == 3) + { + *cbcount -= 1; + } else { + break; + } + } + + info!( + "{:?} Write {} RCWT blocks - skipped {} padding / {} unused blocks.", + *prev_fts, + *cbcount, + store_cbcount - *cbcount, + *cbempty + ); + } + + // Prepare and write data header + (*cbheader)[0..8].copy_from_slice(&(*prev_fts).to_le_bytes()); + (*cbheader)[8..10].copy_from_slice(&(*cbcount).to_le_bytes()); + + if *cbcount > 0 { + if let Some(writedata) = ctx.writedata { + writedata((*cbheader).as_ptr(), 10, ctx.context_cc608_field_1, sub); + writedata( + (*cbbuffer).as_ptr(), + 3 * (*cbcount) as i32, + ctx.context_cc608_field_1, + sub, + ); + } + } + + *cbcount = 0; + *cbempty = 0; + } + + if let Some(data_slice) = data { + // Store new data while FTS is unchanged + let cc_valid = (data_slice[0] & 4) >> 2; + let cc_type = data_slice[0] & 3; + + // Only store non-empty packets + if cc_valid != 0 || cc_type == 3 { + (*cbbuffer)[(*cbcount as usize) * 3..(*cbcount as usize) * 3 + 3] + .copy_from_slice(&data_slice[0..3]); + *cbcount += 1; + } else { + *cbempty += 1; + } + } else { + // Write final padding blocks for field 1 and 2 + let adjusted_fts = curr_fts - 1001 / 30; + + (*cbheader)[0..8].copy_from_slice(&adjusted_fts.to_le_bytes()); + *cbcount = 2; + (*cbheader)[8..10].copy_from_slice(&(*cbcount).to_le_bytes()); + + // Field 1 and 2 padding + (*cbbuffer)[0..3].copy_from_slice(&[0x04, 0x80, 0x80]); + (*cbbuffer)[3..6].copy_from_slice(&[0x05, 0x80, 0x80]); + + if let Some(writedata) = ctx.writedata { + writedata((*cbheader).as_ptr(), 10, ctx.context_cc608_field_1, sub); + writedata( + (*cbbuffer).as_ptr(), + 3 * (*cbcount) as i32, + ctx.context_cc608_field_1, + sub, + ); + } + + *cbcount = 0; + *cbempty = 0; + + info!("{:?} Write final padding RCWT blocks.", adjusted_fts); + } + + *prev_fts = curr_fts; + } +} + +pub fn get_fts(ctx: *mut TimingContext, current_field: i32) -> i64 { + if ctx.is_null() { + error!("get_fts: ctx is null"); + return 0; + } + + let ctx = unsafe { &mut *ctx }; + + let base_ts = ctx.fts_now + ctx.fts_global; + + let field_offset = match current_field { + 1 => 0, // Field 1 + 2 => 1001 / 60, // Field 2 (half frame later) + 3 => 0, // 708 field + _ => { + error!( + "get_fts: unhandled branch: current_field = {}", + current_field + ); + return 0; + } + }; + + base_ts.millis() + field_offset +} diff --git a/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_main.rs b/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_main.rs new file mode 100644 index 000000000..7505fc81a --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_main.rs @@ -0,0 +1,75 @@ +use crate::decoder_vbi::exit_codes::*; +use crate::decoder_vbi::functions_vbi_decode::*; +use crate::decoder_vbi::structs_ccdecode::*; +use crate::decoder_vbi::structs_isdb::*; +use crate::decoder_vbi::structs_vbi_decode::*; +use crate::decoder_vbi::structs_xds::*; + +pub fn delete_decoder_vbi(arg: &mut Option<*mut CcxDecoderVbiCtx>) { + if let Some(ctx_ptr) = arg.take() { + unsafe { + let ctx = &mut *ctx_ptr; + vbi_raw_decoder_destroy(&mut ctx.zvbi_decoder); + } + } +} + +pub fn init_decoder_vbi(cfg: Option<&CcxDecoderVbiCfg>) -> Option> { + let mut vbi = Box::new(CcxDecoderVbiCtx { + vbi_decoder_inited: 0, + zvbi_decoder: VbiRawDecoder::default(), + vbi_debug_dump: std::ptr::null_mut(), + }); + + vbi_raw_decoder_init(&mut vbi.zvbi_decoder); + + if cfg.is_none() { + let rd = &mut vbi.zvbi_decoder; + rd.scanning = 525; + rd.sampling_format = VbiPixfmt::Yuv420; + rd.sampling_rate = 13_500_000; + rd.bytes_per_line = 720; + rd.offset = (9.7e-6 * 13.5e6) as i32; + rd.start = [21, 284]; + rd.count = [1, 1]; + rd.interlaced = CCX_TRUE as i32; + rd.synchronous = CCX_TRUE as i32; + vbi_raw_decoder_add_services(rd, VBI_SLICED_CAPTION_525, 0); + } + Some(vbi) +} + +pub fn decode_vbi( + dec_ctx: &mut LibCcDecode, + field: u8, + buffer: &[u8], + sub: &mut CcSubtitle, +) -> i64 { + let mut sliced = [VbiSliced { + id: 0, + line: 0, + data: [0; 56], + }; 52]; + + if dec_ctx.vbi_decoder.is_null() { + dec_ctx.vbi_decoder = init_decoder_vbi(None).map_or(std::ptr::null_mut(), Box::into_raw); + } + + let n_lines = vbi_raw_decode( + unsafe { &mut dec_ctx.vbi_decoder.as_mut().unwrap().zvbi_decoder }, + buffer, + &mut sliced, + ); + + if n_lines > 0 { + for slice in sliced.iter().take(n_lines as usize) { + let mut data = [0u8; 3]; + data[0] = if field == 1 { 0x04 } else { 0x05 }; + data[1] = slice.data[0]; + data[2] = slice.data[1]; + do_cb(dec_ctx, &data, sub); + } + } + + CCX_OK +} diff --git a/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_slice.rs b/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_slice.rs new file mode 100644 index 000000000..b4ff2570c --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/functions_vbi_slice.rs @@ -0,0 +1,1950 @@ +use crate::decoder_vbi::exit_codes::*; +use crate::decoder_vbi::functions_vbi_decode::*; +use crate::decoder_vbi::structs_isdb::*; +use crate::decoder_vbi::structs_vbi_decode::*; +use crate::decoder_vbi::structs_vbi_slice::*; +use crate::decoder_vbi::vbi_service_table::*; + +use crate::util::log::error; + +use std::alloc::{alloc_zeroed, dealloc, Layout}; +use std::ptr::{self, null_mut}; + +pub unsafe fn vbi3_raw_decoder_delete(rd: *mut Vbi3RawDecoder) { + if rd.is_null() { + return; + } + vbi3_raw_decoder_destroy(&mut *rd); +} + +pub fn vbi3_raw_decoder_debug(rd: &mut Vbi3RawDecoder, enable: bool) -> bool { + assert!(!rd.pattern.is_null(), "Decoder cannot be null"); + + let mut _sp_lines: *mut _Vbi3RawDecoderSpLine = null_mut(); // _sp_lines not used + let mut n_lines: u32 = 0; + let mut result = true; + + rd.debug = enable as i32; + + if enable { + n_lines = (rd.sampling.count[0] + rd.sampling.count[1]) as u32; + } + + match rd.sampling.sampling_format { + VbiPixfmt::Yuv420 => {} + _ => { + // Not implemented + n_lines = 0; + result = false; + } + } + + if rd.n_sp_lines == n_lines { + return result; + } + + // Free existing lines + if !rd.sp_lines.is_null() { + unsafe { + let layout = Layout::array::<_Vbi3RawDecoderSpLine>(rd.n_sp_lines as usize).unwrap(); + dealloc(rd.sp_lines as *mut u8, layout); + } + rd.sp_lines = null_mut(); + } + rd.n_sp_lines = 0; + + if n_lines > 0 { + unsafe { + let layout = Layout::array::<_Vbi3RawDecoderSpLine>(n_lines as usize).unwrap(); + rd.sp_lines = alloc_zeroed(layout) as *mut _Vbi3RawDecoderSpLine; + if rd.sp_lines.is_null() { + return false; + } + } + rd.n_sp_lines = n_lines; + } + + result +} + +use std::mem::MaybeUninit; + +pub fn vbi3_raw_decoder_destroy(rd: &mut Vbi3RawDecoder) { + vbi3_raw_decoder_reset(rd); + vbi3_raw_decoder_debug(rd, false); + + // Make unusable + *rd = unsafe { MaybeUninit::zeroed().assume_init() }; +} + +pub fn vbi3_raw_decoder_reset(rd: &mut Vbi3RawDecoder) { + assert!(!rd.pattern.is_null(), "Decoder cannot be null"); + + unsafe { + if !rd.pattern.is_null() { + dealloc(rd.pattern as *mut u8, Layout::new::()); + rd.pattern = null_mut(); + } + } + + rd.services = 0; + rd.n_jobs = 0; + rd.readjust = 1; + + rd.jobs.fill(Default::default()); +} + +pub fn vbi3_raw_decoder_new(sp: Option<&VbiSamplingPar>) -> Option> { + let mut rd = Box::new(Vbi3RawDecoder { + sampling: Default::default(), + services: 0, + log: Default::default(), + debug: 0, + n_jobs: 0, + n_sp_lines: 0, + readjust: 0, + pattern: std::ptr::null_mut(), + jobs: [Default::default(); _VBI3_RAW_DECODER_MAX_JOBS], + sp_lines: std::ptr::null_mut(), + }); + + if !_vbi3_raw_decoder_init(&mut rd, sp) { + return None; + } + + Some(rd) +} + +pub fn _vbi3_raw_decoder_init(rd: &mut Vbi3RawDecoder, sp: Option<&VbiSamplingPar>) -> bool { + vbi3_raw_decoder_reset(rd); + + if let Some(sp) = sp { + if _vbi_sampling_par_valid_log(sp, &rd.log) == 0 { + return false; + } + rd.sampling = *sp; + } + + true +} + +pub fn vbi3_raw_decoder_set_sampling_par( + rd: &mut Vbi3RawDecoder, + sp: &VbiSamplingPar, + strict: i32, +) -> u32 { + assert!(!rd.pattern.is_null()); + assert!(!sp.pattern.is_null()); + + let services = rd.services; + + vbi3_raw_decoder_reset(rd); + + if _vbi_sampling_par_valid_log(sp, &rd.log) == 0 { + rd.sampling = Default::default(); + return 0; + } + + rd.sampling = *sp; + + vbi3_raw_decoder_debug(rd, rd.debug != 0); + + vbi3_raw_decoder_add_services(rd, services, strict) +} + +pub fn _vbi3_bit_slicer_init(bs: &mut Vbi3BitSlicer) -> bool { + assert!(!ptr::eq(bs, null_mut())); + *bs = Vbi3BitSlicer::default(); + bs.func = Some(null_function); + true +} + +/// Helper function to get the green component based on pixel format. +pub fn get_green(raw: &[u8], bs: &Vbi3BitSlicer, pixfmt: VbiPixfmt) -> u32 { + match pixfmt { + VbiPixfmt::Yuv420 => raw[0] as u32, + VbiPixfmt::Rgb24 | VbiPixfmt::Bgr24 => { + // For RGB24, green is in different positions depending on format + raw[1] as u32 + } + VbiPixfmt::Rgba32Le | VbiPixfmt::Bgra32Le => { + // For RGBA32, green is byte 1 (RGBA) or 1 (BGRA) + raw[1] as u32 + } + VbiPixfmt::Rgb16Le + | VbiPixfmt::Bgr16Le + | VbiPixfmt::Rgba15Le + | VbiPixfmt::Bgra15Le + | VbiPixfmt::Argb15Le + | VbiPixfmt::Abgr15Le => { + let word = u16::from_le_bytes([raw[0], raw[1]]); + ((word & bs.green_mask as u16) >> bs.green_mask.trailing_zeros() as u16) as u32 + } + VbiPixfmt::Rgb16Be + | VbiPixfmt::Bgr16Be + | VbiPixfmt::Rgba15Be + | VbiPixfmt::Bgra15Be + | VbiPixfmt::Argb15Be + | VbiPixfmt::Abgr15Be => { + let word = u16::from_be_bytes([raw[0], raw[1]]); + ((word & bs.green_mask as u16) >> bs.green_mask.trailing_zeros() as u16) as u32 + } + _ => raw[0] as u32, // Default case for YUV formats + } +} + +#[allow(clippy::too_many_arguments)] +pub fn sample<'a>( + raw: &'a [u8], + i: u32, + bpp: usize, + bs: &Vbi3BitSlicer, + pixfmt: VbiPixfmt, + kind: Vbi3BitSlicerBit, + collect_points: bool, + points: &mut Option<&mut [Vbi3BitSlicerPoint]>, + raw_start: usize, + tr: u32, +) -> (u32, &'a [u8]) { + let r = &raw[(i >> 8) as usize * bpp..]; + let raw0 = get_green(r, bs, pixfmt); + let raw1 = get_green(&r[bpp..], bs, pixfmt); + let raw0 = ((raw1 as i32 - raw0 as i32) * (i & 255) as i32 + (raw0 << 8) as i32) >> 8; + + if collect_points { + if let Some(points) = points { + if let Some(first) = points.iter_mut().next() { + *first = Vbi3BitSlicerPoint { + kind, + index: ((raw.as_ptr() as usize - raw_start) * 256 + i as usize) as u32, + level: raw0 as u32, + thresh: tr, + }; + } + } + } + + (raw0 as u32, r) +} + +#[allow(clippy::too_many_arguments)] +pub fn payload( + bs: &Vbi3BitSlicer, + buffer: &mut [u8], + points: &mut Option<&mut [Vbi3BitSlicerPoint]>, + raw: &[u8], + raw_start: usize, + pixfmt: VbiPixfmt, + bpp: usize, + tr: u32, + collect_points: bool, +) -> bool { + let mut i = bs.phase_shift; + let tr = tr * 256; + let mut c = 0; + + // Process FRC bits + for _j in 0..bs.frc_bits { + // _j is unused + let (raw0, _) = sample( + raw, + i, + bpp, + bs, + pixfmt, + Vbi3BitSlicerBit::FrcBit, + collect_points, + points, + raw_start, + tr, + ); + c = c * 2 + (raw0 >= tr) as u32; + i += bs.step; + } + + if c != bs.frc { + return false; + } + + let buffer_len = buffer.len(); + let mut buffer_ptr = 0; + + match bs.endian { + 3 => { + // Bitwise, LSB first + for j in 0..bs.payload { + let (raw0, _) = sample( + raw, + i, + bpp, + bs, + pixfmt, + Vbi3BitSlicerBit::PayloadBit, + collect_points, + points, + raw_start, + tr, + ); + c = ((c >> 1) + ((raw0 >= tr) as u32)) << 7; + i += bs.step; + if (j & 7) == 7 && buffer_ptr < buffer_len { + buffer[buffer_ptr] = c as u8; + buffer_ptr += 1; + c = 0; + } + } + if buffer_ptr < buffer_len { + buffer[buffer_ptr] = (c >> ((8 - bs.payload) & 7)) as u8; + } + } + 2 => { + // Bitwise, MSB first + for j in 0..bs.payload { + let (raw0, _) = sample( + raw, + i, + bpp, + bs, + pixfmt, + Vbi3BitSlicerBit::PayloadBit, + collect_points, + points, + raw_start, + tr, + ); + c = c * 2 + (raw0 >= tr) as u32; + i += bs.step; + if (j & 7) == 7 && buffer_ptr < buffer_len { + buffer[buffer_ptr] = c as u8; + buffer_ptr += 1; + c = 0; + } + } + if buffer_ptr < buffer_len { + buffer[buffer_ptr] = (c & ((1 << (bs.payload & 7)) - 1)) as u8; + } + } + 1 => { + // Octets, LSB first + for _ in 0..(bs.payload / 8) { + let mut byte = 0; + for k in 0..8 { + let (raw0, _) = sample( + raw, + i, + bpp, + bs, + pixfmt, + Vbi3BitSlicerBit::PayloadBit, + collect_points, + points, + raw_start, + tr, + ); + byte += ((raw0 >= tr) as u8) << k; + i += bs.step; + } + if buffer_ptr < buffer_len { + buffer[buffer_ptr] = byte; + buffer_ptr += 1; + } + } + } + _ => { + // Octets, MSB first + for _ in 0..(bs.payload / 8) { + let mut byte = 0; + for _k in 0..8 { + // _k is unused + let (raw0, _) = sample( + raw, + i, + bpp, + bs, + pixfmt, + Vbi3BitSlicerBit::PayloadBit, + collect_points, + points, + raw_start, + tr, + ); + byte = byte * 2 + (raw0 >= tr) as u8; + i += bs.step; + } + if buffer_ptr < buffer_len { + buffer[buffer_ptr] = byte; + buffer_ptr += 1; + } + } + } + } + + true +} + +#[allow(clippy::too_many_arguments)] +pub fn core( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + mut points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], + pixfmt: VbiPixfmt, + bpp: usize, + oversampling: u32, + thresh_frac: u32, + collect_points: bool, +) -> bool { + let thresh0 = bs.thresh; + let raw_start = raw.as_ptr() as usize; + let mut raw = &raw[bs.skip as usize..]; + let mut cl = 0; + let mut c = 0; + let mut b1 = 0; + let mut points_index = 0; // Track the current index in the points slice + + for _ in 0..bs.cri_samples { + let tr = bs.thresh >> thresh_frac; + let raw0 = get_green(raw, bs, pixfmt); + let raw1 = get_green(&raw[bpp..], bs, pixfmt); + let raw1_diff = (raw1 as i32 - raw0 as i32).unsigned_abs(); + bs.thresh = (bs.thresh as i32 + (raw0 as i32 - tr as i32) * raw1_diff as i32) as u32; + let mut t = raw0 * oversampling; + + for _ in 0..oversampling { + let tavg = (t + oversampling / 2) / oversampling; + let b = (tavg >= tr) as u8; + + if b ^ b1 != 0 { + cl = bs.oversampling_rate >> 1; + } else { + cl += bs.cri_rate; + + if cl >= bs.oversampling_rate { + if collect_points { + if let Some(points_ref) = points.as_mut() { + if points_index < points_ref.len() { + points_ref[points_index] = Vbi3BitSlicerPoint { + kind: Vbi3BitSlicerBit::CriBit, + index: ((raw.as_ptr() as usize - raw_start) << 8) as u32, + level: tavg << 8, + thresh: tr << 8, + }; + points_index += 1; // Move to the next point + } + } + } + + cl -= bs.oversampling_rate; + c = c * 2 + b as u32; + if (c & bs.cri_mask) == bs.cri { + bs.thresh = thresh0; + return payload( + bs, + buffer, + &mut points, + raw, + raw_start, + pixfmt, + bpp, + tr, + collect_points, + ); + } + } + } + + b1 = b; + if oversampling > 1 { + t += raw1; + } + } + + raw = &raw[bpp..]; + } + + bs.thresh = thresh0; + if collect_points { + if let (Some(_points_ref), Some(n_points_ref)) = (points, n_points) { + // points_ref is unused + *n_points_ref = points_index as u32; // Use the points_index to calculate the number of points used + } + } + false +} + +pub fn bit_slicer_y8( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], +) -> bool { + let pixfmt = VbiPixfmt::Yuv420; // Pixel format for YUV420 + let bpp = 1; // Bytes per pixel + let oversampling = 4; // Oversampling rate + let thresh_frac = DEF_THR_FRAC; // Threshold fraction + let collect_points = points.is_some(); // Determine if points collection is enabled + + core( + bs, + buffer, + points, + n_points, + raw, + pixfmt, + bpp, + oversampling, + thresh_frac, + collect_points, + ) +} + +pub fn bit_slicer_yuyv( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], +) -> bool { + let pixfmt = VbiPixfmt::Yuyv; // Pixel format for YUYV + let bpp = 2; // Bytes per pixel + let oversampling = 4; // Oversampling rate + let thresh_frac = DEF_THR_FRAC; // Threshold fraction + let collect_points = points.is_some(); // Determine if points collection is enabled + + core( + bs, + buffer, + points, + n_points, + raw, + pixfmt, + bpp, + oversampling, + thresh_frac, + collect_points, + ) +} + +pub fn bit_slicer_rgb24_le( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], +) -> bool { + let pixfmt = VbiPixfmt::Rgb24; // Pixel format for RGB24 + let bpp = 3; // Bytes per pixel + let oversampling = 4; // Oversampling rate + let thresh_frac = DEF_THR_FRAC; // Threshold fraction + let collect_points = points.is_some(); // Determine if points collection is enabled + + core( + bs, + buffer, + points, + n_points, + raw, + pixfmt, + bpp, + oversampling, + thresh_frac, + collect_points, + ) +} + +pub fn bit_slicer_rgba24_le( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], +) -> bool { + let pixfmt = VbiPixfmt::Rgba32Le; // Pixel format for RGBA32 (Little Endian) + let bpp = 4; // Bytes per pixel + let oversampling = 4; // Oversampling rate + let thresh_frac = DEF_THR_FRAC; // Threshold fraction + let collect_points = points.is_some(); // Determine if points collection is enabled + + core( + bs, + buffer, + points, + n_points, + raw, + pixfmt, + bpp, + oversampling, + thresh_frac, + collect_points, + ) +} + +pub fn bit_slicer_rgb16_le( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], +) -> bool { + let pixfmt = VbiPixfmt::Rgb16Le; // Pixel format for RGB16 (Little Endian) + let bpp = 2; // Bytes per pixel + let oversampling = 4; // Oversampling rate + let thresh_frac = bs.thresh_frac; // Use the threshold fraction from the bit slicer + let collect_points = points.is_some(); // Determine if points collection is enabled + + core( + bs, + buffer, + points, + n_points, + raw, + pixfmt, + bpp, + oversampling, + thresh_frac, + collect_points, + ) +} + +pub fn bit_slicer_rgb16_be( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], +) -> bool { + let pixfmt = VbiPixfmt::Rgb16Be; // Pixel format for RGB16 (Big Endian) + let bpp = 2; // Bytes per pixel + let oversampling = 4; // Oversampling rate + let thresh_frac = bs.thresh_frac; // Use the threshold fraction from the bit slicer + let collect_points = points.is_some(); // if points collection is enabled + + core( + bs, + buffer, + points, + n_points, + raw, + pixfmt, + bpp, + oversampling, + thresh_frac, + collect_points, + ) +} + +pub fn low_pass_bit_slicer_y8( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + mut points: Option<&mut [Vbi3BitSlicerPoint]>, + n_points: Option<&mut u32>, + raw: &[u8], +) -> bool { + let bpp = bs.bytes_per_sample as usize; + let raw_start = raw.as_ptr() as usize; + let mut raw = &raw[bs.skip as usize..]; + let thresh0 = bs.thresh; + let mut c = !0u32; + let mut cl = 0; + let mut b1 = 0; + let mut points_idx = 0; + + let mut raw0sum = raw[0] as u32; + for m in bpp..(bpp << LP_AVG) { + if m < raw.len() { + raw0sum += raw[m] as u32; + } + } + + // Process CRI bits + for i in (0..bs.cri_samples).rev() { + let tr = bs.thresh >> bs.thresh_frac; + let raw0 = raw0sum; + + if raw.len() > (bpp << LP_AVG) { + raw0sum += raw[bpp << LP_AVG] as u32; + } + if !raw.is_empty() { + raw0sum -= raw[0] as u32; + } + + raw = &raw[bpp..]; + + let diff = (raw0sum as i32 - raw0 as i32).abs(); + bs.thresh = (bs.thresh as i32 + (raw0 as i32 - tr as i32) * diff) as u32; + + let b = (raw0 >= tr) as u8; + + if b ^ b1 != 0 { + cl = bs.oversampling_rate >> 1; + } else { + cl += bs.cri_rate; + + if cl >= bs.oversampling_rate { + if let Some(points_ref) = &mut points { + if points_idx < points_ref.len() { + points_ref[points_idx] = Vbi3BitSlicerPoint { + kind: Vbi3BitSlicerBit::CriBit, + index: ((raw.as_ptr() as usize - raw_start) * 256 / bpp + + (1 << LP_AVG) * 128) as u32, + level: raw0 << (8 - LP_AVG), + thresh: tr << (8 - LP_AVG), + }; + points_idx += 1; + } + } + + cl -= bs.oversampling_rate; + c = c * 2 + b as u32; + + if (c & bs.cri_mask) == bs.cri { + break; + } + } + } + + b1 = b; + + if i == 0 { + bs.thresh = thresh0; + if let Some(n_points_ref) = n_points { + *n_points_ref = points_idx as u32; + } + return false; + } + } + + // Process FRC and payload bits + let mut i = bs.phase_shift; + let tr = bs.thresh >> bs.thresh_frac; + let mut c = 0; + + for _j in 0..bs.frc_bits { + // _j is unused + let ii = (i >> 8) * bpp as u32; + let mut raw0 = if (ii as usize) < raw.len() { + raw[ii as usize] as u32 + } else { + 0 + }; + + for m in bpp..(bpp << LP_AVG) { + if (ii as usize + m) < raw.len() { + raw0 += raw[ii as usize + m] as u32; + } + } + + if let Some(points_ref) = &mut points { + if points_idx < points_ref.len() { + points_ref[points_idx] = Vbi3BitSlicerPoint { + kind: Vbi3BitSlicerBit::FrcBit, + index: ((raw.as_ptr() as usize - raw_start) * 256 / bpp + + ((1 << LP_AVG) * 128) as usize + + (ii as usize * 256)) as u32, + level: raw0 << (8 - LP_AVG), + thresh: tr << (8 - LP_AVG), + }; + points_idx += 1; + } + } + + c = c * 2 + (raw0 >= tr) as u32; + i += bs.step; + } + + if c != bs.frc { + return false; + } + + let mut buffer_ptr = 0; + c = 0; + + match bs.endian { + // Bitwise, LSB first + 3 => { + for j in 0..bs.payload { + let ii = (i >> 8) * bpp as u32; + let mut raw0 = if (ii as usize) < raw.len() { + raw[ii as usize] as u32 + } else { + 0 + }; + + for m in bpp..(bpp << LP_AVG) { + if (ii as usize + m) < raw.len() { + raw0 += raw[ii as usize + m] as u32; + } + } + + if let Some(points_ref) = &mut points { + if points_idx < points_ref.len() { + points_ref[points_idx] = Vbi3BitSlicerPoint { + kind: Vbi3BitSlicerBit::PayloadBit, + index: ((raw.as_ptr() as usize - raw_start) * 256 / bpp + + (1 << LP_AVG) as usize * 128 + + (ii as usize) * 256) as u32, + level: raw0 << (8 - LP_AVG), + thresh: tr << (8 - LP_AVG), + }; + points_idx += 1; + } + } + + c = ((c >> 1) + ((raw0 >= tr) as u32)) << 7; + i += bs.step; + + if (j & 7) == 7 && buffer_ptr < buffer.len() { + buffer[buffer_ptr] = c as u8; + buffer_ptr += 1; + c = 0; + } + } + + if buffer_ptr < buffer.len() { + buffer[buffer_ptr] = (c >> ((8 - bs.payload) & 7)) as u8; + } + } + + // Bitwise, MSB first + 2 => { + for j in 0..bs.payload { + let ii = (i >> 8) * bpp as u32; + let mut raw0 = if (ii as usize) < raw.len() { + raw[ii as usize] as u32 + } else { + 0 + }; + + for m in bpp..(bpp << LP_AVG) { + if (ii as usize + m) < raw.len() { + raw0 += raw[ii as usize + m] as u32; + } + } + + if let Some(points_ref) = &mut points { + if points_idx < points_ref.len() { + points_ref[points_idx] = Vbi3BitSlicerPoint { + kind: Vbi3BitSlicerBit::PayloadBit, + index: ((raw.as_ptr() as usize - raw_start) * 256 / bpp + + (1 << LP_AVG) * 128 + + (ii as usize) * 256) as u32, + level: raw0 << (8 - LP_AVG), + thresh: tr << (8 - LP_AVG), + }; + points_idx += 1; + } + } + + c = c * 2 + (raw0 >= tr) as u32; + i += bs.step; + + if (j & 7) == 7 && buffer_ptr < buffer.len() { + buffer[buffer_ptr] = c as u8; + buffer_ptr += 1; + c = 0; + } + } + + if buffer_ptr < buffer.len() { + buffer[buffer_ptr] = (c & ((1 << (bs.payload & 7)) - 1)) as u8; + } + } + + // Octets, LSB first + 1 => { + for _j in 0..(bs.payload / 8) { + // _j is unused + let mut byte = 0; + for k in 0..8 { + let ii = (i >> 8) * bpp as u32; + let mut raw0 = if (ii as usize) < raw.len() { + raw[ii as usize] as u32 + } else { + 0 + }; + + for m in bpp..(bpp << LP_AVG) { + if (ii as usize + m) < raw.len() { + raw0 += raw[ii as usize + m] as u32; + } + } + + if let Some(points_ref) = &mut points { + if points_idx < points_ref.len() { + points_ref[points_idx] = Vbi3BitSlicerPoint { + kind: Vbi3BitSlicerBit::PayloadBit, + index: (((raw.as_ptr() as usize - raw_start) * 256 / bpp) as u32) + + ((1 << LP_AVG) * 128) + + (ii * 256), + level: raw0 << (8 - LP_AVG), + thresh: tr << (8 - LP_AVG), + }; + points_idx += 1; + } + } + + byte += ((raw0 >= tr) as u8) << k; + i += bs.step; + } + + if buffer_ptr < buffer.len() { + buffer[buffer_ptr] = byte; + buffer_ptr += 1; + } + } + } + + // Octets, MSB first (default) + _ => { + for _j in 0..(bs.payload / 8) { + // _j is unused + let mut byte = 0; + for _k in 0..8 { + // _k is unused + let ii = (i >> 8) * bpp as u32; + let mut raw0 = if (ii as usize) < raw.len() { + raw[ii as usize] as u32 + } else { + 0 + }; + + for m in bpp..(bpp << LP_AVG) { + if (ii as usize + m) < raw.len() { + raw0 += raw[ii as usize + m] as u32; + } + } + + if let Some(points_ref) = &mut points { + if points_idx < points_ref.len() { + points_ref[points_idx] = Vbi3BitSlicerPoint { + kind: Vbi3BitSlicerBit::PayloadBit, + index: ((raw.as_ptr() as usize - raw_start) * 256 / bpp + + ((1 << LP_AVG) * 128) as usize + + (ii as usize * 256)) + as u32, + level: raw0 << (8 - LP_AVG), + thresh: tr << (8 - LP_AVG), + }; + points_idx += 1; + } + } + + byte = byte * 2 + (raw0 >= tr) as u8; + i += bs.step; + } + + if buffer_ptr < buffer.len() { + buffer[buffer_ptr] = byte; + buffer_ptr += 1; + } + } + } + } + + // Update points count if collecting + if let Some(n_points_ref) = n_points { + *n_points_ref = points_idx as u32; + } + + true +} + +#[allow(clippy::too_many_arguments)] +pub fn vbi3_bit_slicer_set_params( + bs: &mut Vbi3BitSlicer, + sample_format: VbiPixfmt, + sampling_rate: u32, + sample_offset: u32, + samples_per_line: u32, + cri: u32, + cri_mask: u32, + cri_bits: u32, + cri_rate: u32, + cri_end: u32, + frc: u32, + frc_bits: u32, + payload_bits: u32, + payload_rate: u32, + modulation: VbiModulation, +) -> bool { + assert!(cri_bits <= 32); + assert!(frc_bits <= 32); + assert!(payload_bits <= 32767); + assert!(samples_per_line <= 32767); + + if cri_rate > sampling_rate { + error!("cri_rate {} > sampling_rate {}.", cri_rate, sampling_rate); + return set_null_function(bs); + } + + if payload_rate > sampling_rate { + error!( + "payload_rate {} > sampling_rate {}.", + payload_rate, sampling_rate + ); + return set_null_function(bs); + } + + let min_samples_per_bit = sampling_rate / cri_rate.max(payload_rate); + bs.sample_format = sample_format; + + let c_mask = if cri_bits == 32 { + !0u32 + } else { + (1u32 << cri_bits) - 1 + }; + let f_mask = if frc_bits == 32 { + !0u32 + } else { + (1u32 << frc_bits) - 1 + }; + + // Set bytes_per_sample according to pixel format + bs.bytes_per_sample = match sample_format { + VbiPixfmt::Yuv420 => 1, + VbiPixfmt::Yuyv | VbiPixfmt::Yvyu | VbiPixfmt::Uyvy | VbiPixfmt::Vyuy => 2, + VbiPixfmt::Rgb24 | VbiPixfmt::Bgr24 => 3, + VbiPixfmt::Rgba32Le | VbiPixfmt::Rgba32Be | VbiPixfmt::Bgra32Le | VbiPixfmt::Bgra32Be => 4, + VbiPixfmt::Rgb16Le | VbiPixfmt::Rgb16Be | VbiPixfmt::Bgr16Le | VbiPixfmt::Bgr16Be => 2, + VbiPixfmt::Rgba15Le | VbiPixfmt::Rgba15Be | VbiPixfmt::Bgra15Le | VbiPixfmt::Bgra15Be => 2, + VbiPixfmt::Argb15Le | VbiPixfmt::Argb15Be | VbiPixfmt::Abgr15Le | VbiPixfmt::Abgr15Be => 2, + _ => { + error!("Unknown sample_format {:?}.", sample_format); + return set_null_function(bs); + } + }; + + let (oversampling, skip, _func) = match sample_format { + // unused _func + VbiPixfmt::Yuv420 => { + let func = if min_samples_per_bit > (3 << (LP_AVG - 1)) { + low_pass_bit_slicer_y8 + } else { + bit_slicer_y8 + }; + ( + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + 1 + } else { + 4 + }, + 0, + func, + ) + } + VbiPixfmt::Yuyv | VbiPixfmt::Yvyu => { + let func = if min_samples_per_bit > (3 << (LP_AVG - 1)) { + low_pass_bit_slicer_y8 + } else { + bit_slicer_yuyv + }; + ( + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + 1 + } else { + 4 + }, + 0, + func, + ) + } + VbiPixfmt::Uyvy | VbiPixfmt::Vyuy => { + let func = if min_samples_per_bit > (3 << (LP_AVG - 1)) { + low_pass_bit_slicer_y8 + } else { + bit_slicer_yuyv + }; + ( + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + 1 + } else { + 4 + }, + 1, + func, + ) + } + VbiPixfmt::Rgba32Le | VbiPixfmt::Bgra32Le => { + let func = if min_samples_per_bit > (3 << (LP_AVG - 1)) { + low_pass_bit_slicer_y8 + } else { + bit_slicer_rgba24_le + }; + ( + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + 1 + } else { + 4 + }, + 1, + func, + ) + } + VbiPixfmt::Rgba32Be | VbiPixfmt::Bgra32Be => { + let func = if min_samples_per_bit > (3 << (LP_AVG - 1)) { + low_pass_bit_slicer_y8 + } else { + bit_slicer_rgba24_le + }; + ( + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + 1 + } else { + 4 + }, + 2, + func, + ) + } + VbiPixfmt::Rgb24 | VbiPixfmt::Bgr24 => { + let func = if min_samples_per_bit > (3 << (LP_AVG - 1)) { + low_pass_bit_slicer_y8 + } else { + bit_slicer_rgb24_le + }; + ( + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + 1 + } else { + 4 + }, + 1, + func, + ) + } + VbiPixfmt::Rgb16Le | VbiPixfmt::Bgr16Le => { + bs.green_mask = 0x07E0; + bs.thresh = 105 << (5 - 2 + 12); + bs.thresh_frac = 12; + ( + 4, + 0, + bit_slicer_rgb16_le + as for<'a, 'b, 'c, 'd, 'e> fn( + &'a mut Vbi3BitSlicer, + &'b mut _, + Option<&'c mut _>, + Option<&'d mut _>, + &'e _, + ) -> _, + ) + } + VbiPixfmt::Rgb16Be | VbiPixfmt::Bgr16Be => { + bs.green_mask = 0x07E0; + bs.thresh = 105 << (5 - 2 + 12); + bs.thresh_frac = 12; + ( + 4, + 0, + bit_slicer_rgb16_be + as for<'a, 'b, 'c, 'd, 'e> fn( + &'a mut Vbi3BitSlicer, + &'b mut _, + Option<&'c mut _>, + Option<&'d mut _>, + &'e _, + ) -> _, + ) + } + VbiPixfmt::Rgba15Le | VbiPixfmt::Bgra15Le => { + bs.green_mask = 0x03E0; + bs.thresh = 105 << (5 - 3 + 11); + bs.thresh_frac = 11; + ( + 4, + 0, + bit_slicer_rgb16_le + as for<'a, 'b, 'c, 'd, 'e> fn( + &'a mut Vbi3BitSlicer, + &'b mut _, + Option<&'c mut _>, + Option<&'d mut _>, + &'e _, + ) -> _, + ) + } + VbiPixfmt::Rgba15Be | VbiPixfmt::Bgra15Be => { + bs.green_mask = 0x03E0; + bs.thresh = 105 << (5 - 3 + 11); + bs.thresh_frac = 11; + ( + 4, + 0, + bit_slicer_rgb16_be + as for<'a, 'b, 'c, 'd, 'e> fn( + &'a mut Vbi3BitSlicer, + &'b mut _, + Option<&'c mut _>, + Option<&'d mut _>, + &'e _, + ) -> _, + ) + } + VbiPixfmt::Argb15Le | VbiPixfmt::Abgr15Le => { + bs.green_mask = 0x07C0; + bs.thresh = 105 << (6 - 3 + 12); + bs.thresh_frac = 12; + ( + 4, + 0, + bit_slicer_rgb16_le + as for<'a, 'b, 'c, 'd, 'e> fn( + &'a mut Vbi3BitSlicer, + &'b mut _, + Option<&'c mut _>, + Option<&'d mut _>, + &'e _, + ) -> _, + ) + } + VbiPixfmt::Argb15Be | VbiPixfmt::Abgr15Be => { + bs.green_mask = 0x07C0; + bs.thresh = 105 << (6 - 3 + 12); + bs.thresh_frac = 12; + ( + 4, + 0, + bit_slicer_rgb16_be + as for<'a, 'b, 'c, 'd, 'e> fn( + &'a mut Vbi3BitSlicer, + &'b mut _, + Option<&'c mut _>, + Option<&'d mut _>, + &'e _, + ) -> _, + ) + } + _ => { + error!("Unknown sample_format {:?}.", sample_format); + return set_null_function(bs); + } + }; + + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + bs.thresh <<= LP_AVG - 2; + bs.thresh_frac += LP_AVG - 2; + } else { + bs.thresh = 105 << DEF_THR_FRAC; + bs.thresh_frac = DEF_THR_FRAC; + } + + bs.skip = sample_offset * bs.bytes_per_sample + skip; + bs.cri_mask = cri_mask & c_mask; + bs.cri = cri & bs.cri_mask; + + let cri_samples = ((sampling_rate as u64) * (cri_bits as u64)) / (cri_rate as u64); + let data_bits = payload_bits + frc_bits; + let data_samples = ((sampling_rate as u64) * (data_bits as u64)) / (payload_rate as u64); + + bs.total_bits = cri_bits + data_bits; + + if sample_offset > samples_per_line + || (cri_samples + data_samples) > (samples_per_line - sample_offset) as u64 + { + error!( + "{} samples_per_line too small for sample_offset {} + {} cri_bits ({} samples) + {} frc_bits and {} payload_bits ({} samples).", + samples_per_line, sample_offset, cri_bits, cri_samples, frc_bits, payload_bits, data_samples + ); + return set_null_function(bs); + } + + let cri_end = cri_end.min(samples_per_line - data_samples as u32); + bs.cri_samples = cri_end - sample_offset; + bs.cri_rate = cri_rate; + bs.oversampling_rate = sampling_rate * oversampling; + bs.frc = frc & f_mask; + bs.frc_bits = frc_bits; + bs.step = sampling_rate * 256 / payload_rate; + + if payload_bits & 7 != 0 { + bs.payload = payload_bits; + bs.endian = 3; + } else { + bs.payload = payload_bits >> 3; + bs.endian = 1; + } + + match modulation { + VbiModulation::NrzMsb => { + bs.endian -= 1; + bs.phase_shift = (sampling_rate as f64 * 256.0 / cri_rate as f64 * 0.5 + + bs.step as f64 * 0.5 + + 128.0) as u32; + } + VbiModulation::NrzLsb => { + bs.phase_shift = (sampling_rate as f64 * 256.0 / cri_rate as f64 * 0.5 + + bs.step as f64 * 0.5 + + 128.0) as u32; + } + VbiModulation::BiphaseMsb => { + bs.endian -= 1; + bs.phase_shift = (sampling_rate as f64 * 256.0 / cri_rate as f64 * 0.5 + + bs.step as f64 * 0.25 + + 128.0) as u32; + } + VbiModulation::BiphaseLsb => { + bs.phase_shift = (sampling_rate as f64 * 256.0 / cri_rate as f64 * 0.5 + + bs.step as f64 * 0.25 + + 128.0) as u32; + } + } + + // Set the appropriate function based on the sample format + match sample_format { + VbiPixfmt::Yuv420 => { + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + bs.func = Some(wrapper_low_pass_bit_slicer_y8); + } else { + bs.func = Some(wrapper_bit_slicer_y8); + } + } + VbiPixfmt::Yuyv | VbiPixfmt::Yvyu => { + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + bs.func = Some(wrapper_low_pass_bit_slicer_y8); + } else { + bs.func = Some(wrapper_bit_slicer_yuyv); + } + } + VbiPixfmt::Uyvy | VbiPixfmt::Vyuy => { + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + bs.func = Some(wrapper_low_pass_bit_slicer_y8); + } else { + bs.func = Some(wrapper_bit_slicer_yuyv); + } + } + VbiPixfmt::Rgba32Le | VbiPixfmt::Bgra32Le => { + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + bs.func = Some(wrapper_low_pass_bit_slicer_y8); + } else { + bs.func = Some(wrapper_bit_slicer_rgba24_le); + } + } + VbiPixfmt::Rgba32Be | VbiPixfmt::Bgra32Be => { + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + bs.func = Some(wrapper_low_pass_bit_slicer_y8); + } else { + bs.func = Some(wrapper_bit_slicer_rgba24_le); + } + } + VbiPixfmt::Rgb24 | VbiPixfmt::Bgr24 => { + if min_samples_per_bit > (3 << (LP_AVG - 1)) { + bs.func = Some(wrapper_low_pass_bit_slicer_y8); + } else { + bs.func = Some(wrapper_bit_slicer_rgb24_le); + } + } + VbiPixfmt::Rgb16Le | VbiPixfmt::Bgr16Le => { + bs.func = Some(wrapper_bit_slicer_rgb16_le); + } + VbiPixfmt::Rgb16Be | VbiPixfmt::Bgr16Be => { + bs.func = Some(wrapper_bit_slicer_rgb16_be); + } + _ => { + bs.func = Some(wrapper_bit_slicer_y8); + } + } + + unsafe extern "C" fn wrapper_bit_slicer_y8( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, + ) -> i32 { + let bs = unsafe { &mut *bs }; + let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, bs.payload as usize) }; + let points = if points.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts_mut(points, bs.total_bits as usize) }) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(unsafe { &mut *n_points }) + }; + let raw = unsafe { + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize) + }; + + if bit_slicer_y8(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } + } + + unsafe extern "C" fn wrapper_bit_slicer_yuyv( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, + ) -> i32 { + let bs = unsafe { &mut *bs }; + let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, bs.payload as usize) }; + let points = if points.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts_mut(points, bs.total_bits as usize) }) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(unsafe { &mut *n_points }) + }; + let raw = unsafe { + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize) + }; + + if bit_slicer_yuyv(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } + } + + unsafe extern "C" fn wrapper_bit_slicer_rgb24_le( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, + ) -> i32 { + let bs = unsafe { &mut *bs }; + let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, bs.payload as usize) }; + let points = if points.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts_mut(points, bs.total_bits as usize) }) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(unsafe { &mut *n_points }) + }; + let raw = unsafe { + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize) + }; + + if bit_slicer_rgb24_le(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } + } + + unsafe extern "C" fn wrapper_bit_slicer_rgba24_le( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, + ) -> i32 { + let bs = unsafe { &mut *bs }; + let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, bs.payload as usize) }; + let points = if points.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts_mut(points, bs.total_bits as usize) }) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(unsafe { &mut *n_points }) + }; + let raw = unsafe { + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize) + }; + + if bit_slicer_rgba24_le(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } + } + + unsafe extern "C" fn wrapper_bit_slicer_rgb16_le( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, + ) -> i32 { + let bs = unsafe { &mut *bs }; + let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, bs.payload as usize) }; + let points = if points.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts_mut(points, bs.total_bits as usize) }) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(unsafe { &mut *n_points }) + }; + let raw = unsafe { + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize) + }; + + if bit_slicer_rgb16_le(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } + } + + unsafe extern "C" fn wrapper_bit_slicer_rgb16_be( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, + ) -> i32 { + let bs = unsafe { &mut *bs }; + let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, bs.payload as usize) }; + let points = if points.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts_mut(points, bs.total_bits as usize) }) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(unsafe { &mut *n_points }) + }; + let raw = unsafe { + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize) + }; + + if bit_slicer_rgb16_be(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } + } + + unsafe extern "C" fn wrapper_low_pass_bit_slicer_y8( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, + ) -> i32 { + let bs = unsafe { &mut *bs }; + let buffer = unsafe { std::slice::from_raw_parts_mut(buffer, bs.payload as usize) }; + let points = if points.is_null() { + None + } else { + Some(unsafe { std::slice::from_raw_parts_mut(points, bs.total_bits as usize) }) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(unsafe { &mut *n_points }) + }; + let raw = unsafe { + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize) + }; + + if low_pass_bit_slicer_y8(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } + } + true +} + +pub fn set_null_function(bs: &mut Vbi3BitSlicer) -> bool { + bs.func = Some(null_function); + false +} + +pub fn vbi3_raw_decoder_decode( + rd: &mut Vbi3RawDecoder, + sliced: &mut [VbiSliced], + max_lines: u32, + mut raw: &[u8], +) -> u32 { + if rd.services == 0 { + return 0; + } + + let scan_lines = (rd.sampling.count[0] + rd.sampling.count[1]) as usize; + let pitch = (rd.sampling.bytes_per_line as usize) << rd.sampling.interlaced; + let mut pattern = rd.pattern; + let raw1 = raw; + let mut sliced_begin = sliced.as_mut_ptr(); + let sliced_end = unsafe { sliced_begin.add(max_lines as usize) }; + + if RAW_DECODER_PATTERN_DUMP { + let mut output = Vec::new(); + _vbi3_raw_decoder_dump(rd, &mut output); + } + + for i in 0..scan_lines { + if sliced_begin >= sliced_end { + break; + } + + let interlaced = rd.sampling.interlaced; + let count0 = rd.sampling.count[0]; + let bytes_per_line = rd.sampling.bytes_per_line; + + if interlaced != 0 && i == count0 as usize { + raw = &raw1[bytes_per_line as usize..]; + } + + sliced_begin = decode_pattern(rd, sliced_begin, pattern, i as u32, raw); + + unsafe { + pattern = pattern.add(_VBI3_RAW_DECODER_MAX_WAYS); + } + raw = &raw[pitch..]; + } + + rd.readjust = (rd.readjust + 1) & 15; + + unsafe { sliced_begin.offset_from(sliced.as_mut_ptr()) as u32 } +} + +pub fn vbi_sliced_name(service: VbiServiceSet) -> Option<&'static str> { + // These are ambiguous + if service == VBI_SLICED_CAPTION_525 { + return Some("Closed Caption 525"); + } + if service == VBI_SLICED_CAPTION_625 { + return Some("Closed Caption 625"); + } + if service == (VBI_SLICED_VPS | VBI_SLICED_VPS_F2) { + return Some("Video Program System"); + } + if service == VBI_SLICED_TELETEXT_B_L25_625 { + return Some("Teletext System B 625 Level 2.5"); + } + + // Incorrect, no longer in table + if service == VBI_SLICED_TELETEXT_BD_525 { + return Some("Teletext System B/D"); + } + + // Find the service in the table + if let Some(par) = find_service_par(service) { + return Some(unsafe { + std::ffi::CStr::from_ptr(par.label) + .to_str() + .unwrap_or_default() + }); + } + + None +} + +pub fn find_service_par(service: VbiServiceSet) -> Option<&'static VbiServicePar> { + VBI_SERVICE_TABLE.iter().find(|par| par.id == service) +} + +pub fn slice( + rd: &mut Vbi3RawDecoder, + sliced: *mut VbiSliced, + job: &mut VbiRawDecoderJob, + i: u32, + raw: &[u8], +) -> bool { + // VbiBitSlicer to Vbi3BitSlicer adapter function + fn vbi_to_vbi3_bit_slicer(bs: &mut VbiBitSlicer) -> &mut Vbi3BitSlicer { + unsafe { &mut *(bs as *mut VbiBitSlicer as *mut Vbi3BitSlicer) } + } + + if rd.debug != 0 && !rd.sp_lines.is_null() { + let sp_line = unsafe { &mut *rd.sp_lines.add(i as usize) }; + + let points_len = sp_line.points.len(); + vbi3_bit_slicer_slice_with_points( + vbi_to_vbi3_bit_slicer(&mut job.slicer), + unsafe { &mut (*sliced).data }, + std::mem::size_of_val(&(unsafe { *sliced }).data), + &mut sp_line.points, + &mut sp_line.n_points, + points_len, + raw, + ) + } else { + vbi3_bit_slicer_slice( + vbi_to_vbi3_bit_slicer(&mut job.slicer), + unsafe { &mut (*sliced).data }, + std::mem::size_of_val(&(unsafe { *sliced }).data), + raw, + ) + } +} + +pub fn vbi3_bit_slicer_slice( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + buffer_size: usize, + raw: &[u8], +) -> bool { + assert!(bs.func.is_some(), "Bit slicer function is not set."); + assert!(!buffer.is_empty(), "Buffer cannot be null."); + assert!(!raw.is_empty(), "Raw data cannot be null."); + + if bs.payload > (buffer_size * 8) as u32 { + error!( + "buffer_size {} < {} bits of payload.", + buffer_size * 8, + bs.payload + ); + return false; + } + + if let Some(func) = bs.func { + unsafe { + func( + bs, + buffer.as_mut_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + raw.as_ptr(), + ) != 0 + } + } else { + false + } +} + +pub fn vbi3_bit_slicer_slice_with_points( + bs: &mut Vbi3BitSlicer, + buffer: &mut [u8], + buffer_size: usize, + points: &mut [Vbi3BitSlicerPoint], + n_points: &mut u32, + max_points: usize, + raw: &[u8], +) -> bool { + const PIXFMT: VbiPixfmt = VbiPixfmt::Yuv420; // Equivalent to VBI_PIXFMT_Y8 + const BPP: usize = 1; // Bytes per pixel + const OVERSAMPLING: u32 = 4; // Oversampling rate + const THRESH_FRAC: u32 = DEF_THR_FRAC; // Threshold fraction + const COLLECT_POINTS: bool = true; // Collect points + + assert!(!buffer.is_empty(), "Buffer cannot be null."); + assert!(!points.is_empty(), "Points cannot be null."); + // assert!(n_points.is_some(), "n_points cannot be null."); // not needed + assert!(!raw.is_empty(), "Raw data cannot be null."); + + *n_points = 0; + + if bs.payload > (buffer_size * 8) as u32 { + error!( + "buffer_size {} < {} bits of payload.", + buffer_size * 8, + bs.payload + ); + return false; + } + + if bs.total_bits as usize > max_points { + error!( + "max_points {} < {} CRI, FRC and payload bits.", + max_points, bs.total_bits + ); + return false; + } + + if let Some(func) = bs.func { + if std::ptr::fn_addr_eq( + func, + wrapper_low_pass_bit_slicer_y8 + as unsafe extern "C" fn( + *mut Vbi3BitSlicer, + *mut u8, + *mut Vbi3BitSlicerPoint, + *mut u32, + *const u8, + ) -> i32, + ) { + return unsafe { + func( + bs, + buffer.as_mut_ptr(), + points.as_mut_ptr(), + n_points, + raw.as_ptr(), + ) != 0 + }; + } else if !std::ptr::fn_addr_eq( + func, + wrapper_bit_slicer_y8 + as unsafe extern "C" fn( + *mut Vbi3BitSlicer, + *mut u8, + *mut Vbi3BitSlicerPoint, + *mut u32, + *const u8, + ) -> i32, + ) { + error!( + "Function not implemented for pixfmt {:?}.", + bs.sample_format + ); + return unsafe { + func( + bs, + buffer.as_mut_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + raw.as_ptr(), + ) != 0 + }; + } + } + + core( + bs, + buffer, + Some(points), + Some(n_points), + raw, + PIXFMT, + BPP, + OVERSAMPLING, + THRESH_FRAC, + COLLECT_POINTS, + ) +} + +pub unsafe extern "C" fn wrapper_low_pass_bit_slicer_y8( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, +) -> i32 { + let bs = &mut *bs; + let buffer = std::slice::from_raw_parts_mut(buffer, bs.payload as usize); + let points = if points.is_null() { + None + } else { + Some(std::slice::from_raw_parts_mut( + points, + bs.total_bits as usize, + )) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(&mut *n_points) + }; + let raw = + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize); + + if low_pass_bit_slicer_y8(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } +} + +pub unsafe extern "C" fn wrapper_bit_slicer_y8( + bs: *mut Vbi3BitSlicer, + buffer: *mut u8, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const u8, +) -> i32 { + let bs = &mut *bs; + let buffer = std::slice::from_raw_parts_mut(buffer, bs.payload as usize); + let points = if points.is_null() { + None + } else { + Some(std::slice::from_raw_parts_mut( + points, + bs.total_bits as usize, + )) + }; + let n_points = if n_points.is_null() { + None + } else { + Some(&mut *n_points) + }; + let raw = + std::slice::from_raw_parts(raw, bs.bytes_per_sample as usize * bs.total_bits as usize); + + if bit_slicer_y8(bs, buffer, points, n_points, raw) { + 1 // true + } else { + 0 // false + } +} + +pub fn _vbi3_raw_decoder_dump(rd: &Vbi3RawDecoder, fp: &mut dyn std::io::Write) { + writeln!(fp, "vbi3_raw_decoder {:p}", rd).unwrap(); + + if rd.services == 0 { + writeln!(fp, " services 0x00000000").unwrap(); + return; + } + + writeln!(fp, " services 0x{:08x}", rd.services).unwrap(); + + for (i, job) in rd.jobs.iter().enumerate().take(rd.n_jobs as usize) { + writeln!( + fp, + " job {}: 0x{:08x} ({})", + i + 1, + job.id, + vbi_sliced_name(job.id).unwrap_or("Unknown") + ) + .unwrap(); + } + + if rd.pattern.is_null() { + writeln!(fp, " no pattern").unwrap(); + return; + } + + let sp = &rd.sampling; + + for i in 0..(sp.count[0] + sp.count[1]) as usize { + write!(fp, " ").unwrap(); + dump_pattern_line(rd, i, fp); + } +} + +pub fn dump_pattern_line(rd: &Vbi3RawDecoder, row: usize, fp: &mut dyn std::io::Write) { + let sp = &rd.sampling; + + let line; + if sp.interlaced != 0 { + let field = row & 1; + + if sp.start[field] == 0 { + line = 0; + } else { + line = sp.start[field] as usize + (row >> 1); + } + } else if row >= sp.count[0] as usize { + if sp.start[1] == 0 { + line = 0; + } else { + line = sp.start[1] as usize + row - sp.count[0] as usize; + } + } else if sp.start[0] == 0 { + line = 0; + } else { + line = sp.start[0] as usize + row; + } + + writeln!(fp, "scan line {:3}: ", line).unwrap(); + + for i in 0.._VBI3_RAW_DECODER_MAX_WAYS { + let pos = row * _VBI3_RAW_DECODER_MAX_WAYS; + write!(fp, "{:02x} ", unsafe { *rd.pattern.add(pos + i) as u8 }).unwrap(); + } + + writeln!(fp).unwrap(); +} diff --git a/src/rust/lib_ccxr/src/decoder_vbi/mod.rs b/src/rust/lib_ccxr/src/decoder_vbi/mod.rs new file mode 100644 index 000000000..338f66b3b --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/mod.rs @@ -0,0 +1,10 @@ +pub mod exit_codes; +pub mod functions_vbi_decode; +pub mod functions_vbi_main; +pub mod functions_vbi_slice; +pub mod structs_ccdecode; +pub mod structs_isdb; +pub mod structs_vbi_decode; +pub mod structs_vbi_slice; +pub mod structs_xds; +pub mod vbi_service_table; diff --git a/src/rust/lib_ccxr/src/decoder_vbi/structs_ccdecode.rs b/src/rust/lib_ccxr/src/decoder_vbi/structs_ccdecode.rs new file mode 100644 index 000000000..024def698 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/structs_ccdecode.rs @@ -0,0 +1,285 @@ +use crate::decoder_vbi::exit_codes::*; +use crate::decoder_vbi::structs_isdb::CcxDecoderVbiCtx; +use crate::decoder_vbi::structs_xds::CcSubtitle; +use crate::decoder_vbi::structs_xds::XdsBuffer; + +use crate::time::Timestamp; + +use std::ptr::null_mut; + +pub struct LibCcDecode { + pub cc_stats: [i32; 4], + pub saw_caption_block: i32, + pub processed_enough: i32, // If 1, we have enough lines, time, etc. + + /* 608 contexts - note that this shouldn't be global, they should be + * per program */ + pub context_cc608_field_1: *mut std::ffi::c_void, + pub context_cc608_field_2: *mut std::ffi::c_void, + + pub no_rollup: i32, // If 1, write one line at a time + pub noscte20: i32, + pub fix_padding: i32, // Replace 0000 with 8080 in HDTV (needed for some cards) + pub write_format: crate::common::OutputFormat, // 0 = Raw, 1 = srt, 2 = SMI + pub extraction_start: Option, + pub extraction_end: Option, // Segment we actually process + pub subs_delay: i64, // ms to delay (or advance) subs + pub extract: i32, // Extract 1st, 2nd or both fields + pub fullbin: i32, // Disable pruning of padding cc blocks + // TODO when cc_subtitle completed + // pub dec_sub: cc_subtitle, + pub in_bufferdatatype: crate::common::BufferdataType, + pub hauppauge_mode: u32, // If 1, use PID=1003, process specially and so on + + pub frames_since_last_gop: i32, + /* GOP-based timing */ + pub saw_gop_header: i32, + /* Time info for timed-transcript */ + pub max_gop_length: i32, // (Maximum) length of a group of pictures + pub last_gop_length: i32, // Length of the previous group of pictures + pub total_pulldownfields: u32, + pub total_pulldownframes: u32, + pub program_number: i32, + pub list: HList, + pub timing: *mut crate::time::TimingContext, + pub codec: crate::common::Codec, // Can also be SelectCodec + + // Set to true if data is buffered + pub has_ccdata_buffered: i32, + pub is_alloc: i32, + + pub avc_ctx: *mut AvcCtx, + pub private_data: *mut std::ffi::c_void, + + /* General video information */ + pub current_hor_size: u32, + pub current_vert_size: u32, + pub current_aspect_ratio: u32, + pub current_frame_rate: u32, // Assume standard fps, 29.97 + + /* Required in es_function.c */ + pub no_bitstream_error: i32, + pub saw_seqgoppic: i32, + pub in_pic_data: i32, + + pub current_progressive_sequence: u32, + pub current_pulldownfields: u32, + + pub temporal_reference: i32, + pub picture_coding_type: crate::common::FrameType, + pub num_key_frames: u32, + pub picture_structure: u32, + pub repeat_first_field: u32, + pub progressive_frame: u32, + pub pulldownfields: u32, + + /* Required in es_function.c and es_userdata.c */ + pub top_field_first: u32, // Needs to be global + + /* Stats. Modified in es_userdata.c */ + pub stat_numuserheaders: i32, + pub stat_dvdccheaders: i32, + pub stat_scte20ccheaders: i32, + pub stat_replay5000headers: i32, + pub stat_replay4000headers: i32, + pub stat_dishheaders: i32, + pub stat_hdtv: i32, + pub stat_divicom: i32, + pub false_pict_header: i32, + // TODO when 708 completed + // pub dtvcc: *mut DtvccCtx, + pub current_field: i32, + + // Analyse/use the picture information + pub maxtref: i32, // Use to remember the temporal reference number + + pub cc_data_count: [i32; SORTBUF], + // Store fts; + pub cc_fts: [i64; SORTBUF], + // Store HD CC packets + pub cc_data_pkts: [[u8; 10 * 31 * 3 + 1]; SORTBUF], // *10, because MP4 seems to have different limits + + // The sequence number of the current anchor frame. All currently read + // B-Frames belong to this I- or P-frame. + pub anchor_seq_number: i32, + pub xds_ctx: *mut XdsContext, + // TODO when vbi completed + pub vbi_decoder: *mut CcxDecoderVbiCtx, + + // TODO when cc_subtitle completed + pub writedata: Option< + extern "C" fn( + data: *const u8, + length: i32, + private_data: *mut std::ffi::c_void, + sub: *mut CcSubtitle, + ) -> i32, + >, + + // DVB subtitle related + pub ocr_quantmode: i32, + pub prev: *mut LibCcDecode, +} +impl Default for LibCcDecode { + fn default() -> Self { + LibCcDecode { + vbi_decoder: std::ptr::null_mut(), + writedata: None, + cc_stats: [0; 4], + saw_caption_block: 0, + processed_enough: 0, + context_cc608_field_1: std::ptr::null_mut(), + context_cc608_field_2: std::ptr::null_mut(), + no_rollup: 0, + noscte20: 0, + fix_padding: 0, + write_format: crate::common::OutputFormat::Raw, + extraction_start: None, + extraction_end: None, + subs_delay: 0, + extract: 0, + fullbin: 0, + in_bufferdatatype: crate::common::BufferdataType::Unknown, + hauppauge_mode: 0, + frames_since_last_gop: 0, + saw_gop_header: 0, + max_gop_length: 0, + last_gop_length: 0, + total_pulldownfields: 0, + total_pulldownframes: 0, + program_number: 0, + list: HList::default(), + timing: std::ptr::null_mut(), + codec: crate::common::Codec::Dvb, + has_ccdata_buffered: 0, + is_alloc: 0, + avc_ctx: std::ptr::null_mut(), + private_data: std::ptr::null_mut(), + current_hor_size: 0, + current_vert_size: 0, + current_aspect_ratio: 0, + current_frame_rate: 0, + no_bitstream_error: 0, + saw_seqgoppic: 0, + in_pic_data: 0, + current_progressive_sequence: 0, + current_pulldownfields: 0, + temporal_reference: 0, + picture_coding_type: crate::common::FrameType::ResetOrUnknown, + num_key_frames: 0, + picture_structure: 0, + repeat_first_field: 0, + progressive_frame: 0, + pulldownfields: 0, + top_field_first: 0, + stat_numuserheaders: 0, + stat_dvdccheaders: 0, + stat_scte20ccheaders: 0, + stat_replay5000headers: 0, + stat_replay4000headers: 0, + stat_dishheaders: 0, + stat_hdtv: 0, + stat_divicom: 0, + false_pict_header: 0, + current_field: 0, + maxtref: 0, + cc_data_count: [0; SORTBUF], + cc_fts: [0; SORTBUF], + cc_data_pkts: [[0; 10 * 31 * 3 + 1]; SORTBUF], + anchor_seq_number: 0, + xds_ctx: std::ptr::null_mut(), + ocr_quantmode: 0, + prev: std::ptr::null_mut(), + } + } +} + +// HList (Hyperlinked List) +#[derive(Debug)] +pub struct HList { + // A lot of the HList struct is not implemented yet + pub next: *mut HList, + pub prev: *mut HList, +} +impl Default for HList { + fn default() -> Self { + HList { + next: null_mut(), + prev: null_mut(), + } + } +} + +pub struct AvcCtx { + pub cc_count: u8, // Number of closed caption blocks + pub cc_data: *mut u8, // Pointer to buffer holding CC data + pub cc_databufsize: i64, // Buffer size for CC data + pub cc_buffer_saved: i32, // Was the CC buffer saved after the last update? + + pub got_seq_para: i32, // Flag indicating if sequence parameters were received + pub nal_ref_idc: u32, // NAL reference ID + pub seq_parameter_set_id: i64, // Sequence parameter set ID + pub log2_max_frame_num: i32, // Log2 of max frame number + pub pic_order_cnt_type: i32, // Picture order count type + pub log2_max_pic_order_cnt_lsb: i32, // Log2 of max picture order count LSB + pub frame_mbs_only_flag: i32, // Flag indicating if only frame MBs are used + + // Use and throw stats for debugging (TODO: clean up later) + pub num_nal_unit_type_7: i64, // Number of NAL units of type 7 + pub num_vcl_hrd: i64, // Number of VCL HRD parameters encountered + pub num_nal_hrd: i64, // Number of NAL HRD parameters encountered + pub num_jump_in_frames: i64, // Number of frame jumps detected + pub num_unexpected_sei_length: i64, // Number of unexpected SEI lengths + + pub ccblocks_in_avc_total: i32, // Total CC blocks in AVC stream + pub ccblocks_in_avc_lost: i32, // Lost CC blocks in AVC stream + + pub frame_num: i64, // Current frame number + pub lastframe_num: i64, // Last processed frame number + pub currref: i32, // Current reference index + pub maxidx: i32, // Maximum index value for ordering + pub lastmaxidx: i32, // Last max index + + // Used to find tref zero in PTS mode + pub minidx: i32, // Minimum reference index + pub lastminidx: i32, // Last minimum reference index + + // Used to remember the max temporal reference number (POC mode) + pub maxtref: i32, // Max temporal reference + pub last_gop_maxtref: i32, // Last GOP max temporal reference + + // Used for PTS ordering of CC blocks + pub currefpts: i64, // Current reference PTS + pub last_pic_order_cnt_lsb: i64, // Last picture order count LSB + pub last_slice_pts: i64, // Last slice PTS +} + +pub struct XdsContext { + // Program Identification Number (Start Time) for current program + pub current_xds_min: i32, + pub current_xds_hour: i32, + pub current_xds_date: i32, + pub current_xds_month: i32, + pub current_program_type_reported: i32, // No. + pub xds_start_time_shown: i32, + pub xds_program_length_shown: i32, + pub xds_program_description: [[char; 33]; 8], // Program descriptions (8 entries of 33 characters each) + + pub current_xds_network_name: [char; 33], // Network name + pub current_xds_program_name: [char; 33], // Program name + pub current_xds_call_letters: [char; 7], // Call letters + pub current_xds_program_type: [char; 33], // Program type + + pub xds_buffers: [XdsBuffer; NUM_XDS_BUFFERS as usize], // Array of XDS buffers + pub cur_xds_buffer_idx: i32, // Current XDS buffer index + pub cur_xds_packet_class: i32, // Current XDS packet class + pub cur_xds_payload: *mut u8, // Pointer to the current XDS payload + pub cur_xds_payload_length: i32, // Length of the current XDS payload + pub cur_xds_packet_type: i32, // Current XDS packet type + pub timing: *mut crate::time::TimingContext, // Pointer to timing context + + pub current_ar_start: u32, // Current AR start time + pub current_ar_end: u32, // Current AR end time + + pub xds_write_to_file: i32, // Set to 1 if XDS data is to be written to a file +} diff --git a/src/rust/lib_ccxr/src/decoder_vbi/structs_isdb.rs b/src/rust/lib_ccxr/src/decoder_vbi/structs_isdb.rs new file mode 100644 index 000000000..330cae76e --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/structs_isdb.rs @@ -0,0 +1,796 @@ +use crate::decoder_vbi::exit_codes::*; +use crate::decoder_vbi::structs_xds::*; + +use crate::common::{Codec, OutputFormat, StreamMode}; +use crate::time::*; + +use libc::FILE; +use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_ulonglong, c_void}; + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ListHead { + pub next: *mut ListHead, + pub prev: *mut ListHead, +} + +#[repr(C)] +pub struct DtvccCtx { + pub is_active: c_int, + pub active_services_count: c_int, + pub services_active: [c_int; CCX_DTVCC_MAX_SERVICES], // 0 - inactive, 1 - active + pub report_enabled: c_int, + pub report: *mut CcxDecoderDtvccReport, + pub decoders: [DtvccServiceDecoder; CCX_DTVCC_MAX_SERVICES], + pub current_packet: [c_uchar; CCX_DTVCC_MAX_PACKET_LENGTH], + pub current_packet_length: c_int, + pub is_current_packet_header_parsed: c_int, + pub last_sequence: c_int, + pub encoder: *mut c_void, // we can't include header, so keeping it this way + pub no_rollup: c_int, + pub timing: *mut TimingContext, +} + +#[repr(C)] +pub struct CcxDecoderDtvccReport { + pub reset_count: c_int, + pub services: [c_uint; CCX_DTVCC_MAX_SERVICES], +} + +#[repr(C)] +pub struct DtvccServiceDecoder { + pub windows: [DtvccWindow; CCX_DTVCC_MAX_WINDOWS], + pub current_window: c_int, + pub tv: *mut DtvccTvScreen, + pub cc_count: c_int, +} + +#[repr(C)] +pub struct DtvccWindow { + pub is_defined: c_int, + pub number: c_int, + pub priority: c_int, + pub col_lock: c_int, + pub row_lock: c_int, + pub visible: c_int, + pub anchor_vertical: c_int, + pub relative_pos: c_int, + pub anchor_horizontal: c_int, + pub row_count: c_int, + pub anchor_point: c_int, + pub col_count: c_int, + pub pen_style: c_int, + pub win_style: c_int, + pub commands: [c_uchar; 6], // Commands used to create this window + pub attribs: DtvccWindowAttribs, + pub pen_row: c_int, + pub pen_column: c_int, + pub rows: [*mut DtvccSymbol; CCX_DTVCC_MAX_ROWS], + pub pen_colors: [[DtvccPenColor; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_MAX_ROWS], + pub pen_attribs: [[DtvccPenAttribs; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_MAX_ROWS], + pub pen_color_pattern: DtvccPenColor, + pub pen_attribs_pattern: DtvccPenAttribs, + pub memory_reserved: c_int, + pub is_empty: c_int, + pub time_ms_show: i64, // Assuming LLONG is equivalent to i64 + pub time_ms_hide: i64, +} + +#[repr(C)] +pub struct DtvccWindowAttribs { + pub justify: c_int, + pub print_direction: c_int, + pub scroll_direction: c_int, + pub word_wrap: c_int, + pub display_effect: c_int, + pub effect_direction: c_int, + pub effect_speed: c_int, + pub fill_color: c_int, + pub fill_opacity: c_int, + pub border_type: c_int, + pub border_color: c_int, +} + +#[repr(C)] +pub struct DtvccSymbol { + pub sym: u16, // symbol itself, at least 16 bit + pub init: c_uchar, // initialized or not. could be 0 or 1 +} + +#[repr(C)] +pub struct DtvccPenColor { + pub fg_color: c_int, + pub fg_opacity: c_int, + pub bg_color: c_int, + pub bg_opacity: c_int, + pub edge_color: c_int, +} + +#[repr(C)] +pub struct DtvccPenAttribs { + pub pen_size: c_int, + pub offset: c_int, + pub text_tag: c_int, + pub font_tag: c_int, + pub edge_type: c_int, + pub underline: c_int, + pub italic: c_int, +} + +#[repr(C)] +pub struct DtvccTvScreen { + pub chars: [[DtvccSymbol; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_SCREENGRID_ROWS], + pub pen_colors: [[DtvccPenColor; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_SCREENGRID_ROWS], + pub pen_attribs: [[DtvccPenAttribs; CCX_DTVCC_SCREENGRID_COLUMNS]; CCX_DTVCC_SCREENGRID_ROWS], + pub time_ms_show: i64, + pub time_ms_hide: i64, + pub cc_count: c_uint, + pub service_number: c_int, + pub old_cc_time_end: c_int, +} + +#[repr(C)] +pub struct CcxDecoderVbiCtx { + pub vbi_decoder_inited: c_int, + pub zvbi_decoder: VbiRawDecoder, + // vbi3_raw_decoder zvbi_decoder; + // #ifdef VBI_DEBUG + pub vbi_debug_dump: *mut FILE, + // #endif +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct VbiRawDecoder { + /* Sampling parameters */ + /** + * Either 525 (M/NTSC, M/PAL) or 625 (PAL, SECAM), describing the + * scan line system all line numbers refer to. + */ + pub scanning: c_int, + /** + * Format of the raw vbi data. + */ + pub sampling_format: VbiPixfmt, + /** + * Sampling rate in Hz, the number of samples or pixels + * captured per second. + */ + pub sampling_rate: c_int, // Hz + /** + * Number of samples or pixels captured per scan line, + * in bytes. This determines the raw vbi image width and you + * want it large enough to cover all data transmitted in the line (with + * headroom). + */ + pub bytes_per_line: c_int, + /** + * The distance from 0H (leading edge hsync, half amplitude point) + * to the first sample (pixel) captured, in samples (pixels). You want + * an offset small enough not to miss the start of the data + * transmitted. + */ + pub offset: c_int, // 0H, samples + /** + * First scan line to be captured, first and second field + * respectively, according to the ITU-R line numbering scheme + * (see vbi_sliced). Set to zero if the exact line number isn't + * known. + */ + pub start: [c_int; 2], // ITU-R numbering + /** + * Number of scan lines captured, first and second + * field respectively. This can be zero if only data from one + * field is required. The sum @a count[0] + @a count[1] determines the + * raw vbi image height. + */ + pub count: [c_int; 2], // field lines + /** + * In the raw vbi image, normally all lines of the second + * field are supposed to follow all lines of the first field. When + * this flag is set, the scan lines of first and second field + * will be interleaved in memory. This implies @a count[0] and @a count[1] + * are equal. + */ + pub interlaced: c_int, + /** + * Fields must be stored in temporal order, i. e. as the + * lines have been captured. It is assumed that the first field is + * also stored first in memory, however if the hardware cannot reliable + * distinguish fields this flag shall be cleared, which disables + * decoding of data services depending on the field number. + */ + pub synchronous: c_int, + + pub services: c_uint, + pub num_jobs: c_int, + + pub pattern: *mut i8, + pub jobs: [VbiRawDecoderJob; 8], +} + +#[derive(Debug, Clone, Copy, Default)] +#[repr(C)] +pub struct VbiRawDecoderJob { + pub id: c_uint, + pub offset: c_int, + pub slicer: VbiBitSlicer, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub enum VbiPixfmt { + Yuv420 = 1, + Yuyv, + Yvyu, + Uyvy, + Vyuy, + Pal8, + Rgba32Le = 32, + Rgba32Be, + Bgra32Le, + Bgra32Be, + Abgr32Be, /* = 32, // synonyms */ + Abgr32Le, + Argb32Be, + Argb32Le, + Rgb24, + Bgr24, + Rgb16Le, + Rgb16Be, + Bgr16Le, + Bgr16Be, + Rgba15Le, + Rgba15Be, + Bgra15Le, + Bgra15Be, + Argb15Le, + Argb15Be, + Abgr15Le, + Abgr15Be, +} + +#[derive(Debug, Clone, Copy, Default)] +#[repr(C)] +pub struct VbiBitSlicer { + pub func: Option< + extern "C" fn(slicer: *mut VbiBitSlicer, raw: *mut c_uchar, buf: *mut c_uchar) -> c_int, + >, + pub cri: c_uint, + pub cri_mask: c_uint, + pub thresh: c_int, + pub cri_bytes: c_int, + pub cri_rate: c_int, + pub oversampling_rate: c_int, + pub phase_shift: c_int, + pub step: c_int, + pub frc: c_uint, + pub frc_bits: c_int, + pub payload: c_int, + pub endian: c_int, + pub skip: c_int, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ISDBSubContext { + pub nb_char: c_int, + pub nb_line: c_int, + pub timestamp: u64, + pub prev_timestamp: u64, + pub text_list_head: ListHead, + pub buffered_text: ListHead, + pub current_state: ISDBSubState, + pub tmd: IsdbTmd, + pub nb_lang: c_int, + pub offset_time: OffsetTime, + pub dmf: c_uchar, + pub dc: c_uchar, + pub cfg_no_rollup: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct ISDBSubState { + pub auto_display: c_int, // bool + pub rollup_mode: c_int, // bool + pub need_init: c_uchar, // bool + pub clut_high_idx: c_uchar, + pub fg_color: c_uint, + pub bg_color: c_uint, + pub hfg_color: c_uint, + pub hbg_color: c_uint, + pub mat_color: c_uint, + pub raster_color: c_uint, + pub layout_state: ISDBSubLayout, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct ISDBSubLayout { + pub format: WritingFormat, + pub display_area: DispArea, + pub font_size: c_int, + pub font_scale: FScale, + pub cell_spacing: Spacing, + pub cursor_pos: ISDBPos, + pub ccc: IsdbCCComposition, + pub acps: [c_int; 2], +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct DispArea { + pub x: c_int, + pub y: c_int, + pub w: c_int, + pub h: c_int, +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct FScale { + pub fscx: c_int, + pub fscy: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct Spacing { + pub col: c_int, + pub row: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct ISDBPos { + pub x: c_int, + pub y: c_int, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub struct OffsetTime { + pub hour: c_int, + pub min: c_int, + pub sec: c_int, + pub milli: c_int, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum WritingFormat { + #[default] + HorizontalStdDensity = 0, + VerticalStdDensity = 1, + HorizontalHighDensity = 2, + VerticalHighDensity = 3, + HorizontalWesternLang = 4, + Horizontal1920x1080 = 5, + Vertical1920x1080 = 6, + Horizontal960x540 = 7, + Vertical960x540 = 8, + Horizontal720x480 = 9, + Vertical720x480 = 10, + Horizontal1280x720 = 11, + Vertical1280x720 = 12, + HorizontalCustom = 100, + None, +} + +#[derive(Debug, Clone, Default)] +#[repr(C)] +pub enum IsdbCCComposition { + #[default] + None = 0, + And = 2, + Or = 3, + Xor = 4, +} + +use std::convert::TryFrom; + +impl TryFrom for IsdbCCComposition { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(IsdbCCComposition::None), + 2 => Ok(IsdbCCComposition::And), + 3 => Ok(IsdbCCComposition::Or), + 4 => Ok(IsdbCCComposition::Xor), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +#[repr(C)] +pub enum IsdbTmd { + #[default] + Free = 0, + RealTime = 0x1, + OffsetTime = 0x2, +} + +#[repr(C)] +pub enum FontSize { + SmallFontSize, + MiddleFontSize, + StandardFontSize, +} + +#[repr(C)] +pub enum CsiCommand { + Gsm = 0x42, + Swf = 0x53, + Ccc = 0x54, + Sdf = 0x56, + Ssm = 0x57, + Shs = 0x58, + Svs = 0x59, + Pld = 0x5B, + Plu = 0x5C, + Gaa = 0x5D, + Src = 0x5E, + Sdp = 0x5F, + Acps = 0x61, + Tcc = 0x62, + Orn = 0x63, + Mdf = 0x64, + Cfs = 0x65, + Xcs = 0x66, + Pra = 0x68, + Acs = 0x69, + Rcs = 0x6E, + Scs = 0x6F, +} + +#[repr(C)] +pub enum Color { + CcxIsdbBlack, + FiRed, + FiGreen, + FiYellow, + FiBlue, + FiMagenta, + FiCyan, + FiWhite, + CcxIsdbTransparent, + HiRed, + HiGreen, + HiYellow, + HiBlue, + HiMagenta, + HiCyan, + HiWhite, +} + +#[no_mangle] +pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> u32 { + ((255 - a as u32) << 24) | ((b as u32) << 16) | ((g as u32) << 8) | (r as u32) +} + +type Rgba = u32; +pub const ZATA: u32 = 0; +pub const DEFAULT_CLUT: [Rgba; 128] = [ + // 0-7 + rgba(0, 0, 0, 255), + rgba(255, 0, 0, 255), + rgba(0, 255, 0, 255), + rgba(255, 255, 0, 255), + rgba(0, 0, 255, 255), + rgba(255, 0, 255, 255), + rgba(0, 255, 255, 255), + rgba(255, 255, 255, 255), + // 8-15 + rgba(0, 0, 0, 0), + rgba(170, 0, 0, 255), + rgba(0, 170, 0, 255), + rgba(170, 170, 0, 255), + rgba(0, 0, 170, 255), + rgba(170, 0, 170, 255), + rgba(0, 170, 170, 255), + rgba(170, 170, 170, 255), + // 16-23 + rgba(0, 0, 85, 255), + rgba(0, 85, 0, 255), + rgba(0, 85, 85, 255), + rgba(0, 85, 170, 255), + rgba(0, 85, 255, 255), + rgba(0, 170, 85, 255), + rgba(0, 170, 255, 255), + rgba(0, 255, 85, 255), + // 24-31 + rgba(0, 255, 170, 255), + rgba(85, 0, 0, 255), + rgba(85, 0, 85, 255), + rgba(85, 0, 170, 255), + rgba(85, 0, 255, 255), + rgba(85, 85, 0, 255), + rgba(85, 85, 85, 255), + rgba(85, 85, 170, 255), + // 32-39 + rgba(85, 85, 255, 255), + rgba(85, 170, 0, 255), + rgba(85, 170, 85, 255), + rgba(85, 170, 170, 255), + rgba(85, 170, 255, 255), + rgba(85, 255, 0, 255), + rgba(85, 255, 85, 255), + rgba(85, 255, 170, 255), + // 40-47 + rgba(85, 255, 255, 255), + rgba(170, 0, 85, 255), + rgba(170, 0, 255, 255), + rgba(170, 85, 0, 255), + rgba(170, 85, 85, 255), + rgba(170, 85, 170, 255), + rgba(170, 85, 255, 255), + rgba(170, 170, 85, 255), + // 48-55 + rgba(170, 170, 255, 255), + rgba(170, 255, 0, 255), + rgba(170, 255, 85, 255), + rgba(170, 255, 170, 255), + rgba(170, 255, 255, 255), + rgba(255, 0, 85, 255), + rgba(255, 0, 170, 255), + rgba(255, 85, 0, 255), + // 56-63 + rgba(255, 85, 85, 255), + rgba(255, 85, 170, 255), + rgba(255, 85, 255, 255), + rgba(255, 170, 0, 255), + rgba(255, 170, 85, 255), + rgba(255, 170, 170, 255), + rgba(255, 170, 255, 255), + rgba(255, 255, 85, 255), + // 64 + rgba(255, 255, 170, 255), + // 65-127 are calculated later. + // Initialize remaining elements to 0 + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, + ZATA, +]; + +#[repr(C)] +pub struct ISDBText { + pub buf: *mut c_char, + pub len: usize, + pub used: usize, + pub pos: ISDBPos, + pub txt_tail: usize, // tail of the text, excluding trailing control sequences. + pub timestamp: c_ulonglong, // Time Stamp when first string is received + pub refcount: c_int, + pub list: ListHead, +} + +pub struct CcxEncodersTranscriptFormat { + pub show_start_time: i32, // Show start and/or end time. + pub show_end_time: i32, // Show start and/or end time. + pub show_mode: i32, // Show which mode if available (E.G.: POP, RU1, ...) + pub show_cc: i32, // Show which CC channel has been captured. + pub relative_timestamp: i32, // Timestamps relative to start of sample or in UTC? + pub xds: i32, // Show XDS or not + pub use_colors: i32, // Add colors or no colors + pub is_final: i32, // Used to determine if these parameters should be changed afterwards. +} + +#[repr(i32)] +pub enum CcxDataSource { + File = 0, + Stdin = 1, + Network = 2, + Tcp = 3, +} + +#[repr(C)] +pub struct CcxDecoder608Settings { + pub direct_rollup: i32, + pub force_rollup: i32, + pub no_rollup: i32, + pub default_color: CcxDecoder608ColorCode, + pub screens_to_process: i32, + pub report: *mut CcxDecoder608Report, +} + +#[repr(C)] +pub struct CcxDecoder608Report { + pub xds: u8, + pub cc_channels: [u8; 4], +} + +#[repr(C)] +pub struct CcxDecoderDtvccSettings { + pub enabled: i32, + pub print_file_reports: i32, + pub no_rollup: i32, + pub report: *mut CcxDecoderDtvccReport, + pub active_services_count: i32, + pub services_enabled: [i32; CCX_DTVCC_MAX_SERVICES], + pub timing: *mut TimingContext, +} + +#[repr(C)] +pub struct DemuxerCfg { + pub m2ts: i32, + pub auto_stream: StreamMode, + pub codec: Codec, + pub nocodec: Codec, + pub ts_autoprogram: u32, + pub ts_allprogram: u32, + pub ts_cappids: [u32; 128], + pub nb_ts_cappid: i32, + pub ts_forced_cappid: u32, + pub ts_forced_program: i32, + pub ts_forced_program_selected: u32, + pub ts_datastreamtype: i32, + pub ts_forced_streamtype: u32, +} + +#[repr(C)] +pub struct EncoderCfg { + pub extract: i32, + pub dtvcc_extract: i32, + pub gui_mode_reports: i32, + pub output_filename: *mut c_char, + pub write_format: OutputFormat, + pub keep_output_closed: i32, + pub force_flush: i32, + pub append_mode: i32, + pub ucla: i32, + pub encoding: CcxEncodingType, + pub date_format: TimestampFormat, + pub millis_separator: c_char, + pub autodash: i32, + pub trim_subs: i32, + pub sentence_cap: i32, + pub splitbysentence: i32, + pub filter_profanity: i32, + pub with_semaphore: i32, + pub start_credits_text: *mut c_char, + pub end_credits_text: *mut c_char, + pub startcreditsnotbefore: Timestamp, + pub startcreditsnotafter: Timestamp, + pub startcreditsforatleast: Timestamp, + pub startcreditsforatmost: Timestamp, + pub endcreditsforatleast: Timestamp, + pub endcreditsforatmost: Timestamp, + pub transcript_settings: CcxEncodersTranscriptFormat, + pub send_to_srv: u32, + pub no_bom: i32, + pub first_input_file: *mut c_char, + pub multiple_files: i32, + pub no_font_color: i32, + pub no_type_setting: i32, + pub cc_to_stdout: i32, + pub line_terminator_lf: i32, + pub subs_delay: i64, + pub program_number: i32, + pub in_format: u8, + pub nospupngocr: i32, + pub force_dropframe: i32, + pub render_font: *mut c_char, + pub render_font_italics: *mut c_char, + pub services_enabled: [i32; CCX_DTVCC_MAX_SERVICES], + pub services_charsets: *mut *mut c_char, + pub all_services_charset: *mut c_char, + pub extract_only_708: i32, +} + +impl Default for ISDBSubLayout { + fn default() -> Self { + Self { + format: WritingFormat::None, + display_area: Default::default(), + font_size: 0, + font_scale: Default::default(), + cell_spacing: Default::default(), + cursor_pos: Default::default(), + ccc: IsdbCCComposition::None, + acps: [0; 2], + } + } +} + +impl Default for FScale { + fn default() -> Self { + Self { + fscx: 100, + fscy: 100, + } + } +} + +impl Default for ISDBSubContext { + fn default() -> Self { + Self { + nb_char: 0, + nb_line: 0, + timestamp: 0, + prev_timestamp: 0, + text_list_head: Default::default(), + buffered_text: Default::default(), + current_state: Default::default(), + tmd: IsdbTmd::Free, + nb_lang: 0, + offset_time: Default::default(), + dmf: 0, + dc: 0, + cfg_no_rollup: 0, + } + } +} + +impl Default for ListHead { + fn default() -> Self { + Self { + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + } + } +} + +//----------------------isdb_structs-end--------------------------- diff --git a/src/rust/lib_ccxr/src/decoder_vbi/structs_vbi_decode.rs b/src/rust/lib_ccxr/src/decoder_vbi/structs_vbi_decode.rs new file mode 100644 index 000000000..00ab5db13 --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/structs_vbi_decode.rs @@ -0,0 +1,153 @@ +use std::os::raw::{c_char, c_int, c_uchar, c_void}; + +use crate::decoder_vbi::structs_isdb::*; + +pub struct CcxDecoderVbiCfg { + // #ifdef VBI_DEBUG + pub debug_file_name: *mut c_char, // Pointer to a debug file name + // #endif +} + +pub type VbiServiceSet = u32; + +#[derive(Debug, Copy, Clone)] +pub struct VbiLogHook { + pub fn_ptr: Option, + pub user_data: *mut c_void, + pub mask: VbiLogMask, +} + +pub type VbiLogFn = unsafe extern "C" fn( + level: VbiLogMask, + context: *const c_uchar, + message: *const c_uchar, + user_data: *mut c_void, +); + +impl Default for VbiLogHook { + fn default() -> Self { + Self { + fn_ptr: None, + user_data: std::ptr::null_mut(), + mask: 0, + } + } +} + +pub type VbiLogMask = u32; + +pub type VbiBool = c_int; + +pub type VbiSamplingPar = VbiRawDecoder; + +impl Default for VbiRawDecoder { + fn default() -> Self { + Self { + scanning: 0, + sampling_format: VbiPixfmt::Yuv420, + sampling_rate: 0, + bytes_per_line: 0, + offset: 0, + start: [0; 2], + count: [0; 2], + interlaced: 0, + synchronous: 0, + services: 0, + num_jobs: 0, + pattern: std::ptr::null_mut(), + jobs: [VbiRawDecoderJob::default(); 8], + } + } +} + +type VbiVideostdSet = u64; + +pub struct VbiServicePar { + pub id: VbiServiceSet, + pub label: *const c_char, + + /// Video standard + /// - 525 lines, FV = 59.94 Hz, FH = 15734 Hz + /// - 625 lines, FV = 50 Hz, FH = 15625 Hz + pub videostd_set: VbiVideostdSet, + + /// Most scan lines used by the data service, first and last + /// line of first and second field. ITU-R numbering scheme. + /// Zero if no data from this field, requires field sync. + pub first: [u32; 2], + pub last: [u32; 2], + + /// Leading edge hsync to leading edge first CRI one bit, + /// half amplitude points, in nanoseconds. + pub offset: u32, + + pub cri_rate: u32, // Hz + pub bit_rate: u32, // Hz + + /// Clock Run In and FRaming Code, LSB last transmitted bit of FRC. + pub cri_frc: u32, + + /// CRI and FRC bits significant for identification. + pub cri_frc_mask: u32, + + /// Number of significant cri_bits (at cri_rate), + /// frc_bits (at bit_rate). + pub cri_bits: u32, + pub frc_bits: u32, + + pub payload: u32, // bits + pub modulation: VbiModulation, + + pub flags: VbiServiceParFlag, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum VbiModulation { + /// The data is 'non-return to zero' coded, logical '1' bits + /// are described by high sample values, logical '0' bits by + /// low values. The data is last significant bit first transmitted. + NrzLsb = 0, + + /// 'Non-return to zero' coded, most significant bit first + /// transmitted. + NrzMsb = 1, + + /// The data is 'bi-phase' coded. Each data bit is described + /// by two complementary signaling elements, a logical '1' + /// by a sequence of '10' elements, a logical '0' by a '01' + /// sequence. The data is last significant bit first transmitted. + BiphaseLsb = 2, + + /// 'Bi-phase' coded, most significant bit first transmitted. + BiphaseMsb = 3, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub enum VbiServiceParFlag { + LineNum = 1 << 0, + FieldNum = 1 << 1, + LineAndField = (1 << 0) | (1 << 1), // Bitwise OR of LineNum and FieldNum +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct VbiSliced { + /// A VBI_SLICED_ symbol identifying the data service. + /// Under certain circumstances (see VBI_SLICED_TELETEXT_B) + /// this can be a set of VBI_SLICED_ symbols. + pub id: u32, + + /// Source line number according to the ITU-R line numbering scheme, + /// a value of 0 if the exact line number is unknown. Note that some + /// data services cannot be reliably decoded without line number. + /// + /// ITU-R PAL/SECAM line numbering scheme and ITU-R NTSC line numbering + /// scheme are illustrated in the original documentation. + pub line: u32, + + /// The actual payload. See the documentation of VBI_SLICED_ symbols + /// for details. + pub data: [u8; 56], +} diff --git a/src/rust/lib_ccxr/src/decoder_vbi/structs_vbi_slice.rs b/src/rust/lib_ccxr/src/decoder_vbi/structs_vbi_slice.rs new file mode 100644 index 000000000..1486fae6f --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/structs_vbi_slice.rs @@ -0,0 +1,127 @@ +use crate::decoder_vbi::exit_codes::*; +use crate::decoder_vbi::structs_isdb::*; +use crate::decoder_vbi::structs_vbi_decode::*; + +use std::os::raw::{c_int, c_uchar}; +use std::ptr::null_mut; + +pub struct Vbi3RawDecoder { + pub sampling: VbiSamplingPar, + pub services: VbiServiceSet, + pub log: VbiLogHook, + pub debug: VbiBool, + pub n_jobs: u32, + pub n_sp_lines: u32, + pub readjust: c_int, + pub pattern: *mut i8, + pub jobs: [_Vbi3RawDecoderJob; _VBI3_RAW_DECODER_MAX_JOBS], + pub sp_lines: *mut _Vbi3RawDecoderSpLine, +} + +impl Default for Vbi3RawDecoder { + fn default() -> Self { + Self { + sampling: VbiRawDecoder::default(), + services: 0, + log: VbiLogHook::default(), + debug: 0, + n_jobs: 0, + n_sp_lines: 0, + readjust: 0, + pattern: std::ptr::null_mut(), + jobs: [_Vbi3RawDecoderJob::default(); _VBI3_RAW_DECODER_MAX_JOBS], + sp_lines: std::ptr::null_mut(), + } + } +} + +#[derive(Debug, Copy, Clone, Default)] +pub struct _Vbi3RawDecoderJob { + pub id: VbiServiceSet, + pub slicer: Vbi3BitSlicer, +} + +#[derive(Debug, Clone)] +pub struct _Vbi3RawDecoderSpLine { + pub points: [Vbi3BitSlicerPoint; 512], + pub n_points: u32, +} + +impl Default for Vbi3BitSlicer { + fn default() -> Self { + Self { + func: None, + sample_format: VbiPixfmt::Yuv420, + cri: 0, + cri_mask: 0, + thresh: 0, + thresh_frac: 0, + cri_samples: 0, + cri_rate: 0, + oversampling_rate: 0, + phase_shift: 0, + step: 0, + frc: 0, + frc_bits: 0, + total_bits: 0, + payload: 0, + endian: 0, + bytes_per_sample: 0, + skip: 0, + green_mask: 0, + log: VbiLogHook { + fn_ptr: None, + user_data: null_mut(), + mask: 0, + }, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct Vbi3BitSlicer { + pub func: Option, + pub sample_format: VbiPixfmt, + pub cri: u32, + pub cri_mask: u32, + pub thresh: u32, + pub thresh_frac: u32, + pub cri_samples: u32, + pub cri_rate: u32, + pub oversampling_rate: u32, + pub phase_shift: u32, + pub step: u32, + pub frc: u32, + pub frc_bits: u32, + pub total_bits: u32, + pub payload: u32, + pub endian: u32, + pub bytes_per_sample: u32, + pub skip: u32, + pub green_mask: u32, + pub log: VbiLogHook, +} + +pub type Vbi3BitSlicerFn = unsafe extern "C" fn( + bs: *mut Vbi3BitSlicer, + buffer: *mut c_uchar, + points: *mut Vbi3BitSlicerPoint, + n_points: *mut u32, + raw: *const c_uchar, +) -> VbiBool; + +#[derive(Debug, Clone, Default)] +pub struct Vbi3BitSlicerPoint { + pub kind: Vbi3BitSlicerBit, + pub index: u32, + pub level: u32, + pub thresh: u32, +} + +#[derive(Debug, Clone, Default)] +pub enum Vbi3BitSlicerBit { + #[default] + CriBit = 1, + FrcBit, + PayloadBit, +} diff --git a/src/rust/lib_ccxr/src/decoder_vbi/structs_xds.rs b/src/rust/lib_ccxr/src/decoder_vbi/structs_xds.rs new file mode 100644 index 000000000..99b589fae --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/structs_xds.rs @@ -0,0 +1,391 @@ +use crate::decoder_vbi::exit_codes::*; +use crate::time::*; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxEia608Format { + SformatCcScreen, + SformatCcLine, + SformatXds, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcxDecoder608ColorCode { + ColWhite = 0, + ColGreen = 1, + ColBlue = 2, + ColCyan = 3, + ColRed = 4, + ColYellow = 5, + ColMagenta = 6, + ColUserDefined = 7, + ColBlack = 8, + ColTransparent = 9, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FontBits { + FontRegular = 0, + FontItalics = 1, + FontUnderlined = 2, + FontUnderlinedItalics = 3, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CcModes { + ModePopOn = 0, + ModeRollUp2 = 1, + ModeRollUp3 = 2, + ModeRollUp4 = 3, + ModeText = 4, + ModePaintOn = 5, + ModeFakeRollUp1 = 100, // Fake modes to emulate stuff +} + +// The `Eia608Screen` structure +#[derive(Debug)] +pub struct Eia608Screen { + pub format: CcxEia608Format, // Format of data inside this structure + pub characters: [[u8; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Characters + pub colors: + [[CcxDecoder608ColorCode; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Colors + pub fonts: [[FontBits; CCX_DECODER_608_SCREEN_WIDTH + 1]; CCX_DECODER_608_SCREEN_ROWS], // Fonts + pub row_used: [i64; CCX_DECODER_608_SCREEN_ROWS], // Any data in row? + pub empty: i64, // Buffer completely empty? + pub start_time: i64, // Start time of this CC buffer + pub end_time: i64, // End time of this CC buffer + pub mode: CcModes, // Mode + pub channel: i64, // Currently selected channel + pub my_field: i64, // Used for sanity checks + pub xds_str: *const u8, // Pointer to XDS string + pub xds_len: usize, // Length of XDS string + pub cur_xds_packet_class: i64, // Class of XDS string +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubDataType { + #[default] + Generic = 0, + Dvb = 1, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum SubType { + #[default] + Bitmap, + Cc608, + Text, + Raw, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +pub enum CcxEncodingType { + #[default] + Unicode = 0, + Latin1 = 1, + Utf8 = 2, + Ascii = 3, +} + +#[derive(Debug)] +pub struct CcSubtitle { + /** + * A generic data which contains data according to the decoder. + * @warn Decoder can't output multiple types of data. + */ + pub data: *mut std::ffi::c_void, // Pointer to generic data + pub datatype: SubDataType, // Data type (e.g., Generic, DVB) + + /** Number of data */ + pub nb_data: u32, + + /** Type of subtitle */ + pub subtype: SubType, + + /** Encoding type of text, ignored for bitmap or cc_screen subtypes */ + pub enc_type: CcxEncodingType, + + /* Set only when all the data is to be displayed at the same time. + * Unit of time is milliseconds. + */ + pub start_time: i64, + pub end_time: i64, + + /* Flags */ + pub flags: i32, + + /* Index of language table */ + pub lang_index: i32, + + /** Flag to tell that the decoder has given output */ + pub got_output: bool, + + pub mode: [u8; 5], // Mode as a fixed-size array of 5 bytes + pub info: [u8; 4], // Info as a fixed-size array of 4 bytes + + /** Used for DVB end time in milliseconds */ + pub time_out: i32, + + pub next: *mut CcSubtitle, // Pointer to the next subtitle + pub prev: *mut CcSubtitle, // Pointer to the previous subtitle +} + +// XDS classes +pub const XDS_CLASSES: [&str; 8] = [ + "Current", + "Future", + "Channel", + "Miscellaneous", + "Public service", + "Reserved", + "Private data", + "End", +]; + +// XDS program types +pub const XDS_PROGRAM_TYPES: [&str; 96] = [ + "Education", + "Entertainment", + "Movie", + "News", + "Religious", + "Sports", + "Other", + "Action", + "Advertisement", + "Animated", + "Anthology", + "Automobile", + "Awards", + "Baseball", + "Basketball", + "Bulletin", + "Business", + "Classical", + "College", + "Combat", + "Comedy", + "Commentary", + "Concert", + "Consumer", + "Contemporary", + "Crime", + "Dance", + "Documentary", + "Drama", + "Elementary", + "Erotica", + "Exercise", + "Fantasy", + "Farm", + "Fashion", + "Fiction", + "Food", + "Football", + "Foreign", + "Fund-Raiser", + "Game/Quiz", + "Garden", + "Golf", + "Government", + "Health", + "High_School", + "History", + "Hobby", + "Hockey", + "Home", + "Horror", + "Information", + "Instruction", + "International", + "Interview", + "Language", + "Legal", + "Live", + "Local", + "Math", + "Medical", + "Meeting", + "Military", + "Mini-Series", + "Music", + "Mystery", + "National", + "Nature", + "Police", + "Politics", + "Premiere", + "Pre-Recorded", + "Product", + "Professional", + "Public", + "Racing", + "Reading", + "Repair", + "Repeat", + "Review", + "Romance", + "Science", + "Series", + "Service", + "Shopping", + "Soap_Opera", + "Special", + "Suspense", + "Talk", + "Technical", + "Tennis", + "Travel", + "Variety", + "Video", + "Weather", + "Western", +]; + +// XDS class constants +pub const XDS_CLASS_CURRENT: u8 = 0; +pub const XDS_CLASS_FUTURE: u8 = 1; +pub const XDS_CLASS_CHANNEL: u8 = 2; +pub const XDS_CLASS_MISC: u8 = 3; +pub const XDS_CLASS_PUBLIC: u8 = 4; +pub const XDS_CLASS_RESERVED: u8 = 5; +pub const XDS_CLASS_PRIVATE: u8 = 6; +pub const XDS_CLASS_END: u8 = 7; +pub const XDS_CLASS_OUT_OF_BAND: u8 = 0x40; // Not a real class, a marker for packets for out-of-band data + +// Types for the classes current and future +pub const XDS_TYPE_PIN_START_TIME: u8 = 1; +pub const XDS_TYPE_LENGTH_AND_CURRENT_TIME: u8 = 2; +pub const XDS_TYPE_PROGRAM_NAME: u8 = 3; +pub const XDS_TYPE_PROGRAM_TYPE: u8 = 4; +pub const XDS_TYPE_CONTENT_ADVISORY: u8 = 5; +pub const XDS_TYPE_AUDIO_SERVICES: u8 = 6; +pub const XDS_TYPE_CGMS: u8 = 8; // Copy Generation Management System +pub const XDS_TYPE_ASPECT_RATIO_INFO: u8 = 9; // Appears in CEA-608-B but in E it's been removed as is "reserved" +pub const XDS_TYPE_PROGRAM_DESC_1: u8 = 0x10; +pub const XDS_TYPE_PROGRAM_DESC_2: u8 = 0x11; +pub const XDS_TYPE_PROGRAM_DESC_3: u8 = 0x12; +pub const XDS_TYPE_PROGRAM_DESC_4: u8 = 0x13; +pub const XDS_TYPE_PROGRAM_DESC_5: u8 = 0x14; +pub const XDS_TYPE_PROGRAM_DESC_6: u8 = 0x15; +pub const XDS_TYPE_PROGRAM_DESC_7: u8 = 0x16; +pub const XDS_TYPE_PROGRAM_DESC_8: u8 = 0x17; + +// Types for the class channel +pub const XDS_TYPE_NETWORK_NAME: u8 = 1; +pub const XDS_TYPE_CALL_LETTERS_AND_CHANNEL: u8 = 2; +pub const XDS_TYPE_TSID: u8 = 4; // Transmission Signal Identifier + +// Types for miscellaneous packets +pub const XDS_TYPE_TIME_OF_DAY: u8 = 1; +pub const XDS_TYPE_LOCAL_TIME_ZONE: u8 = 4; +pub const XDS_TYPE_OUT_OF_BAND_CHANNEL_NUMBER: u8 = 0x40; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct XdsBuffer { + pub in_use: i64, // Whether the buffer is in use + pub xds_class: i64, // XDS class (e.g., XDS_CLASS_CURRENT, etc.) + pub xds_type: i64, // XDS type (e.g., XDS_TYPE_PROGRAM_NAME, etc.) + pub bytes: [u8; NUM_BYTES_PER_PACKET as usize], // Data bytes (size defined by NUM_BYTES_PER_PACKET) + pub used_bytes: i64, // Number of bytes used in the buffer +} + +#[repr(C)] +pub struct CcxDecodersXdsContext { + // Program Identification Number (Start Time) for current program + pub current_xds_min: i64, + pub current_xds_hour: i64, + pub current_xds_date: i64, + pub current_xds_month: i64, + pub current_program_type_reported: i64, // No. + pub xds_start_time_shown: i64, + pub xds_program_length_shown: i64, + pub xds_program_description: [[u8; 33]; 8], // 8 strings of 33 bytes each + + pub current_xds_network_name: [u8; 33], // String of 33 bytes + pub current_xds_program_name: [u8; 33], // String of 33 bytes + pub current_xds_call_letters: [u8; 7], // String of 7 bytes + pub current_xds_program_type: [u8; 33], // String of 33 bytes + + pub xds_buffers: [XdsBuffer; NUM_XDS_BUFFERS as usize], // Array of XdsBuffer + pub cur_xds_buffer_idx: i64, + pub cur_xds_packet_class: i64, + pub cur_xds_payload: *mut u8, // Pointer to payload + pub cur_xds_payload_length: i64, + pub cur_xds_packet_type: i64, + pub timing: TimingContext, // Replacing ccx_common_timing_ctx with TimingContext + + pub current_ar_start: i64, + pub current_ar_end: i64, + + pub xds_write_to_file: i64, // Set to 1 if XDS data is to be written to file +} + +impl Default for CcxDecodersXdsContext { + fn default() -> Self { + Self { + current_xds_min: -1, + current_xds_hour: -1, + current_xds_date: -1, + current_xds_month: -1, + current_program_type_reported: 0, + xds_start_time_shown: 0, + xds_program_length_shown: 0, + xds_program_description: [[0; 33]; 8], + current_xds_network_name: [0; 33], + current_xds_program_name: [0; 33], + current_xds_call_letters: [0; 7], + current_xds_program_type: [0; 33], + xds_buffers: [XdsBuffer { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + }; NUM_XDS_BUFFERS as usize], + cur_xds_buffer_idx: -1, + cur_xds_packet_class: -1, + cur_xds_payload: std::ptr::null_mut(), + cur_xds_payload_length: 0, + cur_xds_packet_type: 0, + timing: TimingContext::default(), + current_ar_start: 0, + current_ar_end: 0, + xds_write_to_file: 0, + } + } +} + +impl Default for XdsBuffer { + fn default() -> Self { + Self { + in_use: 0, + xds_class: -1, + xds_type: -1, + bytes: [0; NUM_BYTES_PER_PACKET as usize], + used_bytes: 0, + } + } +} + +impl Default for CcSubtitle { + fn default() -> Self { + Self { + data: std::ptr::null_mut(), + datatype: SubDataType::Generic, + nb_data: 0, + subtype: SubType::Cc608, + enc_type: CcxEncodingType::Utf8, + start_time: 0, + end_time: 0, + flags: 0, + lang_index: 0, + got_output: false, + mode: [0; 5], + info: [0; 4], + time_out: 0, + next: std::ptr::null_mut(), + prev: std::ptr::null_mut(), + } + } +} diff --git a/src/rust/lib_ccxr/src/decoder_vbi/vbi_service_table.rs b/src/rust/lib_ccxr/src/decoder_vbi/vbi_service_table.rs new file mode 100644 index 000000000..b53ed2b5c --- /dev/null +++ b/src/rust/lib_ccxr/src/decoder_vbi/vbi_service_table.rs @@ -0,0 +1,312 @@ +use crate::decoder_vbi::exit_codes::*; +use crate::decoder_vbi::structs_vbi_decode::*; + +// 2 fields skip - fixme(if 0), last one(default/with all zeroes) +pub const VBI_SERVICE_TABLE: [VbiServicePar; 20 - 2] = [ + VbiServicePar { + id: VBI_SLICED_TELETEXT_A, + label: c"Teletext System A".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [6, 318], + last: [22, 335], + offset: 10500, + cri_rate: 6203125, + bit_rate: 6203125, + cri_frc: 0x00AAAAE7, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 37 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_TELETEXT_B_L10_625, + label: c"Teletext System B 625 Level 1.5".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [7, 320], + last: [22, 335], + offset: 10300, + cri_rate: 6937500, + bit_rate: 6937500, + cri_frc: 0x00AAAAE4, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 42 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_TELETEXT_B, + label: c"Teletext System B, 625".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [6, 318], + last: [22, 335], + offset: 10300, + cri_rate: 6937500, + bit_rate: 6937500, + cri_frc: 0x00AAAAE4, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 42 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_TELETEXT_C_625, + label: c"Teletext System C 625".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [6, 318], + last: [22, 335], + offset: 10480, + cri_rate: 5734375, + bit_rate: 5734375, + cri_frc: 0x00AAAAE7, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 33 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_TELETEXT_D_625, + label: c"Teletext System D 625".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [6, 318], + last: [22, 335], + offset: 10500, + cri_rate: 5642787, + bit_rate: 5642787, + cri_frc: 0x00AAAAE5, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 34 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_VPS, + label: c"Video Program System".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_PAL_BG, + first: [16, 0], + last: [16, 0], + offset: 12500, + cri_rate: 5000000, + bit_rate: 2500000, + cri_frc: 0xAAAA8A99, + cri_frc_mask: 0xFFFFFF, + cri_bits: 32, + frc_bits: 0, + payload: 13 * 8, + modulation: VbiModulation::BiphaseMsb, + flags: VbiServiceParFlag::FieldNum, + }, + VbiServicePar { + id: VBI_SLICED_VPS_F2, + label: c"Pseudo-VPS on field 2".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_PAL_BG, + first: [0, 329], + last: [0, 329], + offset: 12500, + cri_rate: 5000000, + bit_rate: 2500000, + cri_frc: 0xAAAA8A99, + cri_frc_mask: 0xFFFFFF, + cri_bits: 32, + frc_bits: 0, + payload: 13 * 8, + modulation: VbiModulation::BiphaseMsb, + flags: VbiServiceParFlag::FieldNum, + }, + VbiServicePar { + id: VBI_SLICED_WSS_625, + label: c"Wide Screen Signalling 625".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [23, 0], + last: [23, 0], + offset: 11000, + cri_rate: 5000000, + bit_rate: 833333, + cri_frc: 0x8E3C783E, + cri_frc_mask: 0x2499339C, + cri_bits: 32, + frc_bits: 0, + payload: 14, + modulation: VbiModulation::BiphaseLsb, + flags: VbiServiceParFlag::LineAndField, + }, + VbiServicePar { + id: VBI_SLICED_CAPTION_625_F1, + label: c"Closed Caption 625, field 1".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [22, 0], + last: [22, 0], + offset: 10500, + cri_rate: 1000000, + bit_rate: 500000, + cri_frc: 0x00005551, + cri_frc_mask: 0x7FF, + cri_bits: 14, + frc_bits: 2, + payload: 2 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::FieldNum, + }, + VbiServicePar { + id: VBI_SLICED_CAPTION_625_F2, + label: c"Closed Caption 625, field 2".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [0, 335], + last: [0, 335], + offset: 10500, + cri_rate: 1000000, + bit_rate: 500000, + cri_frc: 0x00005551, + cri_frc_mask: 0x7FF, + cri_bits: 14, + frc_bits: 2, + payload: 2 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::FieldNum, + }, + VbiServicePar { + id: VBI_SLICED_VBI_625, + label: c"VBI 625".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_625_50, + first: [6, 318], + last: [22, 335], + offset: 10000, + cri_rate: 1510000, + bit_rate: 1510000, + cri_frc: 0, + cri_frc_mask: 0, + cri_bits: 0, + frc_bits: 0, + payload: 10 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_TELETEXT_B_525, + label: c"Teletext System B 525".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_525_60, + first: [10, 272], + last: [21, 284], + offset: 10500, + cri_rate: 5727272, + bit_rate: 5727272, + cri_frc: 0x00AAAAE4, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 34 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_TELETEXT_C_525, + label: c"Teletext System C 525".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_525_60, + first: [10, 272], + last: [21, 284], + offset: 10480, + cri_rate: 5727272, + bit_rate: 5727272, + cri_frc: 0x00AAAAE7, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 33 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_TELETEXT_D_525, + label: c"Teletext System D 525".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_525_60, + first: [10, 272], + last: [21, 284], + offset: 9780, + cri_rate: 5727272, + bit_rate: 5727272, + cri_frc: 0x00AAAAE5, + cri_frc_mask: 0xFFFF, + cri_bits: 18, + frc_bits: 6, + payload: 34 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, + VbiServicePar { + id: VBI_SLICED_CAPTION_525_F1, + label: c"Closed Caption 525, field 1".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_525_60, + first: [21, 0], + last: [21, 0], + offset: 10500, + cri_rate: 1006976, + bit_rate: 503488, + cri_frc: 0x03, + cri_frc_mask: 0x0F, + cri_bits: 4, + frc_bits: 0, + payload: 2 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineAndField, + }, + VbiServicePar { + id: VBI_SLICED_CAPTION_525_F2, + label: c"Closed Caption 525, field 2".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_525_60, + first: [0, 284], + last: [0, 284], + offset: 10500, + cri_rate: 1006976, + bit_rate: 503488, + cri_frc: 0x03, + cri_frc_mask: 0x0F, + cri_bits: 4, + frc_bits: 0, + payload: 2 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineAndField, + }, + VbiServicePar { + id: VBI_SLICED_2XCAPTION_525, + label: c"2xCaption 525".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_525_60, + first: [10, 0], + last: [21, 0], + offset: 10500, + cri_rate: 1006976, + bit_rate: 1006976, + cri_frc: 0x000554ED, + cri_frc_mask: 0xFFFF, + cri_bits: 12, + frc_bits: 8, + payload: 4 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::FieldNum, + }, + VbiServicePar { + id: VBI_SLICED_VBI_525, + label: c"VBI 525".as_ptr(), + videostd_set: VBI_VIDEOSTD_SET_525_60, + first: [10, 272], + last: [21, 284], + offset: 9500, + cri_rate: 1510000, + bit_rate: 1510000, + cri_frc: 0, + cri_frc_mask: 0, + cri_bits: 0, + frc_bits: 0, + payload: 10 * 8, + modulation: VbiModulation::NrzLsb, + flags: VbiServiceParFlag::LineNum, + }, +]; diff --git a/src/rust/lib_ccxr/src/lib.rs b/src/rust/lib_ccxr/src/lib.rs index 9f32678db..500b3cd9a 100644 --- a/src/rust/lib_ccxr/src/lib.rs +++ b/src/rust/lib_ccxr/src/lib.rs @@ -1,5 +1,6 @@ pub mod activity; pub mod common; +pub mod decoder_vbi; pub mod hardsubx; pub mod subtitle; pub mod teletext; diff --git a/src/rust/src/libccxr_exports/mod.rs b/src/rust/src/libccxr_exports/mod.rs index c2f64a258..0e951114c 100644 --- a/src/rust/src/libccxr_exports/mod.rs +++ b/src/rust/src/libccxr_exports/mod.rs @@ -1,6 +1,7 @@ //! Provides C-FFI functions that are direct equivalent of functions available in C. pub mod time; +pub mod vbi_exports; use crate::ccx_options; use lib_ccxr::util::log::*; use lib_ccxr::util::{bits::*, levenshtein::*}; diff --git a/src/rust/src/libccxr_exports/vbi_exports.rs b/src/rust/src/libccxr_exports/vbi_exports.rs new file mode 100644 index 000000000..f0d85297d --- /dev/null +++ b/src/rust/src/libccxr_exports/vbi_exports.rs @@ -0,0 +1,68 @@ +use lib_ccxr::decoder_vbi::exit_codes::CCX_EINVAL; +use lib_ccxr::decoder_vbi::functions_vbi_main::*; +use lib_ccxr::decoder_vbi::structs_ccdecode::LibCcDecode; +use lib_ccxr::decoder_vbi::structs_isdb::CcxDecoderVbiCtx; +use lib_ccxr::decoder_vbi::structs_vbi_decode::CcxDecoderVbiCfg; +use lib_ccxr::decoder_vbi::structs_xds::CcSubtitle; + +use std::ptr; +use std::slice; + +/// Deletes a VBI decoder context. +/// +/// # Safety +/// - `arg` must be a valid, non‐null pointer to a `*mut CcxDecoderVbiCtx` returned by `ccxr_init_decoder_vbi`. +/// - The pointer `*arg` must not have been freed already. +/// - After this call, `*arg` is set to null to prevent double‐free. +#[no_mangle] +pub extern "C" fn ccxr_delete_decoder_vbi(arg: *mut *mut CcxDecoderVbiCtx) { + if !arg.is_null() { + unsafe { + let mut arg_option = Some(*arg); + delete_decoder_vbi(&mut arg_option); + *arg = arg_option.unwrap_or(std::ptr::null_mut()); + } + } +} + +/// Initializes a VBI decoder context. +/// +/// # Safety +/// - `cfg` may be null, in which case defaults are used. +/// - If non‐null, `cfg` must point to a valid `CcxDecoderVbiCfg`. +/// - The returned pointer must be freed with `ccxr_delete_decoder_vbi`. +#[no_mangle] +pub extern "C" fn ccxr_init_decoder_vbi(cfg: *const CcxDecoderVbiCfg) -> *mut CcxDecoderVbiCtx { + let opt = if cfg.is_null() { + None + } else { + Some(unsafe { &*cfg }) + }; + init_decoder_vbi(opt) + .map(|b| Box::into_raw(b)) + .unwrap_or(ptr::null_mut()) +} + +/// Feeds raw VBI data into the decoder and produces subtitles. +/// +/// # Safety +/// - `ctx` must be a valid, non‐null `*mut LibCcDecode` returned by `ccxr_init_decoder_vbi`. +/// - `buf` must point to at least `len` bytes of valid memory. +/// - `sub` must be a valid, non‐null pointer to a `CcSubtitle` struct for output. +/// - Caller must ensure thread‑safety when reusing the same context. +#[no_mangle] +pub extern "C" fn ccxr_decode_vbi( + ctx: *mut LibCcDecode, + field: u8, + buf: *const u8, + len: usize, + sub: *mut CcSubtitle, +) -> i64 { + if ctx.is_null() || buf.is_null() || sub.is_null() { + return CCX_EINVAL; + } + let dec = unsafe { &mut *ctx }; + let data = unsafe { slice::from_raw_parts(buf, len) }; + let sub = unsafe { &mut *sub }; + decode_vbi(dec, field, data, sub) +}