Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 78 additions & 4 deletions src/lib_ccx/ccx_demuxer_mxf.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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);
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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",
Expand All @@ -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;
Expand Down Expand Up @@ -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
{
Expand All @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions src/lib_ccx/ccx_dtvcc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
30 changes: 29 additions & 1 deletion src/lib_ccx/general_loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down Expand Up @@ -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)
{
Expand Down
6 changes: 4 additions & 2 deletions src/rust/src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -402,6 +416,7 @@ extern "C" fn ccxr_process_cc_data(
let mut cc_data: Vec<u8> = (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() {
Expand Down
Loading