Skip to content

Commit ddf2967

Browse files
cfsmp3claude
andcommitted
Fix MXF files containing CEA-708 captions not being detected/extracted
Root cause: CCX_RAW_TYPE data from MXF demuxer was not being passed to the DTVCC decoder, only to the legacy 608 decoder via process_raw_with_field. Changes: - general_loop.c: Changed CCX_RAW_TYPE handling to use process_cc_data instead of process_raw_with_field to properly invoke DTVCC decoder - general_loop.c: Added DTVCC activation for MXF/GXF sources since they may contain 708 captions - general_loop.c: Initialize timing from caption PTS when not set - ccx_dtvcc.h: Added ccxr_dtvcc_set_active FFI declaration - lib.rs: Added ccxr_dtvcc_set_active function to enable DTVCC decoder - decoder/mod.rs: Fixed flush logic to always process visible windows - ccx_demuxer_mxf.c: Fixed PTS calculation to use 90kHz units based on edit_rate, and changed verbose logging to debug() Fixes #1647 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8c33412 commit ddf2967

File tree

5 files changed

+130
-7
lines changed

5 files changed

+130
-7
lines changed

src/lib_ccx/ccx_demuxer_mxf.c

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,15 @@ enum MXFLocalTag
7575
void update_tid_lut(struct MXFContext *ctx, uint32_t track_id, uint8_t *track_number, struct ccx_rational edit_rate)
7676
{
7777
int i;
78+
debug("update_tid_lut: track_id=%u (0x%x), track_number=%02X%02X%02X%02X, cap_track_id=%u\n",
79+
track_id, track_id, track_number[0], track_number[1], track_number[2], track_number[3], ctx->cap_track_id);
7880
// Update essence element key if we have track Id of caption
7981
if (ctx->cap_track_id == track_id)
8082
{
8183
memcpy(ctx->cap_essence_key, mxf_essence_element_key, 12);
8284
memcpy(ctx->cap_essence_key + 12, track_number, 4);
8385
ctx->edit_rate = edit_rate;
86+
debug("MXF: Found caption track, track_id=%u\n", track_id);
8487
}
8588

8689
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)
248251
{
249252
case MXF_TAG_LTRACK_ID:
250253
ctx->cap_track_id = buffered_get_be32(demux);
254+
debug("MXF: VANC/VBI descriptor found, Linked Track ID = %u\n", ctx->cap_track_id);
251255
update_cap_essence_key(ctx, ctx->cap_track_id);
252256
break;
253257
default:
@@ -304,6 +308,17 @@ static int mxf_read_cdp_data(struct ccx_demuxer *demux, int size, struct demuxer
304308
log("Incomplete CDP packet\n");
305309

306310
ret = buffered_read(demux, data->buffer + data->len, cc_count * 3);
311+
// Log first few bytes of cc_data for debugging
312+
if (cc_count > 0)
313+
{
314+
unsigned char *cc_ptr = data->buffer + data->len;
315+
debug("cc_data (first 6 triplets): ");
316+
for (int j = 0; j < (cc_count < 6 ? cc_count : 6); j++)
317+
{
318+
debug("%02X%02X%02X ", cc_ptr[j * 3], cc_ptr[j * 3 + 1], cc_ptr[j * 3 + 2]);
319+
}
320+
debug("\n");
321+
}
307322
data->len += cc_count * 3;
308323
demux->past += cc_count * 3;
309324
len += ret;
@@ -361,7 +376,10 @@ static int mxf_read_vanc_data(struct ccx_demuxer *demux, uint64_t size, struct d
361376
// uint8_t count; /* Currently unused */
362377

363378
if (size < 19)
379+
{
380+
debug("VANC data too small: %" PRIu64 " < 19\n", size);
364381
goto error;
382+
}
365383

366384
ret = buffered_read(demux, vanc_header, 16);
367385

@@ -370,31 +388,39 @@ static int mxf_read_vanc_data(struct ccx_demuxer *demux, uint64_t size, struct d
370388
return CCX_EOF;
371389
len += ret;
372390

391+
debug("VANC header: num_packets=%d, line=0x%02x%02x, wrap_type=0x%02x, sample_config=0x%02x\n",
392+
vanc_header[1], vanc_header[2], vanc_header[3], vanc_header[4], vanc_header[5]);
393+
373394
for (int i = 0; i < vanc_header[1]; i++)
374395
{
375396
DID = buffered_get_byte(demux);
376397
len++;
398+
debug("VANC packet %d: DID=0x%02x\n", i, DID);
377399
if (!(DID == 0x61 || DID == 0x80))
378400
{
401+
debug("DID 0x%02x not recognized as caption DID\n", DID);
379402
goto error;
380403
}
381404

382405
SDID = buffered_get_byte(demux);
383406
len++;
407+
debug("VANC packet %d: SDID=0x%02x\n", i, SDID);
384408
if (SDID == 0x01)
385409
debug("Caption Type 708\n");
386410
else if (SDID == 0x02)
387411
debug("Caption Type 608\n");
388412

389413
cdp_size = buffered_get_byte(demux);
414+
debug("VANC packet %d: cdp_size=%d\n", i, cdp_size);
390415
if (cdp_size + 19 > size)
391416
{
392-
debug("Incomplete cdp(%d) in anc data(%d)\n", cdp_size, size);
417+
log("Incomplete cdp(%d) in anc data(%" PRIu64 ")\n", cdp_size, size);
393418
goto error;
394419
}
395420
len++;
396421

397422
ret = mxf_read_cdp_data(demux, cdp_size, data);
423+
debug("mxf_read_cdp_data returned %d, data->len=%d\n", ret, data->len);
398424
len += ret;
399425
// len += (3 + count + 4);
400426
}
@@ -411,15 +437,33 @@ static int mxf_read_essence_element(struct ccx_demuxer *demux, uint64_t size, st
411437
int ret;
412438
struct MXFContext *ctx = demux->private_data;
413439

440+
debug("mxf_read_essence_element: ctx->type=%d (ANC=%d, VBI=%d), size=%" PRIu64 "\n",
441+
ctx->type, MXF_CT_ANC, MXF_CT_VBI, size);
442+
414443
if (ctx->type == MXF_CT_ANC)
415444
{
416445
data->bufferdatatype = CCX_RAW_TYPE;
417446
ret = mxf_read_vanc_data(demux, size, data);
418-
data->pts = ctx->cap_count;
447+
debug("mxf_read_vanc_data returned %d, data->len=%d\n", ret, data->len);
448+
// Calculate PTS in 90kHz units from frame count and edit rate
449+
// edit_rate is frames per second (e.g., 25/1 for 25fps)
450+
// PTS = frame_count * 90000 / fps = frame_count * 90000 * edit_rate.den / edit_rate.num
451+
if (ctx->edit_rate.num > 0 && ctx->edit_rate.den > 0)
452+
{
453+
data->pts = (int64_t)ctx->cap_count * 90000 * ctx->edit_rate.den / ctx->edit_rate.num;
454+
}
455+
else
456+
{
457+
// Fallback to 25fps if edit_rate not set
458+
data->pts = (int64_t)ctx->cap_count * 90000 / 25;
459+
}
460+
debug("Frame %d, PTS=%" PRId64 " (edit_rate=%d/%d)\n",
461+
ctx->cap_count, data->pts, ctx->edit_rate.num, ctx->edit_rate.den);
419462
ctx->cap_count++;
420463
}
421464
else
422465
{
466+
debug("Skipping essence element (not ANC type)\n");
423467
ret = buffered_skip(demux, size);
424468
demux->past += ret;
425469
}
@@ -514,6 +558,7 @@ static int read_packet(struct ccx_demuxer *demux, struct demuxer_data *data)
514558
KLVPacket klv;
515559
const MXFReadTableEntry *reader;
516560
struct MXFContext *ctx = demux->private_data;
561+
static int first_essence_logged = 0;
517562
while ((ret = klv_read_packet(&klv, demux)) == 0)
518563
{
519564
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)
523568
klv.key[12], klv.key[13], klv.key[14], klv.key[15],
524569
klv.length);
525570

571+
// Check if this is an essence element key (first 12 bytes match)
572+
if (IS_KLV_KEY(klv.key, mxf_essence_element_key) && !first_essence_logged)
573+
{
574+
debug("MXF: First essence element key: %02X%02X%02X%02X%02X%02X%02X%02X.%02X%02X%02X%02X%02X%02X%02X%02X\n",
575+
klv.key[0], klv.key[1], klv.key[2], klv.key[3],
576+
klv.key[4], klv.key[5], klv.key[6], klv.key[7],
577+
klv.key[8], klv.key[9], klv.key[10], klv.key[11],
578+
klv.key[12], klv.key[13], klv.key[14], klv.key[15]);
579+
debug("MXF: cap_essence_key: %02X%02X%02X%02X%02X%02X%02X%02X.%02X%02X%02X%02X%02X%02X%02X%02X\n",
580+
ctx->cap_essence_key[0], ctx->cap_essence_key[1], ctx->cap_essence_key[2], ctx->cap_essence_key[3],
581+
ctx->cap_essence_key[4], ctx->cap_essence_key[5], ctx->cap_essence_key[6], ctx->cap_essence_key[7],
582+
ctx->cap_essence_key[8], ctx->cap_essence_key[9], ctx->cap_essence_key[10], ctx->cap_essence_key[11],
583+
ctx->cap_essence_key[12], ctx->cap_essence_key[13], ctx->cap_essence_key[14], ctx->cap_essence_key[15]);
584+
first_essence_logged = 1;
585+
}
586+
526587
if (IS_KLV_KEY(klv.key, ctx->cap_essence_key))
527588
{
589+
debug("MXF: Found ANC essence element, size=%" PRIu64 "\n", klv.length);
528590
mxf_read_essence_element(demux, klv.length, data);
529591
if (data->len > 0)
530592
break;
@@ -566,8 +628,15 @@ int ccx_mxf_getmoredata(struct lib_ccx_ctx *ctx, struct demuxer_data **ppdata)
566628
data->program_number = 1;
567629
data->stream_pid = 1;
568630
data->codec = CCX_CODEC_ATSC_CC;
569-
data->tb.num = 1001;
570-
data->tb.den = 30000;
631+
// PTS is already calculated in 90kHz units by mxf_read_essence_element
632+
data->tb.num = 1;
633+
data->tb.den = 90000;
634+
635+
// Enable CEA-708 (DTVCC) decoder for MXF files with VANC captions
636+
if (ctx->dec_global_setting && ctx->dec_global_setting->settings_dtvcc)
637+
{
638+
ctx->dec_global_setting->settings_dtvcc->enabled = 1;
639+
}
571640
}
572641
else
573642
{
@@ -576,6 +645,11 @@ int ccx_mxf_getmoredata(struct lib_ccx_ctx *ctx, struct demuxer_data **ppdata)
576645

577646
ret = read_packet(ctx->demux_ctx, data);
578647

648+
// Ensure timebase is 90kHz since PTS is calculated in 90kHz units
649+
// CDP parsing may have set a frame-based timebase which would cause incorrect conversion
650+
data->tb.num = 1;
651+
data->tb.den = 90000;
652+
579653
return ret;
580654
}
581655

src/lib_ccx/ccx_dtvcc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ extern void ccxr_dtvcc_free(void *dtvcc_rust);
1717
extern void ccxr_dtvcc_process_data(void *dtvcc_rust, const unsigned char cc_valid,
1818
const unsigned char cc_type, const unsigned char data1, const unsigned char data2);
1919
extern int ccxr_dtvcc_is_active(void *dtvcc_rust);
20+
extern void ccxr_dtvcc_set_active(void *dtvcc_rust, int active);
2021
#endif
2122

2223
#endif // CCEXTRACTOR_CCX_DTVCC_H

src/lib_ccx/general_loop.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "ccx_gxf.h"
1919
#include "dvd_subtitle_decoder.h"
2020
#include "ccx_demuxer_mxf.h"
21+
#include "ccx_dtvcc.h"
2122

2223
int end_of_file = 0; // End of file?
2324

@@ -861,7 +862,34 @@ int process_data(struct encoder_ctx *enc_ctx, struct lib_cc_decode *dec_ctx, str
861862
}
862863
else if (data_node->bufferdatatype == CCX_RAW_TYPE)
863864
{
864-
got = process_raw_with_field(dec_ctx, dec_sub, data_node->buffer, data_node->len);
865+
// CCX_RAW_TYPE contains cc_data triplets (cc_type + 2 data bytes each)
866+
// Used by MXF and GXF demuxers
867+
868+
// Initialize timing if not set (use caption PTS as reference)
869+
if (dec_ctx->timing->pts_set == 0 && data_node->pts != CCX_NOPTS)
870+
{
871+
dec_ctx->timing->min_pts = data_node->pts;
872+
dec_ctx->timing->pts_set = 2; // MinPtsSet
873+
dec_ctx->timing->sync_pts = data_node->pts;
874+
set_fts(dec_ctx->timing);
875+
}
876+
877+
#ifndef DISABLE_RUST
878+
// Enable DTVCC decoder for CEA-708 captions from MXF/GXF
879+
if (dec_ctx->dtvcc_rust)
880+
{
881+
int is_active = ccxr_dtvcc_is_active(dec_ctx->dtvcc_rust);
882+
if (!is_active)
883+
{
884+
ccxr_dtvcc_set_active(dec_ctx->dtvcc_rust, 1);
885+
}
886+
}
887+
#endif
888+
889+
// Use process_cc_data to properly invoke DTVCC decoder for 708 captions
890+
int cc_count = data_node->len / 3;
891+
process_cc_data(enc_ctx, dec_ctx, data_node->buffer, cc_count, dec_sub);
892+
got = data_node->len;
865893
}
866894
else if (data_node->bufferdatatype == CCX_ISDB_SUBTITLE)
867895
{

src/rust/src/decoder/mod.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,8 +515,13 @@ impl DtvccRust {
515515
}
516516

517517
if let Some(decoder) = &mut self.decoders[i] {
518-
if decoder.cc_count > 0 {
519-
// Flush this decoder
518+
// Check if there's content to flush: either cc_count > 0 (already printed)
519+
// or any window has visible content (needs to be printed during flush)
520+
let has_visible_windows = decoder
521+
.windows
522+
.iter()
523+
.any(|w| is_true(w.visible));
524+
if decoder.cc_count > 0 || has_visible_windows {
520525
self.flush_decoder(i);
521526
}
522527
}

src/rust/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,20 @@ pub extern "C" fn ccxr_dtvcc_is_active(dtvcc_ptr: *mut std::ffi::c_void) -> i32
373373
}
374374
}
375375

376+
/// Enable or disable the DTVCC decoder
377+
/// This allows enabling the decoder after initialization
378+
///
379+
/// # Safety
380+
/// dtvcc_ptr must be a valid pointer to a DtvccRust struct or null
381+
#[no_mangle]
382+
pub extern "C" fn ccxr_dtvcc_set_active(dtvcc_ptr: *mut std::ffi::c_void, active: i32) {
383+
if dtvcc_ptr.is_null() {
384+
return;
385+
}
386+
let dtvcc = unsafe { &mut *(dtvcc_ptr as *mut DtvccRust) };
387+
dtvcc.is_active = active != 0;
388+
}
389+
376390
/// Process cc_data
377391
///
378392
/// # Safety
@@ -402,6 +416,7 @@ extern "C" fn ccxr_process_cc_data(
402416
let mut cc_data: Vec<u8> = (0..cc_count * 3)
403417
.map(|x| unsafe { *data.add(x as usize) })
404418
.collect();
419+
405420
// Use the persistent DtvccRust context from dtvcc_rust
406421
let dtvcc_rust = dec_ctx.dtvcc_rust as *mut DtvccRust;
407422
if dtvcc_rust.is_null() {

0 commit comments

Comments
 (0)