diff --git a/src/lib_ccx/ccx_decoders_708_output.c b/src/lib_ccx/ccx_decoders_708_output.c index bda39e056..05c91d484 100644 --- a/src/lib_ccx/ccx_decoders_708_output.c +++ b/src/lib_ccx/ccx_decoders_708_output.c @@ -538,6 +538,36 @@ void dtvcc_write_scc(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, s // when hiding subtract a frame (1 frame = 34 ms) struct ccx_boundary_time time_end = get_time(tv->time_ms_hide + encoder->subs_delay - 34); + /* ---- SCC accurate timing: byte budgeting ---- */ + int bytes_required = 0; + + /* Control codes: RCL + ENM + EOC (each = 2 bytes) */ + bytes_required += 6; + + for (int i = 0; i < CCX_DTVCC_SCREENGRID_ROWS; i++) + { + if (!dtvcc_is_row_empty(tv, i)) + { + int first, last; + dtvcc_get_write_interval(tv, i, &first, &last); + bytes_required += (last - first + 1); + } + } + + /* Pad to even number of bytes */ + if (bytes_required % 2 != 0) + bytes_required++; + + /* ---- SCC accurate timing: scheduling ---- */ + int frames_required = (bytes_required + 1) / 2; + int tx_duration_ms = (int)(frames_required * (1000.0 / 29.97)); + + time_show.time_in_ms -= tx_duration_ms; + + /* ---- SCC accurate timing: collision handling ---- */ + if (encoder->last_scc_tx_end_ms >= 0 && time_show.time_in_ms < encoder->last_scc_tx_end_ms) + time_show.time_in_ms = encoder->last_scc_tx_end_ms; + #define SCC_SNPRINTF(fmt, ...) \ do \ { \ @@ -614,6 +644,7 @@ void dtvcc_write_scc(dtvcc_writer_ctx *writer, dtvcc_service_decoder *decoder, s #undef SCC_SNPRINTF write_wrapped(encoder->dtvcc_writers[tv->service_number - 1].fd, buf, strlen(buf)); + encoder->last_scc_tx_end_ms = tv->time_ms_show + encoder->subs_delay; tv->old_cc_time_end = time_end.time_in_ms; } diff --git a/src/lib_ccx/ccx_encoders_common.c b/src/lib_ccx/ccx_encoders_common.c index ac2af699a..916670ded 100644 --- a/src/lib_ccx/ccx_encoders_common.c +++ b/src/lib_ccx/ccx_encoders_common.c @@ -829,6 +829,7 @@ struct encoder_ctx *init_encoder(struct encoder_cfg *opt) ctx->dtvcc_extract = opt->dtvcc_extract; + ctx->last_scc_tx_end_ms = -1; ctx->segment_pending = 0; ctx->segment_last_key_frame = 0; ctx->nospupngocr = opt->nospupngocr; diff --git a/src/lib_ccx/ccx_encoders_common.h b/src/lib_ccx/ccx_encoders_common.h index 4540de681..e3d70e73a 100644 --- a/src/lib_ccx/ccx_encoders_common.h +++ b/src/lib_ccx/ccx_encoders_common.h @@ -157,6 +157,8 @@ struct encoder_ctx int sbs_enabled; // for dvb subs + + int last_scc_tx_end_ms; struct encoder_ctx *prev; int write_previous; // for dvb in .mkv diff --git a/src/lib_ccx/ccx_encoders_scc.c b/src/lib_ccx/ccx_encoders_scc.c index fdeca183b..edaa876ea 100644 --- a/src/lib_ccx/ccx_encoders_scc.c +++ b/src/lib_ccx/ccx_encoders_scc.c @@ -550,8 +550,44 @@ int write_cc_buffer_as_scenarist(const struct eia608_screen *data, struct encode unsigned char current_row = UINT8_MAX; unsigned char current_column = UINT8_MAX; + /* ---- SCC accurate timing: byte budgeting ---- */ + int bytes_required = 0; + + /* Control codes: RCL + ENM + EOC (each = 2 bytes) */ + bytes_required += 6; + + /* Count text bytes and control codes needed */ + for (uint8_t row = 0; row < 15; ++row) + { + if (!data->row_used[row]) + continue; + + int first, last; + find_limit_characters(data->characters[row], &first, &last, CCX_DECODER_608_SCREEN_WIDTH); + + /* Preamble code (2 bytes) + potential tab offset (2 bytes) */ + bytes_required += 4; + + /* Text characters */ + bytes_required += (last - first + 1); + } + + /* Pad to even number of bytes */ + if (bytes_required % 2 != 0) + bytes_required++; + + /* ---- SCC accurate timing: scheduling ---- */ + int frames_required = (bytes_required + 1) / 2; + int tx_duration_ms = (int)(frames_required * (1000.0 / 29.97)); + + LLONG adjusted_start_time = data->start_time - tx_duration_ms; + + /* ---- SCC accurate timing: collision handling ---- */ + if (context->last_scc_tx_end_ms >= 0 && adjusted_start_time < context->last_scc_tx_end_ms) + adjusted_start_time = context->last_scc_tx_end_ms; + // 1. Load the caption - add_timestamp(context, data->start_time, disassemble); + add_timestamp(context, adjusted_start_time, disassemble); write_control_code(context->out->fh, data->channel, RCL, disassemble, &bytes_written); for (uint8_t row = 0; row < 15; ++row) { @@ -624,6 +660,8 @@ int write_cc_buffer_as_scenarist(const struct eia608_screen *data, struct encode // 3. Clear the caption clear_screen(context, data->end_time, data->channel, disassemble); + /* Track transmission end time */ + context->last_scc_tx_end_ms = adjusted_start_time + tx_duration_ms; return 1; }