diff --git a/src/lib_ccx/mp4_rust_bridge.c b/src/lib_ccx/mp4_rust_bridge.c index 681ad31fc..5558d15e5 100644 --- a/src/lib_ccx/mp4_rust_bridge.c +++ b/src/lib_ccx/mp4_rust_bridge.c @@ -222,14 +222,14 @@ int ccx_mp4_process_cc_packet(struct lib_ccx_ctx *ctx, int sub_type_c608, int ccx_mp4_process_tx3g_packet(struct lib_ccx_ctx *ctx, unsigned char *data, unsigned int data_length, - long long dts, int cts_offset, - unsigned int timescale, + long long start_ms, struct cc_subtitle *sub) { struct lib_cc_decode *dec_ctx = update_decoder_list(ctx); struct encoder_ctx *enc_ctx = update_encoder_list(ctx); - set_current_pts(dec_ctx->timing, (dts + cts_offset) * MPEG_CLOCK_FREQ / timescale); + /* Keep the global timing pointer roughly in sync with this track. */ + set_current_pts(dec_ctx->timing, start_ms * MPEG_CLOCK_FREQ / 1000); set_fts(dec_ctx->timing); /* tx3g format: 16-bit text length + UTF-8 text */ @@ -242,7 +242,7 @@ int ccx_mp4_process_tx3g_packet(struct lib_ccx_ctx *ctx, /* Set up subtitle for deferred encoding */ sub->type = CC_TEXT; - sub->start_time = dec_ctx->timing->fts_now; + sub->start_time = start_ms; if (sub->data != NULL) free(sub->data); sub->data = malloc(text_length + 1); @@ -255,14 +255,13 @@ int ccx_mp4_process_tx3g_packet(struct lib_ccx_ctx *ctx, return 0; } -void ccx_mp4_flush_tx3g(struct lib_ccx_ctx *ctx, struct cc_subtitle *sub) +void ccx_mp4_flush_tx3g(struct lib_ccx_ctx *ctx, struct cc_subtitle *sub, long long end_ms) { - struct lib_cc_decode *dec_ctx = update_decoder_list(ctx); struct encoder_ctx *enc_ctx = update_encoder_list(ctx); if (sub->data != NULL) { - sub->end_time = dec_ctx->timing->fts_now; + sub->end_time = end_ms; encode_sub(enc_ctx, sub); } } diff --git a/src/lib_ccx/mp4_rust_bridge.h b/src/lib_ccx/mp4_rust_bridge.h index 01d4460c2..7345e4ef7 100644 --- a/src/lib_ccx/mp4_rust_bridge.h +++ b/src/lib_ccx/mp4_rust_bridge.h @@ -59,28 +59,30 @@ extern "C" /* * Process a tx3g (3GPP timed text) subtitle sample. * + * Subtitle times are taken explicitly from the packet timestamps instead of + * the global timing pointer, which can be far ahead of this track because + * packets from every track are interleaved. + * * @param ctx CCExtractor library context * @param data tx3g sample data * @param data_length Length of the data - * @param dts Decoding timestamp - * @param cts_offset Composition time offset - * @param timescale Media timescale + * @param start_ms Subtitle start time in milliseconds * @param sub Output subtitle structure * @return 0 on success, non-zero on error */ int ccx_mp4_process_tx3g_packet(struct lib_ccx_ctx *ctx, unsigned char *data, unsigned int data_length, - long long dts, int cts_offset, - unsigned int timescale, + long long start_ms, struct cc_subtitle *sub); /* * Flush pending tx3g subtitle (encode the last one). * - * @param ctx CCExtractor library context - * @param sub Output subtitle structure + * @param ctx CCExtractor library context + * @param sub Output subtitle structure + * @param end_ms Subtitle end time in milliseconds */ - void ccx_mp4_flush_tx3g(struct lib_ccx_ctx *ctx, struct cc_subtitle *sub); + void ccx_mp4_flush_tx3g(struct lib_ccx_ctx *ctx, struct cc_subtitle *sub, long long end_ms); /* * Report progress for the MP4 demuxer. diff --git a/src/rust/src/demuxer/mp4.rs b/src/rust/src/demuxer/mp4.rs index cf0cd4548..fc3151892 100644 --- a/src/rust/src/demuxer/mp4.rs +++ b/src/rust/src/demuxer/mp4.rs @@ -54,13 +54,11 @@ extern "C" { ctx: *mut lib_ccx_ctx, data: *mut u8, data_length: c_uint, - dts: i64, - cts_offset: c_int, - timescale: c_uint, + start_ms: i64, sub: *mut cc_subtitle, ) -> c_int; - fn ccx_mp4_flush_tx3g(ctx: *mut lib_ccx_ctx, sub: *mut cc_subtitle); + fn ccx_mp4_flush_tx3g(ctx: *mut lib_ccx_ctx, sub: *mut cc_subtitle, end_ms: i64); fn ccx_mp4_report_progress(ctx: *mut lib_ccx_ctx, cur: c_uint, total: c_uint); @@ -313,6 +311,8 @@ pub unsafe fn processmp4_rust(ctx: *mut lib_ccx_ctx, path: &CStr, sub: *mut cc_s let mut packet_count: u32 = 0; let mut has_tx3g = false; + let mut prev_tx3g_start_ms: i64 = -1; + let mut prev_tx3g_end_ms: i64 = -1; let mut prev_dvdsub_pts: i64 = -1; let mut prev_dvdsub_data: Vec = Vec::new(); @@ -397,20 +397,36 @@ pub unsafe fn processmp4_rust(ctx: *mut lib_ccx_ctx, path: &CStr, sub: *mut cc_s } TrackType::Tx3g => { if pkt.size > 0 && !pkt.data.is_null() { + // Subtitle times are taken from the packet timestamps directly: + // the global timing pointer may already be past this subtitle + // because packets from every track are interleaved. + let start_ms = pts * 1000 / timescale as i64; + let end_ms = if pkt.duration > 0 { + start_ms + pkt.duration * 1000 / timescale as i64 + } else { + -1 + }; if has_tx3g { - ccx_mp4_flush_tx3g(ctx, sub); + // Fall back to the new subtitle's start when the previous + // sample had no duration information. + let flush_end_ms = if prev_tx3g_end_ms >= 0 { + prev_tx3g_end_ms + } else { + start_ms + }; + ccx_mp4_flush_tx3g(ctx, sub, flush_end_ms); } let r = ccx_mp4_process_tx3g_packet( ctx, pkt.data, pkt.size as c_uint, - dts, - cts_offset, - timescale, + start_ms, sub, ); if r == 0 { has_tx3g = true; + prev_tx3g_start_ms = start_ms; + prev_tx3g_end_ms = end_ms; mp4_ret = 1; } } @@ -459,7 +475,13 @@ pub unsafe fn processmp4_rust(ctx: *mut lib_ccx_ctx, path: &CStr, sub: *mut cc_s // Flush last tx3g subtitle if has_tx3g { - ccx_mp4_flush_tx3g(ctx, sub); + let end_ms = if prev_tx3g_end_ms >= 0 { + prev_tx3g_end_ms + } else { + // Match the 5 second default used for the other subtitle paths. + prev_tx3g_start_ms + 5000 + }; + ccx_mp4_flush_tx3g(ctx, sub, end_ms); } // Flush last dvdsub packet (use 5s default duration)