mirror of
https://github.com/CCExtractor/ccextractor.git
synced 2026-07-08 18:06:30 +00:00
This commit fixes two issues uncovered during Sample Platform testing:
1. Variable shadowing in general_loop() (general_loop.c):
- The inner `int ret = process_non_multiprogram_general_loop(...)`
was shadowing the outer `ret` variable
- This caused the return value to always be 0, making ccextractor
report "No captions found" even when captions were extracted
- Also added `ret = 1` when captions are detected via counters,
needed for CEA-708 which writes directly via Rust
2. Missing private_data refresh in update_decoder_list_cinfo (lib_ccx.c):
- After PAT changes, dinit_cap() frees the teletext context and
NULLs dec_ctx->private_data
- But update_decoder_list_cinfo() returned existing decoder without
refreshing private_data from the new cap_info
- This caused all subsequent teletext processing to be skipped
- Fixed by updating dec_ctx->private_data when returning existing decoder
These fixes resolve Sample Platform test failures in CEA-708 and Teletext
categories where tests returned exit code 10 (no captions) unexpectedly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
490 lines
15 KiB
C
490 lines
15 KiB
C
#include "lib_ccx.h"
|
|
#include "ccx_common_option.h"
|
|
#include "activity.h"
|
|
#include "utility.h"
|
|
#include "dvb_subtitle_decoder.h"
|
|
#include "ccx_decoders_708.h"
|
|
#include "ccx_decoders_isdb.h"
|
|
|
|
struct ccx_common_logging_t ccx_common_logging;
|
|
static struct ccx_decoders_common_settings_t *init_decoder_setting(
|
|
struct ccx_s_options *opt)
|
|
{
|
|
struct ccx_decoders_common_settings_t *setting;
|
|
|
|
setting = calloc(1, sizeof(struct ccx_decoders_common_settings_t));
|
|
if (!setting)
|
|
return NULL;
|
|
|
|
setting->subs_delay = opt->subs_delay;
|
|
setting->output_format = opt->write_format;
|
|
setting->fix_padding = opt->fix_padding;
|
|
setting->extract = opt->extract;
|
|
setting->fullbin = opt->fullbin;
|
|
setting->no_rollup = opt->no_rollup;
|
|
setting->noscte20 = opt->noscte20;
|
|
memcpy(&setting->extraction_start, &opt->extraction_start, sizeof(struct ccx_boundary_time));
|
|
memcpy(&setting->extraction_end, &opt->extraction_end, sizeof(struct ccx_boundary_time));
|
|
setting->cc_to_stdout = opt->cc_to_stdout;
|
|
setting->settings_608 = &opt->settings_608;
|
|
setting->settings_dtvcc = &opt->settings_dtvcc;
|
|
setting->cc_channel = opt->cc_channel;
|
|
setting->send_to_srv = opt->send_to_srv;
|
|
setting->hauppauge_mode = opt->hauppauge_mode;
|
|
setting->xds_write_to_file = opt->transcript_settings.xds;
|
|
setting->ocr_quantmode = opt->ocr_quantmode;
|
|
// program_number, codec, and private_data are zero-initialized by calloc
|
|
|
|
return setting;
|
|
}
|
|
static void dinit_decoder_setting(struct ccx_decoders_common_settings_t **setting)
|
|
{
|
|
freep(setting);
|
|
}
|
|
|
|
static int init_ctx_outbase(struct ccx_s_options *opt, struct lib_ccx_ctx *ctx)
|
|
{
|
|
char *file;
|
|
|
|
if (opt->output_filename)
|
|
{
|
|
ctx->basefilename = get_basename(opt->output_filename);
|
|
}
|
|
else
|
|
{
|
|
switch (opt->input_source)
|
|
{
|
|
case CCX_DS_FILE:
|
|
if (!ctx->inputfile || !ctx->inputfile[0])
|
|
{
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
file = ctx->inputfile[0];
|
|
break;
|
|
case CCX_DS_STDIN:
|
|
file = "stdin";
|
|
break;
|
|
case CCX_DS_NETWORK:
|
|
case CCX_DS_TCP:
|
|
file = "network";
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
ctx->basefilename = get_basename(file);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct encoder_ctx *get_encoder_by_pn(struct lib_ccx_ctx *ctx, int pn)
|
|
{
|
|
struct encoder_ctx *enc_ctx;
|
|
list_for_each_entry(enc_ctx, &ctx->enc_ctx_head, list, struct encoder_ctx)
|
|
{
|
|
if (enc_ctx && enc_ctx->program_number == pn)
|
|
return enc_ctx;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct lib_ccx_ctx *init_libraries(struct ccx_s_options *opt)
|
|
{
|
|
int ret = 0;
|
|
|
|
activity_header(); // Brag about writing it :-)
|
|
|
|
// Set logging functions for libraries
|
|
ccx_common_logging.debug_ftn = &dbg_print;
|
|
ccx_common_logging.debug_mask = opt->debug_mask;
|
|
ccx_common_logging.fatal_ftn = &fatal;
|
|
ccx_common_logging.log_ftn = &mprint;
|
|
ccx_common_logging.gui_ftn = &activity_library_process;
|
|
|
|
struct lib_ccx_ctx *ctx = malloc(sizeof(struct lib_ccx_ctx));
|
|
if (!ctx)
|
|
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "init_libraries: Not enough memory allocating lib_ccx_ctx context.");
|
|
memset(ctx, 0, sizeof(struct lib_ccx_ctx));
|
|
|
|
if (opt->xmltv)
|
|
{
|
|
ctx->epg_inited = 1;
|
|
ctx->epg_buffers = (struct PSI_buffer *)malloc(sizeof(struct PSI_buffer) * (0xfff + 1));
|
|
ctx->eit_programs = (struct EIT_program *)malloc(sizeof(struct EIT_program) * (TS_PMT_MAP_SIZE + 1));
|
|
ctx->eit_current_events = (int32_t *)malloc(sizeof(int32_t) * (TS_PMT_MAP_SIZE + 1));
|
|
ctx->ATSC_source_pg_map = (int16_t *)malloc(sizeof(int16_t) * (0xffff));
|
|
if (!ctx->epg_buffers || !ctx->eit_programs || !ctx->eit_current_events || !ctx->ATSC_source_pg_map)
|
|
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "lib_ccx_ctx");
|
|
memset(ctx->epg_buffers, 0, sizeof(struct PSI_buffer) * (0xfff + 1));
|
|
memset(ctx->eit_programs, 0, sizeof(struct EIT_program) * (TS_PMT_MAP_SIZE + 1));
|
|
memset(ctx->eit_current_events, 0, sizeof(int32_t) * (TS_PMT_MAP_SIZE + 1));
|
|
memset(ctx->ATSC_source_pg_map, 0, sizeof(int16_t) * (0xffff));
|
|
}
|
|
else
|
|
{
|
|
ctx->epg_inited = 0;
|
|
ctx->epg_buffers = NULL;
|
|
ctx->eit_programs = NULL;
|
|
ctx->eit_current_events = NULL;
|
|
ctx->ATSC_source_pg_map = NULL;
|
|
}
|
|
|
|
struct ccx_decoder_608_report *report_608 = malloc(sizeof(struct ccx_decoder_608_report));
|
|
if (!report_608)
|
|
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "init_libraries: Not enough memory allocating report_608");
|
|
memset(report_608, 0, sizeof(struct ccx_decoder_608_report));
|
|
|
|
ccx_decoder_dtvcc_report *report_dtvcc = (ccx_decoder_dtvcc_report *)
|
|
malloc(sizeof(ccx_decoder_dtvcc_report));
|
|
if (!report_dtvcc)
|
|
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "init_libraries: Not enough memory allocating report_dtvcc");
|
|
memset(report_dtvcc, 0, sizeof(ccx_decoder_dtvcc_report));
|
|
|
|
// Initialize some constants
|
|
ctx->screens_to_process = -1;
|
|
ctx->current_file = -1;
|
|
|
|
// Init shared decoder settings
|
|
ctx->dec_global_setting = init_decoder_setting(opt);
|
|
if (!ctx->dec_global_setting)
|
|
{
|
|
free(report_608);
|
|
free(report_dtvcc);
|
|
EPG_free(ctx);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
// Need to set the 608 data for the report to the correct variable.
|
|
ctx->freport.data_from_608 = report_608;
|
|
ctx->dec_global_setting->settings_608->report = report_608;
|
|
ctx->freport.data_from_708 = report_dtvcc;
|
|
ctx->dec_global_setting->settings_dtvcc->report = report_dtvcc;
|
|
ctx->mp4_cfg.mp4vidtrack = opt->mp4vidtrack;
|
|
// Initialize input files
|
|
ctx->inputfile = opt->inputfile;
|
|
ctx->num_input_files = opt->num_input_files;
|
|
|
|
ret = init_ctx_outbase(opt, ctx);
|
|
if (ret < 0)
|
|
{
|
|
goto end;
|
|
}
|
|
ctx->extension = get_file_extension(opt->write_format);
|
|
|
|
ctx->subs_delay = opt->subs_delay;
|
|
|
|
ctx->pesheaderbuf = (unsigned char *)malloc(188); // Never larger anyway
|
|
if (!ctx->pesheaderbuf)
|
|
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "init_libraries: Not enough memory allocating pesheaderbuf");
|
|
|
|
ctx->cc_to_stdout = opt->cc_to_stdout;
|
|
ctx->pes_header_to_stdout = opt->pes_header_to_stdout;
|
|
ctx->ignore_pts_jumps = opt->ignore_pts_jumps;
|
|
|
|
ctx->hauppauge_mode = opt->hauppauge_mode;
|
|
ctx->live_stream = opt->live_stream;
|
|
ctx->binary_concat = opt->binary_concat;
|
|
build_parity_table();
|
|
|
|
ctx->demux_ctx = init_demuxer(ctx, &opt->demux_cfg);
|
|
INIT_LIST_HEAD(&ctx->dec_ctx_head);
|
|
INIT_LIST_HEAD(&ctx->enc_ctx_head);
|
|
|
|
// Init timing
|
|
ccx_common_timing_init(&ctx->demux_ctx->past, opt->nosync);
|
|
ctx->multiprogram = opt->multiprogram;
|
|
ctx->write_format = opt->write_format;
|
|
ctx->out_interval = opt->out_interval;
|
|
ctx->segment_on_key_frames_only = opt->segment_on_key_frames_only;
|
|
ctx->segment_counter = 0;
|
|
ctx->system_start_time = -1;
|
|
|
|
end:
|
|
if (ret != EXIT_OK)
|
|
{
|
|
dinit_decoder_setting(&ctx->dec_global_setting);
|
|
free(ctx->freport.data_from_608);
|
|
free(ctx->freport.data_from_708);
|
|
EPG_free(ctx);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
void dinit_libraries(struct lib_ccx_ctx **ctx)
|
|
{
|
|
struct lib_ccx_ctx *lctx = *ctx;
|
|
struct encoder_ctx *enc_ctx;
|
|
struct lib_cc_decode *dec_ctx;
|
|
struct lib_cc_decode *dec_ctx1;
|
|
int i;
|
|
list_for_each_entry_safe(dec_ctx, dec_ctx1, &lctx->dec_ctx_head, list, struct lib_cc_decode)
|
|
{
|
|
LLONG cfts;
|
|
void *saved_private_data = dec_ctx->private_data; // Save before close NULLs it
|
|
if (dec_ctx->codec == CCX_CODEC_DVB)
|
|
dvbsub_close_decoder(&dec_ctx->private_data);
|
|
// Test memory for teletext
|
|
else if (dec_ctx->codec == CCX_CODEC_TELETEXT)
|
|
telxcc_close(&dec_ctx->private_data, &dec_ctx->dec_sub);
|
|
else if (dec_ctx->codec == CCX_CODEC_ISDB_CC)
|
|
delete_isdb_decoder(&dec_ctx->private_data);
|
|
|
|
// Also NULL out any cinfo entries that shared this private_data pointer
|
|
// to prevent double-free in dinit_cap
|
|
if (saved_private_data && lctx->demux_ctx)
|
|
{
|
|
struct cap_info *cinfo_iter;
|
|
list_for_each_entry(cinfo_iter, &lctx->demux_ctx->cinfo_tree.all_stream, all_stream, struct cap_info)
|
|
{
|
|
if (cinfo_iter->codec_private_data == saved_private_data)
|
|
cinfo_iter->codec_private_data = NULL;
|
|
}
|
|
}
|
|
|
|
flush_cc_decode(dec_ctx, &dec_ctx->dec_sub);
|
|
cfts = get_fts(dec_ctx->timing, dec_ctx->current_field);
|
|
enc_ctx = get_encoder_by_pn(lctx, dec_ctx->program_number);
|
|
if (enc_ctx && dec_ctx->dec_sub.got_output == CCX_TRUE)
|
|
{
|
|
encode_sub(enc_ctx, &dec_ctx->dec_sub);
|
|
dec_ctx->dec_sub.got_output = CCX_FALSE;
|
|
}
|
|
list_del(&dec_ctx->list);
|
|
dinit_cc_decode(&dec_ctx);
|
|
if (enc_ctx)
|
|
{
|
|
list_del(&enc_ctx->list);
|
|
dinit_encoder(&enc_ctx, cfts);
|
|
}
|
|
}
|
|
|
|
// free EPG memory
|
|
EPG_free(lctx);
|
|
freep(&lctx->freport.data_from_608);
|
|
freep(&lctx->freport.data_from_708);
|
|
ccx_demuxer_delete(&lctx->demux_ctx);
|
|
dinit_decoder_setting(&lctx->dec_global_setting);
|
|
freep(&ccx_options.enc_cfg.output_filename);
|
|
freep(&lctx->basefilename);
|
|
freep(&lctx->pesheaderbuf);
|
|
for (i = 0; i < lctx->num_input_files; i++)
|
|
freep(&lctx->inputfile[i]);
|
|
freep(&lctx->inputfile);
|
|
freep(ctx);
|
|
}
|
|
|
|
int is_decoder_processed_enough(struct lib_ccx_ctx *ctx)
|
|
{
|
|
struct lib_cc_decode *dec_ctx;
|
|
|
|
// If the decoder list is empty, no user-defined limits could have been reached
|
|
if (list_empty(&ctx->dec_ctx_head))
|
|
return CCX_FALSE;
|
|
|
|
if (ctx->multiprogram == CCX_FALSE)
|
|
{
|
|
// In single-program mode, return TRUE if ANY decoder has processed enough
|
|
list_for_each_entry(dec_ctx, &ctx->dec_ctx_head, list, struct lib_cc_decode)
|
|
{
|
|
if (dec_ctx->processed_enough == CCX_TRUE)
|
|
return CCX_TRUE;
|
|
}
|
|
return CCX_FALSE;
|
|
}
|
|
else
|
|
{
|
|
// In multiprogram mode, return TRUE only if ALL decoders have processed enough
|
|
list_for_each_entry(dec_ctx, &ctx->dec_ctx_head, list, struct lib_cc_decode)
|
|
{
|
|
if (dec_ctx->processed_enough == CCX_FALSE)
|
|
return CCX_FALSE;
|
|
}
|
|
return CCX_TRUE;
|
|
}
|
|
}
|
|
struct lib_cc_decode *update_decoder_list(struct lib_ccx_ctx *ctx)
|
|
{
|
|
struct lib_cc_decode *dec_ctx;
|
|
list_for_each_entry(dec_ctx, &ctx->dec_ctx_head, list, struct lib_cc_decode)
|
|
{
|
|
return dec_ctx;
|
|
}
|
|
|
|
if (list_empty(&ctx->dec_ctx_head))
|
|
{
|
|
ctx->dec_global_setting->codec = CCX_CODEC_ATSC_CC;
|
|
ctx->dec_global_setting->program_number = 0;
|
|
dec_ctx = init_cc_decode(ctx->dec_global_setting);
|
|
if (!dec_ctx)
|
|
fatal(EXIT_NOT_ENOUGH_MEMORY, "In update_decoder_list: Not enough memory to init_cc_decode.\n");
|
|
list_add_tail(&(dec_ctx->list), &(ctx->dec_ctx_head));
|
|
|
|
// DVB related
|
|
dec_ctx->prev = NULL;
|
|
dec_ctx->dec_sub.prev = NULL;
|
|
if (dec_ctx->codec == CCX_CODEC_DVB)
|
|
{
|
|
dec_ctx->prev = malloc(sizeof(struct lib_cc_decode));
|
|
dec_ctx->dec_sub.prev = malloc(sizeof(struct cc_subtitle));
|
|
if (!dec_ctx->prev || !dec_ctx->dec_sub.prev)
|
|
ccx_common_logging.fatal_ftn(EXIT_NOT_ENOUGH_MEMORY, "update_decoder_list: Not enough memory for DVB context");
|
|
memset(dec_ctx->dec_sub.prev, 0, sizeof(struct cc_subtitle));
|
|
}
|
|
}
|
|
return dec_ctx;
|
|
}
|
|
|
|
struct lib_cc_decode *update_decoder_list_cinfo(struct lib_ccx_ctx *ctx, struct cap_info *cinfo)
|
|
{
|
|
struct lib_cc_decode *dec_ctx = NULL;
|
|
|
|
list_for_each_entry(dec_ctx, &ctx->dec_ctx_head, list, struct lib_cc_decode)
|
|
{
|
|
if (!cinfo || ctx->multiprogram == CCX_FALSE)
|
|
{
|
|
/* Update private_data from cinfo if available.
|
|
This is needed after PAT changes when dinit_cap() freed the old context
|
|
and a new cap_info was created with a new codec_private_data. */
|
|
if (cinfo && cinfo->codec_private_data)
|
|
dec_ctx->private_data = cinfo->codec_private_data;
|
|
return dec_ctx;
|
|
}
|
|
|
|
if (dec_ctx->program_number == cinfo->program_number)
|
|
return dec_ctx;
|
|
}
|
|
if (cinfo)
|
|
{
|
|
ctx->dec_global_setting->program_number = cinfo->program_number;
|
|
ctx->dec_global_setting->codec = cinfo->codec;
|
|
ctx->dec_global_setting->private_data = cinfo->codec_private_data;
|
|
}
|
|
else
|
|
{
|
|
ctx->dec_global_setting->program_number = 0;
|
|
ctx->dec_global_setting->codec = CCX_CODEC_ATSC_CC;
|
|
}
|
|
if (ctx->multiprogram == CCX_FALSE)
|
|
{
|
|
if (list_empty(&ctx->dec_ctx_head))
|
|
{
|
|
dec_ctx = init_cc_decode(ctx->dec_global_setting);
|
|
if (!dec_ctx)
|
|
fatal(EXIT_NOT_ENOUGH_MEMORY, "In update_decoder_list_cinfo: Not enough memory allocating dec_ctx (multiprogram == false)\n");
|
|
list_add_tail(&(dec_ctx->list), &(ctx->dec_ctx_head));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dec_ctx = init_cc_decode(ctx->dec_global_setting);
|
|
if (!dec_ctx)
|
|
fatal(EXIT_NOT_ENOUGH_MEMORY, "In update_decoder_list_cinfo: Not enough memory allocating dec_ctx ((multiprogram == true)\n");
|
|
list_add_tail(&(dec_ctx->list), &(ctx->dec_ctx_head));
|
|
}
|
|
|
|
// DVB related
|
|
dec_ctx->prev = NULL;
|
|
dec_ctx->dec_sub.prev = NULL;
|
|
|
|
return dec_ctx;
|
|
}
|
|
|
|
struct encoder_ctx *update_encoder_list_cinfo(struct lib_ccx_ctx *ctx, struct cap_info *cinfo)
|
|
{
|
|
struct encoder_ctx *enc_ctx;
|
|
unsigned int pn = 0;
|
|
unsigned char in_format = 1;
|
|
|
|
if (ctx->write_format == CCX_OF_NULL)
|
|
return NULL;
|
|
|
|
if (cinfo)
|
|
{
|
|
pn = cinfo->program_number;
|
|
if (cinfo->codec == CCX_CODEC_ISDB_CC)
|
|
in_format = 3;
|
|
else if (cinfo->codec == CCX_CODEC_TELETEXT)
|
|
in_format = 2;
|
|
else
|
|
in_format = 1;
|
|
}
|
|
list_for_each_entry(enc_ctx, &ctx->enc_ctx_head, list, struct encoder_ctx)
|
|
{
|
|
if (ctx->multiprogram == CCX_FALSE)
|
|
return enc_ctx;
|
|
|
|
if (enc_ctx->program_number == pn)
|
|
return enc_ctx;
|
|
}
|
|
|
|
const char *extension = get_file_extension(ccx_options.enc_cfg.write_format);
|
|
if (!extension && ccx_options.enc_cfg.write_format != CCX_OF_CURL)
|
|
return NULL;
|
|
|
|
if (ctx->multiprogram == CCX_FALSE)
|
|
{
|
|
if (ctx->out_interval != -1)
|
|
{
|
|
// Format: "%s_%06d%s" needs: basefilename + '_' + up to 10 digits + extension + null
|
|
size_t len = strlen(ctx->basefilename) + 1 + 10 + strlen(extension) + 1;
|
|
|
|
freep(&ccx_options.enc_cfg.output_filename);
|
|
ccx_options.enc_cfg.output_filename = malloc(len);
|
|
if (!ccx_options.enc_cfg.output_filename)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
snprintf(ccx_options.enc_cfg.output_filename, len, "%s_%06d%s", ctx->basefilename, ctx->segment_counter + 1, extension);
|
|
}
|
|
if (list_empty(&ctx->enc_ctx_head))
|
|
{
|
|
ccx_options.enc_cfg.program_number = pn;
|
|
ccx_options.enc_cfg.in_format = in_format;
|
|
enc_ctx = init_encoder(&ccx_options.enc_cfg);
|
|
if (!enc_ctx)
|
|
return NULL;
|
|
list_add_tail(&(enc_ctx->list), &(ctx->enc_ctx_head));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Format: "%s_%d%s" needs: basefilename + '_' + up to 10 digits + extension + null
|
|
size_t len = strlen(ctx->basefilename) + 1 + 10 + strlen(extension) + 1;
|
|
|
|
ccx_options.enc_cfg.program_number = pn;
|
|
ccx_options.enc_cfg.output_filename = malloc(len);
|
|
if (!ccx_options.enc_cfg.output_filename)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
snprintf(ccx_options.enc_cfg.output_filename, len, "%s_%d%s", ctx->basefilename, pn, extension);
|
|
enc_ctx = init_encoder(&ccx_options.enc_cfg);
|
|
if (!enc_ctx)
|
|
{
|
|
freep(&ccx_options.enc_cfg.output_filename);
|
|
return NULL;
|
|
}
|
|
|
|
list_add_tail(&(enc_ctx->list), &(ctx->enc_ctx_head));
|
|
freep(ccx_options.enc_cfg.output_filename);
|
|
}
|
|
// DVB related
|
|
enc_ctx->prev = NULL;
|
|
if (cinfo)
|
|
if (cinfo->codec == CCX_CODEC_DVB)
|
|
enc_ctx->write_previous = 0;
|
|
return enc_ctx;
|
|
}
|
|
|
|
struct encoder_ctx *update_encoder_list(struct lib_ccx_ctx *ctx)
|
|
{
|
|
return update_encoder_list_cinfo(ctx, NULL);
|
|
}
|