diff --git a/.gitignore b/.gitignore index 50e187721..74ea95d90 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ windows/.vs/** *.opendb *.db *.vscode +.cache #### # Ignore the header file that is updated upon build @@ -153,4 +154,4 @@ src/rust/target/ windows/ccx_rust.lib windows/*/debug/* windows/*/CACHEDIR.TAG -windows/.rustc_info.json \ No newline at end of file +windows/.rustc_info.json diff --git a/src/lib_ccx/ccx_decoders_common.c b/src/lib_ccx/ccx_decoders_common.c index c9e639af6..5200482c2 100644 --- a/src/lib_ccx/ccx_decoders_common.c +++ b/src/lib_ccx/ccx_decoders_common.c @@ -18,6 +18,8 @@ made to reuse, not duplicate, as many functions as possible */ #ifndef DISABLE_RUST extern int ccxr_process_cc_data(struct lib_cc_decode *dec_ctx, unsigned char *cc_data, int cc_count); extern void ccxr_flush_decoder(struct dtvcc_ctx *dtvcc, struct dtvcc_service_decoder *decoder); +extern void *ccxr_dtvcc_init(struct ccx_decoder_dtvcc_settings *settings_dtvcc); +extern void ccxr_dtvcc_free(void *dtvcc_rust); #endif uint64_t utc_refvalue = UINT64_MAX; /* _UI64_MAX/UINT64_MAX means don't use UNIX, 0 = use current system time as reference, +1 use a specific reference */ @@ -232,6 +234,9 @@ int do_cb(struct lib_cc_decode *ctx, unsigned char *cc_block, struct cc_subtitle void dinit_cc_decode(struct lib_cc_decode **ctx) { struct lib_cc_decode *lctx = *ctx; +#ifndef DISABLE_RUST + ccxr_dtvcc_free(lctx->dtvcc_rust); +#endif dtvcc_free(&lctx->dtvcc); dinit_avc(&lctx->avc_ctx); ccx_decoder_608_dinit_library(&lctx->context_cc608_field_1); @@ -261,6 +266,9 @@ struct lib_cc_decode *init_cc_decode(struct ccx_decoders_common_settings_t *sett ctx->no_rollup = setting->no_rollup; ctx->noscte20 = setting->noscte20; +#ifndef DISABLE_RUST + ctx->dtvcc_rust = ccxr_dtvcc_init(setting->settings_dtvcc); +#endif ctx->dtvcc = dtvcc_init(setting->settings_dtvcc); ctx->dtvcc->is_active = setting->settings_dtvcc->enabled; diff --git a/src/lib_ccx/ccx_decoders_structs.h b/src/lib_ccx/ccx_decoders_structs.h index 9d4e0619b..da6b3ace6 100644 --- a/src/lib_ccx/ccx_decoders_structs.h +++ b/src/lib_ccx/ccx_decoders_structs.h @@ -208,6 +208,7 @@ struct lib_cc_decode int stat_divicom; int false_pict_header; + void *dtvcc_rust; dtvcc_ctx *dtvcc; int current_field; // Analyse/use the picture information diff --git a/src/lib_ccx/general_loop.c b/src/lib_ccx/general_loop.c index fc9b59c37..2a6d1d139 100644 --- a/src/lib_ccx/general_loop.c +++ b/src/lib_ccx/general_loop.c @@ -18,6 +18,10 @@ #include "dvd_subtitle_decoder.h" #include "ccx_demuxer_mxf.h" +#ifndef DISABLE_RUST +extern void ccxr_dtvcc_set_encoder(void *dtvcc_rust, void *encoder); +#endif + int end_of_file = 0; // End of file? // Program stream specific data grabber @@ -896,7 +900,11 @@ int process_non_multiprogram_general_loop(struct lib_ccx_ctx *ctx, cinfo = get_cinfo(ctx->demux_ctx, pid); *enc_ctx = update_encoder_list_cinfo(ctx, cinfo); *dec_ctx = update_decoder_list_cinfo(ctx, cinfo); +#ifdef DISABLE_RUST (*dec_ctx)->dtvcc->encoder = (void *)(*enc_ctx); +#else + ccxr_dtvcc_set_encoder((*dec_ctx)->dtvcc_rust, *enc_ctx); +#endif if ((*dec_ctx)->timing->min_pts == 0x01FFFFFFFFLL) // if we didn't set the min_pts of the program { @@ -1093,7 +1101,11 @@ int general_loop(struct lib_ccx_ctx *ctx) enc_ctx = update_encoder_list_cinfo(ctx, cinfo); dec_ctx = update_decoder_list_cinfo(ctx, cinfo); +#ifdef DISABLE_RUST dec_ctx->dtvcc->encoder = (void *)enc_ctx; // WARN: otherwise cea-708 will not work +#else + ccxr_dtvcc_set_encoder(dec_ctx->dtvcc_rust, (void *)enc_ctx); +#endif if (dec_ctx->timing->min_pts == 0x01FFFFFFFFLL) // if we didn't set the min_pts of the program { @@ -1268,7 +1280,11 @@ int rcwt_loop(struct lib_ccx_ctx *ctx) } dec_ctx = update_decoder_list(ctx); +#ifdef DISABLE_RUST dec_ctx->dtvcc->encoder = (void *)enc_ctx; // WARN: otherwise cea-708 will not work +#else + ccxr_dtvcc_set_encoder(dec_ctx->dtvcc_rust, (void *)enc_ctx); +#endif if (parsebuf[6] == 0 && parsebuf[7] == 2) { dec_ctx->codec = CCX_CODEC_TELETEXT; diff --git a/src/lib_ccx/mp4.c b/src/lib_ccx/mp4.c index 1b5c45119..0fcc8346b 100644 --- a/src/lib_ccx/mp4.c +++ b/src/lib_ccx/mp4.c @@ -16,6 +16,11 @@ #define GF_ISOM_SUBTYPE_C708 GF_4CC('c', '7', '0', '8') +#ifndef DISABLE_RUST +extern void ccxr_process_data(void *dtvcc_rust, unsigned char cc_valid, unsigned char cc_char, unsigned char data1, unsigned char data2); +extern void ccxr_dtvcc_set_encoder(void *dtvcc_rust, void *encoder); +#endif + static short bswap16(short v) { return ((v >> 8) & 0x00FF) | ((v << 8) & 0xFF00); @@ -394,8 +399,13 @@ static int process_clcp(struct lib_ccx_ctx *ctx, struct encoder_ctx *enc_ctx, continue; } // WARN: otherwise cea-708 will not work +#ifdef DISABLE_RUST dec_ctx->dtvcc->encoder = (void *)enc_ctx; dtvcc_process_data(dec_ctx->dtvcc, (unsigned char *)temp); +#else + ccxr_dtvcc_set_encoder(dec_ctx->dtvcc_rust, (void *)enc_ctx); + ccxr_process_data(dec_ctx->dtvcc_rust, cc_valid, cc_type, cc_data[1], cc_data[2]); +#endif cb_708++; } if (ctx->write_format == CCX_OF_MCC) diff --git a/src/rust/build.rs b/src/rust/build.rs index f8ecc04c8..068c4aa56 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -59,6 +59,7 @@ fn main() { } let bindings = builder + .derive_default(true) // Finish the builder and generate the bindings. .generate() // Unwrap the Result and panic on failure. diff --git a/src/rust/src/decoder/mod.rs b/src/rust/src/decoder/mod.rs index e13c7ed2d..0077f645f 100644 --- a/src/rust/src/decoder/mod.rs +++ b/src/rust/src/decoder/mod.rs @@ -9,7 +9,10 @@ mod timing; mod tv_screen; mod window; -use crate::{bindings::*, utils::is_true}; +use crate::{ + bindings::*, + utils::{is_false, is_true}, +}; use log::{debug, warn}; @@ -19,47 +22,107 @@ const CCX_DTVCC_SCREENGRID_ROWS: u8 = 75; const CCX_DTVCC_SCREENGRID_COLUMNS: u8 = 210; const CCX_DTVCC_MAX_ROWS: u8 = 15; const CCX_DTVCC_MAX_COLUMNS: u8 = 32 * 2; +const CCX_DTVCC_MAX_SERVICES: usize = 63; +// const CCX_DTVCC_MAX_WINDOWS: usize = 8; /// Context required for processing 708 data -pub struct Dtvcc<'a> { +pub struct Dtvcc { pub is_active: bool, pub active_services_count: u8, - pub services_active: Vec, + pub services_active: [i32; CCX_DTVCC_MAX_SERVICES], pub report_enabled: bool, - pub report: &'a mut ccx_decoder_dtvcc_report, - pub decoders: Vec<&'a mut dtvcc_service_decoder>, - pub packet: Vec, + pub report: *mut ccx_decoder_dtvcc_report, + pub decoders: [Option>; CCX_DTVCC_MAX_SERVICES], + pub packet: [u8; CCX_DTVCC_MAX_SERVICES], pub packet_length: u8, pub is_header_parsed: bool, pub last_sequence: i32, - pub encoder: &'a mut encoder_ctx, + pub encoder: *mut encoder_ctx, pub no_rollup: bool, - pub timing: &'a mut ccx_common_timing_ctx, + pub timing: *mut ccx_common_timing_ctx, } -impl<'a> Dtvcc<'a> { +impl Dtvcc { /// Create a new dtvcc context - pub fn new(ctx: &'a mut dtvcc_ctx) -> Self { - let report = unsafe { &mut *ctx.report }; - let encoder = unsafe { &mut *(ctx.encoder as *mut encoder_ctx) }; - let timing = unsafe { &mut *ctx.timing }; + pub fn new(opts: &ccx_decoder_dtvcc_settings) -> Self { + // closely follows `dtvcc_init` at `src/lib_ccx/ccx_dtvcc.c:76` + + let report = opts.report; + unsafe { + (*report).reset_count = 0; + } + + let is_active = false; + let no_rollup = is_true(opts.no_rollup); + let active_services_count = opts.active_services_count as u8; + let services_active = opts.services_enabled; + + // `dtvcc_clear_packet` does the following + let packet_length = 0; + let is_header_parsed = false; + let packet = [0; CCX_DTVCC_MAX_SERVICES]; // unlike C, packet is allocated on the stack + + let last_sequence = CCX_DTVCC_NO_LAST_SEQUENCE; + + let report_enabled = is_true(opts.print_file_reports); + let timing = opts.timing; + + // unlike C, here the decoders are allocated on the stack as an array. + let decoders = { + const INIT: Option> = None; + let mut decoders = [INIT; CCX_DTVCC_MAX_SERVICES]; + + decoders + .iter_mut() + .zip(opts.services_enabled) + .enumerate() + .for_each(|(i, (d, se))| { + if is_false(se) { + return; + } + + let mut decoder = Box::new(dtvcc_service_decoder { + // we cannot allocate this on the stack as `dtvcc_service_decoder` is a C + // struct cannot be changed trivially + tv: Box::into_raw(Box::new(dtvcc_tv_screen { + cc_count: 0, + service_number: i as i32 + 1, + ..dtvcc_tv_screen::default() + })), + ..dtvcc_service_decoder::default() + }); + + decoder.windows.iter_mut().for_each(|w| { + w.memory_reserved = 0; + }); + + unsafe { dtvcc_windows_reset(decoder.as_mut()) }; + + *d = Some(decoder); + }); + + decoders + }; + + let encoder = std::ptr::null_mut(); // Unlike C, does not mention `encoder` and is initialised to `null` by default Self { - is_active: is_true(ctx.is_active), - active_services_count: ctx.active_services_count as u8, - services_active: ctx.services_active.to_vec(), - report_enabled: is_true(ctx.report_enabled), report, - decoders: ctx.decoders.iter_mut().collect(), - packet: ctx.current_packet.to_vec(), - packet_length: ctx.current_packet_length as u8, - is_header_parsed: is_true(ctx.is_current_packet_header_parsed), - last_sequence: ctx.last_sequence, - encoder, - no_rollup: is_true(ctx.no_rollup), + is_active, + no_rollup, + active_services_count, + services_active, + packet_length, + is_header_parsed, + packet, + last_sequence, + report_enabled, timing, + decoders, + encoder, } } + /// Process cc data and add it to the dtvcc packet pub fn process_cc_data(&mut self, cc_valid: u8, cc_type: u8, data1: u8, data2: u8) { if !self.is_active && !self.report_enabled { @@ -166,15 +229,15 @@ impl<'a> Dtvcc<'a> { } if block_length != 0 { - self.report.services[service_number as usize] = 1; + unsafe { (*self.report).services[service_number as usize] = 1 }; } if service_number > 0 && is_true(self.services_active[(service_number - 1) as usize]) { let decoder = &mut self.decoders[(service_number - 1) as usize]; - decoder.process_service_block( + decoder.as_mut().unwrap().process_service_block( &self.packet[pos as usize..(pos + block_length) as usize], - self.encoder, - self.timing, + unsafe { self.encoder.as_mut().unwrap() }, + unsafe { self.timing.as_mut().unwrap() }, self.no_rollup, ); } @@ -197,6 +260,33 @@ impl<'a> Dtvcc<'a> { } } +impl Drop for Dtvcc { + fn drop(&mut self) { + // closely follows `dtvcc_free` at `src/lib_ccx/ccx_dtvcc.c:126` + for i in 0..CCX_DTVCC_MAX_SERVICES { + if let Some(decoder) = self.decoders[i].as_mut() { + if !is_true(self.services_active[i]) { + continue; + } + + decoder.windows.iter_mut().for_each(|window| { + if is_false(window.memory_reserved) { + return; + } + + window.rows.iter().for_each(|symbol_ptr| unsafe { + symbol_ptr.drop_in_place(); + }); + + window.memory_reserved = 0; + }); + + unsafe { decoder.tv.drop_in_place() }; + } + } + } +} + /// A single character symbol /// /// sym stores the symbol @@ -216,10 +306,3 @@ impl dtvcc_symbol { is_true(self.init) } } - -impl Default for dtvcc_symbol { - /// Create a blank uninitialized symbol - fn default() -> Self { - Self { sym: 0, init: 0 } - } -} diff --git a/src/rust/src/decoder/tv_screen.rs b/src/rust/src/decoder/tv_screen.rs index 71f3f2b2f..cc1deefde 100644 --- a/src/rust/src/decoder/tv_screen.rs +++ b/src/rust/src/decoder/tv_screen.rs @@ -148,8 +148,8 @@ impl dtvcc_tv_screen { use_colors: bool, ) -> Result<(), String> { let mut buf = Vec::new(); - let mut pen_color = dtvcc_pen_color::default(); - let mut pen_attribs = dtvcc_pen_attribs::default(); + let mut pen_color = dtvcc_pen_color::new(); + let mut pen_attribs = dtvcc_pen_attribs::new(); let (first, last) = self.get_write_interval(row_index); debug!("First: {}, Last: {}", first, last); @@ -418,7 +418,7 @@ impl dtvcc_tv_screen { return; } let new_pen_attribs = if col_index >= CCX_DTVCC_SCREENGRID_COLUMNS as usize { - dtvcc_pen_attribs::default() + dtvcc_pen_attribs::new() } else { self.pen_attribs[row_index][col_index] }; @@ -455,7 +455,7 @@ impl dtvcc_tv_screen { return; } let new_pen_color = if col_index >= CCX_DTVCC_SCREENGRID_COLUMNS as usize { - dtvcc_pen_color::default() + dtvcc_pen_color::new() } else { self.pen_colors[row_index][col_index] }; diff --git a/src/rust/src/decoder/window.rs b/src/rust/src/decoder/window.rs index fbd4d024c..36280cf85 100644 --- a/src/rust/src/decoder/window.rs +++ b/src/rust/src/decoder/window.rs @@ -149,9 +149,9 @@ impl dtvcc_window { /// Clear all text from the window pub fn clear_text(&mut self) { // Set pen color to default value - self.pen_color_pattern = dtvcc_pen_color::default(); + self.pen_color_pattern = dtvcc_pen_color::new(); // Set pen attributes to default value - self.pen_attribs_pattern = dtvcc_pen_attribs::default(); + self.pen_attribs_pattern = dtvcc_pen_attribs::new(); for row in 0..CCX_DTVCC_MAX_ROWS as usize { self.clear_row(row); } @@ -521,9 +521,9 @@ enum Opacity { Transparent = 3, } -impl Default for dtvcc_pen_color { +impl dtvcc_pen_color { /// Returns the default pen color - fn default() -> Self { + pub fn new() -> Self { Self { fg_color: 0x3F, fg_opacity: 0, @@ -534,9 +534,9 @@ impl Default for dtvcc_pen_color { } } -impl Default for dtvcc_pen_attribs { +impl dtvcc_pen_attribs { /// Returns the default pen attributes - fn default() -> Self { + pub fn new() -> Self { Self { pen_size: dtvcc_pen_size::DTVCC_PEN_SIZE_STANDART as i32, offset: 0, diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index a76faa419..1fed0e1b1 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -44,6 +44,49 @@ pub extern "C" fn ccxr_init_logger() { .init(); } +/// Create a `dtvcc_rust` +/// +/// SAFETY: +/// The following should not be `NULL`: +/// - opts +/// - opts.report +/// - opts.timing +#[no_mangle] +extern "C" fn ccxr_dtvcc_init(opts_ptr: *const ccx_decoder_dtvcc_settings) -> *mut Dtvcc { + let opts = unsafe { opts_ptr.as_ref() }.unwrap(); + Box::into_raw(Box::new(Dtvcc::new(opts))) +} + +/// Frees `dtvcc_rust` +/// +/// SAFETY: +/// The following should not be `NULL`: +/// - dtvcc_rust +/// - dtvcc_rust.decoders[i] if dtvcc_rust.services_active[i] is true +/// - dtvcc_rust.decoders[i].windows[j].rows[k] if +/// dtvcc_rust.decoders[i].windows[j].memory_reserved is true +/// - dtvcc_rust.decoders[i].tv +#[no_mangle] +extern "C" fn ccxr_dtvcc_free(dtvcc_rust: *mut Dtvcc) { + unsafe { dtvcc_rust.drop_in_place() }; +} + +#[no_mangle] +extern "C" fn ccxr_dtvcc_set_encoder(dtvcc_rust: *mut Dtvcc, encoder: *mut encoder_ctx) { + unsafe { (*dtvcc_rust).encoder = encoder }; +} + +#[no_mangle] +extern "C" fn ccxr_process_data( + dtvcc_rust: *mut Dtvcc, + cc_valid: u8, + cc_type: u8, + data1: u8, + data2: u8, +) { + unsafe { dtvcc_rust.as_mut().unwrap() }.process_cc_data(cc_valid, cc_type, data1, data2); +} + /// Process cc_data /// /// # Safety @@ -60,13 +103,11 @@ extern "C" fn ccxr_process_cc_data( .map(|x| unsafe { *data.add(x as usize) }) .collect(); let dec_ctx = unsafe { &mut *dec_ctx }; - let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc }; - let mut dtvcc = Dtvcc::new(dtvcc_ctx); for cc_block in cc_data.chunks_exact_mut(3) { if !validate_cc_pair(cc_block) { continue; } - let success = do_cb(dec_ctx, &mut dtvcc, cc_block); + let success = do_cb(dec_ctx, cc_block); if success { ret = 0; } @@ -110,7 +151,8 @@ pub fn verify_parity(data: u8) -> bool { } /// Process CC data according to its type -pub fn do_cb(ctx: &mut lib_cc_decode, dtvcc: &mut Dtvcc, cc_block: &[u8]) -> bool { +pub fn do_cb(ctx: &mut lib_cc_decode, cc_block: &[u8]) -> bool { + let dtvcc = unsafe { &mut *(ctx.dtvcc_rust as *mut decoder::Dtvcc) }; let cc_valid = (cc_block[0] & 4) >> 2; let cc_type = cc_block[0] & 3; let mut timeok = true;