diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 9f921024f..8b63ac7fd 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -20,10 +20,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Set up Git + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' - name: Format code + id: format run: | find src/ -type f -not -path "src/thirdparty/*" -not -path "src/lib_ccx/zvbi/*" -name '*.c' -not -path "src/GUI/icon_data.c" | xargs clang-format -i - git diff-index --quiet HEAD -- || (git diff && exit 1) + git diff-index --quiet HEAD -- || echo "changes_detected=true" >> $GITHUB_ENV + - name: Commit changes + if: env.changes_detected == 'true' + run: | + git add . + git commit -m "Automated code formatting by GitHub Actions" + git push format_rust: runs-on: ubuntu-latest strategy: @@ -51,7 +62,7 @@ jobs: - name: dependencies run: sudo apt update && sudo apt install libtesseract-dev libavformat-dev libavdevice-dev libswscale-dev yasm - name: rustfmt - run: cargo fmt --all -- --check + run: cargo fmt --all - name: clippy run: | - cargo clippy -- -D warnings + cargo clippy -- -D warnings \ No newline at end of file diff --git a/src/lib_ccx/ccx_decoders_common.c b/src/lib_ccx/ccx_decoders_common.c index 7961506f6..629bda0f5 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 int ccxr_dtvcc_init(struct lib_cc_decode *ctx); +extern void ccxr_dtvcc_free(struct lib_cc_decode *ctx); #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 */ @@ -263,6 +265,12 @@ struct lib_cc_decode *init_cc_decode(struct ccx_decoders_common_settings_t *sett ctx->dtvcc = dtvcc_init(setting->settings_dtvcc); ctx->dtvcc->is_active = setting->settings_dtvcc->enabled; + ctx->dtvcc_rust = NULL; // Initialize dtvcc_rust to NULL + + // Initialize the Rust Dtvcc instance + if (ccxr_dtvcc_init(ctx) != 0) { + ccx_log("Failed to initialize Rust Dtvcc instance\n"); + } if (setting->codec == CCX_CODEC_ATSC_CC) { @@ -540,6 +548,10 @@ struct lib_cc_decode *copy_decoder_context(struct lib_cc_decode *ctx) ctx_copy->dtvcc = malloc(sizeof(struct dtvcc_ctx)); memcpy(ctx_copy->dtvcc, ctx->dtvcc, sizeof(struct dtvcc_ctx)); } + + // dtvcc_rust will be initialized later by ccxr_dtvcc_init + ctx_copy->dtvcc_rust = NULL; + if (ctx->xds_ctx) { ctx_copy->xds_ctx = malloc(sizeof(struct ccx_decoders_xds_context)); @@ -593,6 +605,10 @@ void free_decoder_context(struct lib_cc_decode *ctx) freep(&ctx->timing); freep(&ctx->avc_ctx); freep(&ctx->private_data); + + // Free the Rust Dtvcc instance + ccxr_dtvcc_free(ctx); + freep(&ctx->dtvcc); freep(&ctx->xds_ctx); freep(&ctx->vbi_decoder); diff --git a/src/lib_ccx/ccx_decoders_common.h b/src/lib_ccx/ccx_decoders_common.h index 0db796b73..a08dc4d21 100644 --- a/src/lib_ccx/ccx_decoders_common.h +++ b/src/lib_ccx/ccx_decoders_common.h @@ -32,4 +32,10 @@ struct cc_subtitle* copy_subtitle(struct cc_subtitle *sub); void free_encoder_context(struct encoder_ctx *ctx); void free_decoder_context(struct lib_cc_decode *ctx); void free_subtitle(struct cc_subtitle* sub); + +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 int ccxr_dtvcc_init(struct lib_cc_decode *ctx); +extern void ccxr_dtvcc_free(struct lib_cc_decode *ctx); + #endif diff --git a/src/lib_ccx/ccx_decoders_structs.h b/src/lib_ccx/ccx_decoders_structs.h index 9d4e0619b..de77edbdd 100644 --- a/src/lib_ccx/ccx_decoders_structs.h +++ b/src/lib_ccx/ccx_decoders_structs.h @@ -209,6 +209,7 @@ struct lib_cc_decode int false_pict_header; dtvcc_ctx *dtvcc; + void *dtvcc_rust; // Rust Dtvcc instance int current_field; // Analyse/use the picture information int maxtref; // Use to remember the temporal reference number diff --git a/src/lib_ccx/mp4.c b/src/lib_ccx/mp4.c index 05df43fe0..ac6335ef3 100644 --- a/src/lib_ccx/mp4.c +++ b/src/lib_ccx/mp4.c @@ -11,11 +11,25 @@ #include "ccx_mp4.h" #include "activity.h" #include "ccx_dtvcc.h" +#include "ccx_demuxer.h" +#include "png.h" +#include "ccx_decoders_common.h" + +#ifndef DISABLE_RUST +extern int ccxr_dtvcc_init(struct lib_cc_decode *ctx); +extern int ccxr_process_cc_data(struct lib_cc_decode *dec_ctx, unsigned char *cc_data, int cc_count); +#endif #define MEDIA_TYPE(type, subtype) (((u64)(type) << 32) + (subtype)) #define GF_ISOM_SUBTYPE_C708 GF_4CC('c', '7', '0', '8') +// Convert a 4-byte type to 4 separate bytes for printing +#define PRINT_TYPE(t) (t) & 0xFF, ((t) >> 8) & 0xFF, ((t) >> 16) & 0xFF, ((t) >> 24) & 0xFF +#define ATOMPRINT mprint("Atom: [%c%c%c%c]", PRINT_TYPE(head.type)) + +#define ATOM_CONTAINS(a, b) (a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]) + static short bswap16(short v) { return ((v >> 8) & 0x00FF) | ((v << 8) & 0xFF00); @@ -418,7 +432,38 @@ static int process_clcp(struct lib_ccx_ctx *ctx, struct encoder_ctx *enc_ctx, } // WARN: otherwise cea-708 will not work dec_ctx->dtvcc->encoder = (void *)enc_ctx; + + // Use Rust implementation if available +#ifndef DISABLE_RUST + // Initialize Rust Dtvcc if not already done + if (dec_ctx->dtvcc_rust == NULL) { + if (ccxr_dtvcc_init(dec_ctx) != 0) { + ccx_log("Failed to initialize Rust Dtvcc instance\n"); + // Fall back to C implementation + dtvcc_process_data(dec_ctx->dtvcc, (unsigned char *)temp); + return -1; + } + } + + if (dec_ctx->dtvcc_rust != NULL) + { + // Process data using Rust implementation + unsigned char cc_block[3]; + cc_block[0] = (cc_valid << 2) | cc_type; + cc_block[1] = cc_data[1]; + cc_block[2] = cc_data[2]; + + // For Rust, we send the whole cc_block instead of temp + ccxr_process_cc_data(dec_ctx, cc_block, 1); + } + else + { + // Fallback to C implementation + dtvcc_process_data(dec_ctx->dtvcc, (unsigned char *)temp); + } +#else dtvcc_process_data(dec_ctx->dtvcc, (unsigned char *)temp); +#endif cb_708++; } if (ctx->write_format == CCX_OF_MCC) diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index d6b575d89..a932c852c 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -13,6 +13,10 @@ pub mod bindings { include!(concat!(env!("OUT_DIR"), "/bindings.rs")); } +// Constants from ccx_decoders_structs.h +pub const MAXBFRAMES: usize = 50; +pub const SORTBUF: usize = 2 * MAXBFRAMES + 1; + pub mod args; pub mod common; pub mod decoder; @@ -39,9 +43,95 @@ use log::{warn, LevelFilter}; use std::{ ffi::CStr, io::Write, - os::raw::{c_char, c_double, c_int, c_long, c_uint}, + os::raw::{c_char, c_double, c_int, c_long, c_uint, c_void}, }; +// Add a new field to store the Rust Dtvcc instance +#[repr(C)] +pub struct lib_cc_decode { + pub dtvcc_rust: *mut Dtvcc<'static>, + pub dtvcc: *mut dtvcc_ctx, + pub write_format: ccx_output_format, + pub cc_stats: [c_int; 4], + pub timing: *mut ccx_common_timing_ctx, + pub current_field: c_int, + pub extraction_start: ccx_boundary_time, + pub extraction_end: ccx_boundary_time, + pub processed_enough: c_int, + pub saw_caption_block: c_int, + pub context_cc608_field_1: *mut c_void, + pub context_cc608_field_2: *mut c_void, + pub no_rollup: c_int, + pub noscte20: c_int, + pub fix_padding: c_int, + pub subs_delay: i64, + pub extract: c_int, + pub fullbin: c_int, + pub dec_sub: cc_subtitle, + pub in_bufferdatatype: ccx_bufferdata_type, + pub hauppauge_mode: c_uint, + pub frames_since_last_gop: c_int, + pub saw_gop_header: c_int, + pub max_gop_length: c_int, + pub last_gop_length: c_int, + pub total_pulldownfields: c_uint, + pub total_pulldownframes: c_uint, + pub program_number: c_int, + pub list: list_head, + pub codec: ccx_code_type, + pub has_ccdata_buffered: c_int, + pub is_alloc: c_int, + pub avc_ctx: *mut avc_ctx, + pub private_data: *mut c_void, + pub current_hor_size: c_uint, + pub current_vert_size: c_uint, + pub current_aspect_ratio: c_uint, + pub current_frame_rate: c_uint, + pub no_bitstream_error: c_int, + pub saw_seqgoppic: c_int, + pub in_pic_data: c_int, + pub current_progressive_sequence: c_uint, + pub current_pulldownfields: c_uint, + pub temporal_reference: c_int, + pub picture_coding_type: ccx_frame_type, + pub num_key_frames: c_uint, + pub picture_structure: c_uint, + pub repeat_first_field: c_uint, + pub progressive_frame: c_uint, + pub pulldownfields: c_uint, + pub top_field_first: c_uint, + pub stat_numuserheaders: c_int, + pub stat_dvdccheaders: c_int, + pub stat_scte20ccheaders: c_int, + pub stat_replay5000headers: c_int, + pub stat_replay4000headers: c_int, + pub stat_dishheaders: c_int, + pub stat_hdtv: c_int, + pub stat_divicom: c_int, + pub false_pict_header: c_int, + pub maxtref: c_int, + pub cc_data_count: [c_int; SORTBUF], + pub cc_fts: [i64; SORTBUF], + pub cc_data_pkts: [[u8; 10*31*3+1]; SORTBUF], + pub anchor_seq_number: c_int, + pub xds_ctx: *mut ccx_decoders_xds_context, + pub vbi_decoder: *mut ccx_decoder_vbi_ctx, + pub writedata: Option c_int>, + pub ocr_quantmode: c_int, + pub prev: *mut lib_cc_decode, +} + +// Define the ccx_boundary_time struct +#[repr(C)] +#[derive(Default)] +pub struct ccx_boundary_time { + pub hh: c_int, + pub mm: c_int, + pub ss: c_int, + pub time_in_ms: i64, + pub set: c_int, +} + #[cfg(test)] static mut cb_708: c_int = 0; #[cfg(test)] @@ -86,6 +176,41 @@ pub extern "C" fn ccxr_init_logger() { .init(); } +/// Initialize a new Dtvcc instance and store it in the lib_cc_decode struct +#[no_mangle] +pub extern "C" fn ccxr_dtvcc_init(dec_ctx: *mut lib_cc_decode) -> c_int { + if dec_ctx.is_null() { + return -1; + } + + let dec_ctx = unsafe { &mut *dec_ctx }; + if dec_ctx.dtvcc.is_null() { + return -1; + } + + let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc }; + let dtvcc = Box::new(Dtvcc::new(dtvcc_ctx)); + dec_ctx.dtvcc_rust = Box::into_raw(dtvcc) as *mut Dtvcc<'static>; + + 0 +} + +/// Free the Dtvcc instance stored in the lib_cc_decode struct +#[no_mangle] +pub extern "C" fn ccxr_dtvcc_free(dec_ctx: *mut lib_cc_decode) { + if dec_ctx.is_null() { + return; + } + + let dec_ctx = unsafe { &mut *dec_ctx }; + if !dec_ctx.dtvcc_rust.is_null() { + unsafe { + let _ = Box::from_raw(dec_ctx.dtvcc_rust); + } + dec_ctx.dtvcc_rust = std::ptr::null_mut(); + } +} + /// Process cc_data /// /// # Safety @@ -102,13 +227,35 @@ 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); + + // Use the stored Dtvcc instance instead of creating a new one + if dec_ctx.dtvcc_rust.is_null() { + // If dtvcc_rust is null, initialize it + if ccxr_dtvcc_init(dec_ctx) != 0 { + // If initialization fails, fall back to the old method + let dtvcc_ctx = unsafe { &mut *dec_ctx.dtvcc }; + let mut dtvcc = Dtvcc::new(dtvcc_ctx); + + for cc_block in cc_data.chunks_exact_mut(3) { + if !validate_cc_pair(cc_block) { + continue; + } + let success = do_cb(dec_ctx, &mut dtvcc, cc_block); + if success { + ret = 0; + } + } + return ret; + } + } + + let dtvcc = unsafe { &mut *dec_ctx.dtvcc_rust }; + 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, dtvcc, cc_block); if success { ret = 0; } @@ -294,6 +441,85 @@ pub unsafe extern "C" fn ccxr_parse_parameters(argc: c_int, argv: *mut *mut c_ch ExitCause::Ok.exit_code() } +// Implement Default for lib_cc_decode +impl Default for lib_cc_decode { + fn default() -> Self { + // Create a default instance with all fields zeroed/nulled + Self { + dtvcc_rust: std::ptr::null_mut(), + dtvcc: std::ptr::null_mut(), + write_format: ccx_output_format::CCX_OF_SRT, // Default to SRT + cc_stats: [0; 4], + timing: std::ptr::null_mut(), + current_field: 0, + extraction_start: ccx_boundary_time::default(), + extraction_end: ccx_boundary_time::default(), + processed_enough: 0, + saw_caption_block: 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, + subs_delay: 0, + extract: 0, + fullbin: 0, + dec_sub: unsafe { std::mem::zeroed() }, + in_bufferdatatype: unsafe { std::mem::zeroed() }, + 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: unsafe { std::mem::zeroed() }, + codec: unsafe { std::mem::zeroed() }, + 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: unsafe { std::mem::zeroed() }, + 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, + 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(), + vbi_decoder: std::ptr::null_mut(), + writedata: None, + ocr_quantmode: 0, + prev: std::ptr::null_mut(), + } + } +} + #[cfg(test)] mod test { use super::*;