diff --git a/src/lib_ccx/ccx_demuxer_mxf.c b/src/lib_ccx/ccx_demuxer_mxf.c index 07326d328..35856e86b 100644 --- a/src/lib_ccx/ccx_demuxer_mxf.c +++ b/src/lib_ccx/ccx_demuxer_mxf.c @@ -75,12 +75,15 @@ enum MXFLocalTag void update_tid_lut(struct MXFContext *ctx, uint32_t track_id, uint8_t *track_number, struct ccx_rational edit_rate) { int i; + debug("update_tid_lut: track_id=%u (0x%x), track_number=%02X%02X%02X%02X, cap_track_id=%u\n", + track_id, track_id, track_number[0], track_number[1], track_number[2], track_number[3], ctx->cap_track_id); // Update essence element key if we have track Id of caption if (ctx->cap_track_id == track_id) { memcpy(ctx->cap_essence_key, mxf_essence_element_key, 12); memcpy(ctx->cap_essence_key + 12, track_number, 4); ctx->edit_rate = edit_rate; + debug("MXF: Found caption track, track_id=%u\n", track_id); } for (i = 0; i < ctx->nb_tracks; i++) @@ -248,6 +251,7 @@ static int mxf_read_vanc_vbi_desc(struct ccx_demuxer *demux, uint64_t size) { case MXF_TAG_LTRACK_ID: ctx->cap_track_id = buffered_get_be32(demux); + debug("MXF: VANC/VBI descriptor found, Linked Track ID = %u\n", ctx->cap_track_id); update_cap_essence_key(ctx, ctx->cap_track_id); break; default: @@ -304,6 +308,17 @@ static int mxf_read_cdp_data(struct ccx_demuxer *demux, int size, struct demuxer log("Incomplete CDP packet\n"); ret = buffered_read(demux, data->buffer + data->len, cc_count * 3); + // Log first few bytes of cc_data for debugging + if (cc_count > 0) + { + unsigned char *cc_ptr = data->buffer + data->len; + debug("cc_data (first 6 triplets): "); + for (int j = 0; j < (cc_count < 6 ? cc_count : 6); j++) + { + debug("%02X%02X%02X ", cc_ptr[j * 3], cc_ptr[j * 3 + 1], cc_ptr[j * 3 + 2]); + } + debug("\n"); + } data->len += cc_count * 3; demux->past += cc_count * 3; len += ret; @@ -361,7 +376,10 @@ static int mxf_read_vanc_data(struct ccx_demuxer *demux, uint64_t size, struct d // uint8_t count; /* Currently unused */ if (size < 19) + { + debug("VANC data too small: %" PRIu64 " < 19\n", size); goto error; + } ret = buffered_read(demux, vanc_header, 16); @@ -370,31 +388,39 @@ static int mxf_read_vanc_data(struct ccx_demuxer *demux, uint64_t size, struct d return CCX_EOF; len += ret; + debug("VANC header: num_packets=%d, line=0x%02x%02x, wrap_type=0x%02x, sample_config=0x%02x\n", + vanc_header[1], vanc_header[2], vanc_header[3], vanc_header[4], vanc_header[5]); + for (int i = 0; i < vanc_header[1]; i++) { DID = buffered_get_byte(demux); len++; + debug("VANC packet %d: DID=0x%02x\n", i, DID); if (!(DID == 0x61 || DID == 0x80)) { + debug("DID 0x%02x not recognized as caption DID\n", DID); goto error; } SDID = buffered_get_byte(demux); len++; + debug("VANC packet %d: SDID=0x%02x\n", i, SDID); if (SDID == 0x01) debug("Caption Type 708\n"); else if (SDID == 0x02) debug("Caption Type 608\n"); cdp_size = buffered_get_byte(demux); + debug("VANC packet %d: cdp_size=%d\n", i, cdp_size); if (cdp_size + 19 > size) { - debug("Incomplete cdp(%d) in anc data(%d)\n", cdp_size, size); + log("Incomplete cdp(%d) in anc data(%" PRIu64 ")\n", cdp_size, size); goto error; } len++; ret = mxf_read_cdp_data(demux, cdp_size, data); + debug("mxf_read_cdp_data returned %d, data->len=%d\n", ret, data->len); len += ret; // len += (3 + count + 4); } @@ -411,15 +437,33 @@ static int mxf_read_essence_element(struct ccx_demuxer *demux, uint64_t size, st int ret; struct MXFContext *ctx = demux->private_data; + debug("mxf_read_essence_element: ctx->type=%d (ANC=%d, VBI=%d), size=%" PRIu64 "\n", + ctx->type, MXF_CT_ANC, MXF_CT_VBI, size); + if (ctx->type == MXF_CT_ANC) { data->bufferdatatype = CCX_RAW_TYPE; ret = mxf_read_vanc_data(demux, size, data); - data->pts = ctx->cap_count; + debug("mxf_read_vanc_data returned %d, data->len=%d\n", ret, data->len); + // Calculate PTS in 90kHz units from frame count and edit rate + // edit_rate is frames per second (e.g., 25/1 for 25fps) + // PTS = frame_count * 90000 / fps = frame_count * 90000 * edit_rate.den / edit_rate.num + if (ctx->edit_rate.num > 0 && ctx->edit_rate.den > 0) + { + data->pts = (int64_t)ctx->cap_count * 90000 * ctx->edit_rate.den / ctx->edit_rate.num; + } + else + { + // Fallback to 25fps if edit_rate not set + data->pts = (int64_t)ctx->cap_count * 90000 / 25; + } + debug("Frame %d, PTS=%" PRId64 " (edit_rate=%d/%d)\n", + ctx->cap_count, data->pts, ctx->edit_rate.num, ctx->edit_rate.den); ctx->cap_count++; } else { + debug("Skipping essence element (not ANC type)\n"); ret = buffered_skip(demux, size); demux->past += ret; } @@ -514,6 +558,7 @@ static int read_packet(struct ccx_demuxer *demux, struct demuxer_data *data) KLVPacket klv; const MXFReadTableEntry *reader; struct MXFContext *ctx = demux->private_data; + static int first_essence_logged = 0; while ((ret = klv_read_packet(&klv, demux)) == 0) { debug("Key %02X%02X%02X%02X%02X%02X%02X%02X.%02X%02X%02X%02X%02X%02X%02X%02X size %" PRIu64 "\n", @@ -523,8 +568,25 @@ static int read_packet(struct ccx_demuxer *demux, struct demuxer_data *data) klv.key[12], klv.key[13], klv.key[14], klv.key[15], klv.length); + // Check if this is an essence element key (first 12 bytes match) + if (IS_KLV_KEY(klv.key, mxf_essence_element_key) && !first_essence_logged) + { + debug("MXF: First essence element key: %02X%02X%02X%02X%02X%02X%02X%02X.%02X%02X%02X%02X%02X%02X%02X%02X\n", + klv.key[0], klv.key[1], klv.key[2], klv.key[3], + klv.key[4], klv.key[5], klv.key[6], klv.key[7], + klv.key[8], klv.key[9], klv.key[10], klv.key[11], + klv.key[12], klv.key[13], klv.key[14], klv.key[15]); + debug("MXF: cap_essence_key: %02X%02X%02X%02X%02X%02X%02X%02X.%02X%02X%02X%02X%02X%02X%02X%02X\n", + ctx->cap_essence_key[0], ctx->cap_essence_key[1], ctx->cap_essence_key[2], ctx->cap_essence_key[3], + ctx->cap_essence_key[4], ctx->cap_essence_key[5], ctx->cap_essence_key[6], ctx->cap_essence_key[7], + ctx->cap_essence_key[8], ctx->cap_essence_key[9], ctx->cap_essence_key[10], ctx->cap_essence_key[11], + ctx->cap_essence_key[12], ctx->cap_essence_key[13], ctx->cap_essence_key[14], ctx->cap_essence_key[15]); + first_essence_logged = 1; + } + if (IS_KLV_KEY(klv.key, ctx->cap_essence_key)) { + debug("MXF: Found ANC essence element, size=%" PRIu64 "\n", klv.length); mxf_read_essence_element(demux, klv.length, data); if (data->len > 0) break; @@ -566,8 +628,15 @@ int ccx_mxf_getmoredata(struct lib_ccx_ctx *ctx, struct demuxer_data **ppdata) data->program_number = 1; data->stream_pid = 1; data->codec = CCX_CODEC_ATSC_CC; - data->tb.num = 1001; - data->tb.den = 30000; + // PTS is already calculated in 90kHz units by mxf_read_essence_element + data->tb.num = 1; + data->tb.den = 90000; + + // Enable CEA-708 (DTVCC) decoder for MXF files with VANC captions + if (ctx->dec_global_setting && ctx->dec_global_setting->settings_dtvcc) + { + ctx->dec_global_setting->settings_dtvcc->enabled = 1; + } } else { @@ -576,6 +645,11 @@ int ccx_mxf_getmoredata(struct lib_ccx_ctx *ctx, struct demuxer_data **ppdata) ret = read_packet(ctx->demux_ctx, data); + // Ensure timebase is 90kHz since PTS is calculated in 90kHz units + // CDP parsing may have set a frame-based timebase which would cause incorrect conversion + data->tb.num = 1; + data->tb.den = 90000; + return ret; } diff --git a/src/lib_ccx/ccx_dtvcc.h b/src/lib_ccx/ccx_dtvcc.h index 446064a1d..4f9255550 100644 --- a/src/lib_ccx/ccx_dtvcc.h +++ b/src/lib_ccx/ccx_dtvcc.h @@ -17,6 +17,7 @@ extern void ccxr_dtvcc_free(void *dtvcc_rust); extern void ccxr_dtvcc_process_data(void *dtvcc_rust, const unsigned char cc_valid, const unsigned char cc_type, const unsigned char data1, const unsigned char data2); extern int ccxr_dtvcc_is_active(void *dtvcc_rust); +extern void ccxr_dtvcc_set_active(void *dtvcc_rust, int active); #endif #endif // CCEXTRACTOR_CCX_DTVCC_H diff --git a/src/lib_ccx/general_loop.c b/src/lib_ccx/general_loop.c index 40730063d..3a26f5b17 100644 --- a/src/lib_ccx/general_loop.c +++ b/src/lib_ccx/general_loop.c @@ -18,6 +18,7 @@ #include "ccx_gxf.h" #include "dvd_subtitle_decoder.h" #include "ccx_demuxer_mxf.h" +#include "ccx_dtvcc.h" int end_of_file = 0; // End of file? @@ -861,7 +862,34 @@ int process_data(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, str } else if (data_node->bufferdatatype == CCX_RAW_TYPE) { - got = process_raw_with_field(dec_ctx, dec_sub, data_node->buffer, data_node->len); + // CCX_RAW_TYPE contains cc_data triplets (cc_type + 2 data bytes each) + // Used by MXF and GXF demuxers + + // Initialize timing if not set (use caption PTS as reference) + if (dec_ctx->timing->pts_set == 0 && data_node->pts != CCX_NOPTS) + { + dec_ctx->timing->min_pts = data_node->pts; + dec_ctx->timing->pts_set = 2; // MinPtsSet + dec_ctx->timing->sync_pts = data_node->pts; + set_fts(dec_ctx->timing); + } + +#ifndef DISABLE_RUST + // Enable DTVCC decoder for CEA-708 captions from MXF/GXF + if (dec_ctx->dtvcc_rust) + { + int is_active = ccxr_dtvcc_is_active(dec_ctx->dtvcc_rust); + if (!is_active) + { + ccxr_dtvcc_set_active(dec_ctx->dtvcc_rust, 1); + } + } +#endif + + // Use process_cc_data to properly invoke DTVCC decoder for 708 captions + int cc_count = data_node->len / 3; + process_cc_data(enc_ctx, dec_ctx, data_node->buffer, cc_count, dec_sub); + got = data_node->len; } else if (data_node->bufferdatatype == CCX_ISDB_SUBTITLE) { diff --git a/src/rust/src/decoder/mod.rs b/src/rust/src/decoder/mod.rs index 4c9498d9c..ff1439722 100644 --- a/src/rust/src/decoder/mod.rs +++ b/src/rust/src/decoder/mod.rs @@ -515,8 +515,10 @@ impl DtvccRust { } if let Some(decoder) = &mut self.decoders[i] { - if decoder.cc_count > 0 { - // Flush this decoder + // Check if there's content to flush: either cc_count > 0 (already printed) + // or any window has visible content (needs to be printed during flush) + let has_visible_windows = decoder.windows.iter().any(|w| is_true(w.visible)); + if decoder.cc_count > 0 || has_visible_windows { self.flush_decoder(i); } } diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 87e4fa369..7a12d3e9f 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -373,6 +373,20 @@ pub extern "C" fn ccxr_dtvcc_is_active(dtvcc_ptr: *mut std::ffi::c_void) -> i32 } } +/// Enable or disable the DTVCC decoder +/// This allows enabling the decoder after initialization +/// +/// # Safety +/// dtvcc_ptr must be a valid pointer to a DtvccRust struct or null +#[no_mangle] +pub extern "C" fn ccxr_dtvcc_set_active(dtvcc_ptr: *mut std::ffi::c_void, active: i32) { + if dtvcc_ptr.is_null() { + return; + } + let dtvcc = unsafe { &mut *(dtvcc_ptr as *mut DtvccRust) }; + dtvcc.is_active = active != 0; +} + /// Process cc_data /// /// # Safety @@ -402,6 +416,7 @@ extern "C" fn ccxr_process_cc_data( let mut cc_data: Vec = (0..cc_count * 3) .map(|x| unsafe { *data.add(x as usize) }) .collect(); + // Use the persistent DtvccRust context from dtvcc_rust let dtvcc_rust = dec_ctx.dtvcc_rust as *mut DtvccRust; if dtvcc_rust.is_null() {