From 403984457c38d1811bdb28a550db594cfd95d143 Mon Sep 17 00:00:00 2001 From: Dhanush Varma Date: Fri, 6 Mar 2026 11:53:44 +0530 Subject: [PATCH 1/2] feat: replace GPAC with FFmpeg for MP4 demuxing Add FFmpeg-based MP4 demuxer as alternative to GPAC, enabled via -DWITH_FFMPEG=ON (sets ENABLE_FFMPEG_MP4). Uses avformat_open_input, av_read_frame, and extradata parsing instead of libgpac. Supports AVC, HEVC, tx3g, c608, c708, and VOBSUB tracks. Existing GPAC path unchanged when FFmpeg is not enabled. Also adds libswresample to CMake FFmpeg dependencies. --- docs/CHANGES.TXT | 1 + src/CMakeLists.txt | 7 + src/ccextractor.c | 2 +- src/lib_ccx/CMakeLists.txt | 1 + src/lib_ccx/ccx_mp4.h | 6 + src/lib_ccx/mp4.c | 883 +++++++++++++++++++++++++++++++++++-- 6 files changed, 857 insertions(+), 43 deletions(-) diff --git a/docs/CHANGES.TXT b/docs/CHANGES.TXT index ad8c0995f..37dcb0598 100644 --- a/docs/CHANGES.TXT +++ b/docs/CHANGES.TXT @@ -1,5 +1,6 @@ 0.96.7 (unreleased) ------------------- +- Feature: Added FFmpeg-based MP4 demuxing as alternative to GPAC (enabled with -DWITH_FFMPEG=ON). - Fix: Prevent crash in Rust timing module when logging out-of-range PTS/FTS timestamps from malformed streams. 0.96.6 (2026-02-19) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16736731f..bb446598f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -175,20 +175,24 @@ if (PKG_CONFIG_FOUND AND WITH_FFMPEG) pkg_check_modules (AVCODEC REQUIRED libavcodec) pkg_check_modules (AVFILTER REQUIRED libavfilter) pkg_check_modules (SWSCALE REQUIRED libswscale) + pkg_check_modules (SWRESAMPLE REQUIRED libswresample) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVFORMAT_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVUTIL_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVCODEC_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVFILTER_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${SWSCALE_LIBRARIES}) + set (EXTRA_LIBS ${EXTRA_LIBS} ${SWRESAMPLE_LIBRARIES}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVFORMAT_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVUTIL_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVCODEC_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVFILTER_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${SWSCALE_INCLUDE_DIRS}) + set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${SWRESAMPLE_INCLUDE_DIRS}) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_FFMPEG") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_FFMPEG_MP4") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_HARDSUBX") endif (PKG_CONFIG_FOUND AND WITH_FFMPEG) @@ -219,18 +223,21 @@ if (PKG_CONFIG_FOUND AND WITH_HARDSUBX) pkg_check_modules (AVCODEC REQUIRED libavcodec) pkg_check_modules (AVFILTER REQUIRED libavfilter) pkg_check_modules (SWSCALE REQUIRED libswscale) + pkg_check_modules (SWRESAMPLE REQUIRED libswresample) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVFORMAT_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVUTIL_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVCODEC_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${AVFILTER_LIBRARIES}) set (EXTRA_LIBS ${EXTRA_LIBS} ${SWSCALE_LIBRARIES}) + set (EXTRA_LIBS ${EXTRA_LIBS} ${SWRESAMPLE_LIBRARIES}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVFORMAT_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVUTIL_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVCODEC_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${AVFILTER_INCLUDE_DIRS}) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${SWSCALE_INCLUDE_DIRS}) + set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${SWRESAMPLE_INCLUDE_DIRS}) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_HARDSUBX") pkg_check_modules (TESSERACT REQUIRED tesseract) diff --git a/src/ccextractor.c b/src/ccextractor.c index e8d2cffa5..4b766dde4 100644 --- a/src/ccextractor.c +++ b/src/ccextractor.c @@ -222,7 +222,7 @@ int start_ccx() ret = tmp; break; case CCX_SM_MP4: - mprint("\rAnalyzing data with GPAC (MP4 library)\n"); + mprint("\rAnalyzing data in MP4 mode\n"); close_input_file(ctx); // No need to have it open. GPAC will do it for us if (ctx->current_file == -1) // We don't have a file to open, must be stdin, and GPAC is incompatible with stdin { diff --git a/src/lib_ccx/CMakeLists.txt b/src/lib_ccx/CMakeLists.txt index a891560b8..b3197cbc7 100644 --- a/src/lib_ccx/CMakeLists.txt +++ b/src/lib_ccx/CMakeLists.txt @@ -38,6 +38,7 @@ if (WITH_FFMPEG) set (EXTRA_INCLUDES ${EXTRA_INCLUDES} ${SWSCALE_INCLUDE_DIRS}) set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_FFMPEG") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_FFMPEG_MP4") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_HARDSUBX") endif (WITH_FFMPEG) diff --git a/src/lib_ccx/ccx_mp4.h b/src/lib_ccx/ccx_mp4.h index 51331b55d..594236e3e 100644 --- a/src/lib_ccx/ccx_mp4.h +++ b/src/lib_ccx/ccx_mp4.h @@ -3,4 +3,10 @@ int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file); int dumpchapters(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file); + +#ifdef ENABLE_FFMPEG_MP4 +int processmp4_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file); +int dumpchapters_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file); +#endif + #endif diff --git a/src/lib_ccx/mp4.c b/src/lib_ccx/mp4.c index 48e20e842..5a314ad89 100644 --- a/src/lib_ccx/mp4.c +++ b/src/lib_ccx/mp4.c @@ -2,8 +2,40 @@ #include #include +#ifndef ENABLE_FFMPEG_MP4 #include #include +#else +/* GPAC type stubs so shared functions compile without GPAC headers */ +typedef uint32_t u32; +typedef int32_t s32; +typedef int64_t s64; +typedef double Double; +typedef uint8_t u8; +typedef uint64_t u64; +typedef struct +{ + char *data; + uint32_t dataLength; + uint64_t DTS; + uint32_t CTS_Offset; +} GF_ISOSample; +typedef struct +{ + uint8_t nal_unit_size; +} GF_AVCConfig; +typedef struct +{ + uint8_t nal_unit_size; +} GF_HEVCConfig; +typedef void GF_ISOFile; +typedef void GF_GenericSampleDescription; +#include +#define GF_4CC(a, b, c, d) ((((u32)(a)) << 24) | (((u32)(b)) << 16) | (((u32)(c)) << 8) | ((u32)(d))) +#include +#include +#include +#endif #include "lib_ccx.h" #include "utility.h" #include "ccx_encoders_common.h" @@ -200,6 +232,7 @@ static int process_hevc_sample(struct lib_ccx_ctx *ctx, u32 timescale, GF_HEVCCo return status; } +#ifndef ENABLE_FFMPEG_MP4 static int process_xdvb_track(struct lib_ccx_ctx *ctx, const char *basename, GF_ISOFile *f, u32 track, struct cc_subtitle *sub) { u32 timescale, i, sample_count; @@ -254,7 +287,9 @@ static int process_xdvb_track(struct lib_ccx_ctx *ctx, const char *basename, GF_ return status; } +#endif // !ENABLE_FFMPEG_MP4 +#ifndef ENABLE_FFMPEG_MP4 static int process_avc_track(struct lib_ccx_ctx *ctx, const char *basename, GF_ISOFile *f, u32 track, struct cc_subtitle *sub) { u32 timescale, i, sample_count, last_sdi = 0; @@ -334,7 +369,9 @@ static int process_avc_track(struct lib_ccx_ctx *ctx, const char *basename, GF_I return status; } +#endif // !ENABLE_FFMPEG_MP4 +#ifndef ENABLE_FFMPEG_MP4 static int process_hevc_track(struct lib_ccx_ctx *ctx, const char *basename, GF_ISOFile *f, u32 track, struct cc_subtitle *sub) { u32 timescale, i, sample_count, last_sdi = 0; @@ -417,7 +454,9 @@ static int process_hevc_track(struct lib_ccx_ctx *ctx, const char *basename, GF_ return status; } +#endif // !ENABLE_FFMPEG_MP4 +#ifndef ENABLE_FFMPEG_MP4 static int process_vobsub_track(struct lib_ccx_ctx *ctx, GF_ISOFile *f, u32 track, struct cc_subtitle *sub) { u32 timescale, i, sample_count; @@ -555,6 +594,7 @@ static int process_vobsub_track(struct lib_ccx_ctx *ctx, GF_ISOFile *f, u32 trac return status; } +#endif // !ENABLE_FFMPEG_MP4 static char *format_duration(u64 dur, u32 timescale, char *szDur, size_t szDur_size) { @@ -877,8 +917,748 @@ static int process_tx3g(struct lib_ccx_ctx *ctx, struct encoder_ctx *enc_ctx, } */ +#ifdef ENABLE_FFMPEG_MP4 +#define CCX_4CC(a, b, c, d) (((uint32_t)(a) << 24) | ((uint32_t)(b) << 16) | ((uint32_t)(c) << 8) | (uint32_t)(d)) +#define CCX_MEDIA_VISUAL CCX_4CC('v', 'i', 'd', 'e') +#define CCX_MEDIA_CLOSED_CAPTION CCX_4CC('c', 'l', 'c', 'p') +#define CCX_MEDIA_TEXT CCX_4CC('t', 'e', 'x', 't') +#define CCX_MEDIA_SUBT CCX_4CC('s', 'b', 't', 'l') +#define CCX_MEDIA_SUBPIC CCX_4CC('s', 'u', 'b', 'p') +#define CCX_SUBTYPE_AVC_H264 CCX_4CC('a', 'v', 'c', '1') +#define CCX_SUBTYPE_HEV1 CCX_4CC('h', 'e', 'v', '1') +#define CCX_SUBTYPE_HVC1 CCX_4CC('h', 'v', 'c', '1') +#define CCX_SUBTYPE_XDVB CCX_4CC('x', 'd', 'v', 'b') +#define CCX_SUBTYPE_MPEG4 CCX_4CC('M', 'P', 'E', 'G') +#define CCX_SUBTYPE_C608 CCX_4CC('c', '6', '0', '8') +#define CCX_SUBTYPE_C708 CCX_4CC('c', '7', '0', '8') +#define CCX_SUBTYPE_TX3G CCX_4CC('t', 'x', '3', 'g') +#define CCX_MEDIA_TYPE(t, s) (((uint64_t)(t) << 32) | (uint64_t)(s)) + +typedef struct +{ + char *data; + uint32_t dataLength; + uint64_t DTS; + uint32_t CTS_Offset; +} CCX_ISOSample; + +typedef struct +{ + uint8_t nal_unit_size; +} CCX_AVCConfig; +typedef struct +{ + uint8_t nal_unit_size; +} CCX_HEVCConfig; + +typedef struct +{ + AVPacket **pkts; + int count; + int capacity; +} PacketStore; + +static PacketStore *packet_store_new(void) +{ + return calloc(1, sizeof(PacketStore)); +} + +static int packet_store_push(PacketStore *ps, AVPacket *pkt) +{ + if (ps->count == ps->capacity) + { + int nc = ps->capacity ? ps->capacity * 2 : 64; + AVPacket **tmp = realloc(ps->pkts, nc * sizeof(AVPacket *)); + if (!tmp) + return -1; + ps->pkts = tmp; + ps->capacity = nc; + } + ps->pkts[ps->count++] = pkt; + return 0; +} + +static void packet_store_free(PacketStore *ps) +{ + if (!ps) + return; + for (int i = 0; i < ps->count; i++) + av_packet_free(&ps->pkts[i]); + free(ps->pkts); + free(ps); +} + +static void ffmpeg_stream_to_type_subtype(AVStream *stream, uint32_t *type, uint32_t *subtype) +{ + AVCodecParameters *par = stream->codecpar; + *type = 0; + *subtype = 0; + switch (par->codec_type) + { + case AVMEDIA_TYPE_VIDEO: + *type = CCX_MEDIA_VISUAL; + if (par->codec_id == AV_CODEC_ID_H264) + *subtype = CCX_SUBTYPE_AVC_H264; + else if (par->codec_id == AV_CODEC_ID_HEVC) + *subtype = CCX_SUBTYPE_HEV1; + else if (par->codec_id == AV_CODEC_ID_MPEG2VIDEO) + *subtype = CCX_SUBTYPE_XDVB; + break; + case AVMEDIA_TYPE_SUBTITLE: + if (par->codec_id == AV_CODEC_ID_MOV_TEXT) + { + *type = CCX_MEDIA_TEXT; + *subtype = CCX_SUBTYPE_TX3G; + } + else if (par->codec_id == AV_CODEC_ID_EIA_608) + { + *type = CCX_MEDIA_CLOSED_CAPTION; + *subtype = CCX_SUBTYPE_C608; + } + else if (par->codec_id == AV_CODEC_ID_DVD_SUBTITLE) + { + *type = CCX_MEDIA_SUBPIC; + *subtype = CCX_SUBTYPE_MPEG4; + } + else + { + *type = CCX_MEDIA_TEXT; + } + break; + case AVMEDIA_TYPE_DATA: + { + uint32_t tag = par->codec_tag; + uint32_t tag_be = ((tag & 0xFF) << 24) | (((tag >> 8) & 0xFF) << 16) | (((tag >> 16) & 0xFF) << 8) | ((tag >> 24) & 0xFF); + if (tag_be == CCX_SUBTYPE_C608) + { + *type = CCX_MEDIA_CLOSED_CAPTION; + *subtype = CCX_SUBTYPE_C608; + } + else if (tag_be == CCX_SUBTYPE_C708) + { + *type = CCX_MEDIA_CLOSED_CAPTION; + *subtype = CCX_SUBTYPE_C708; + } + break; + } + default: + break; + } +} + +static uint8_t avc_nal_unit_size(const uint8_t *e, int n) +{ + if (!e || n < 5 || e[0] != 1) + return 4; + return (e[4] & 0x03) + 1; +} + +static uint8_t hevc_nal_unit_size(const uint8_t *e, int n) +{ + if (!e || n < 22) + return 4; + return (e[21] & 0x03) + 1; +} + +static CCX_ISOSample *sample_from_packet(AVPacket *pkt) +{ + CCX_ISOSample *s = malloc(sizeof(CCX_ISOSample)); + if (!s) + return NULL; + s->data = malloc(pkt->size); + if (!s->data) + { + free(s); + return NULL; + } + memcpy(s->data, pkt->data, pkt->size); + s->dataLength = pkt->size; + int64_t dts = (pkt->dts != AV_NOPTS_VALUE) ? pkt->dts : (pkt->pts != AV_NOPTS_VALUE ? pkt->pts : 0); + int64_t pts = (pkt->pts != AV_NOPTS_VALUE) ? pkt->pts : dts; + s->DTS = (uint64_t)(dts >= 0 ? dts : 0); + s->CTS_Offset = (uint32_t)(pts > dts ? (pts - dts) : 0); + return s; +} + +static void sample_free(CCX_ISOSample **s) +{ + if (!s || !*s) + return; + free((*s)->data); + free(*s); + *s = NULL; +} + +static PacketStore **collect_packets(AVFormatContext *fmt_ctx) +{ + int n = fmt_ctx->nb_streams; + PacketStore **stores = calloc(n, sizeof(PacketStore *)); + if (!stores) + return NULL; + for (int i = 0; i < n; i++) + stores[i] = packet_store_new(); + AVPacket *pkt = av_packet_alloc(); + while (av_read_frame(fmt_ctx, pkt) >= 0) + { + int idx = pkt->stream_index; + if (idx >= 0 && idx < n) + { + AVPacket *copy = av_packet_alloc(); + av_packet_ref(copy, pkt); + packet_store_push(stores[idx], copy); + } + av_packet_unref(pkt); + } + av_packet_free(&pkt); + return stores; +} + +static void free_stores(PacketStore **stores, int n) +{ + if (!stores) + return; + for (int i = 0; i < n; i++) + packet_store_free(stores[i]); + free(stores); +} + +static int do_avc_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, struct cc_subtitle *sub) +{ + struct lib_cc_decode *dec = update_decoder_list(ctx); + struct encoder_ctx *enc = update_encoder_list(ctx); + dec->in_bufferdatatype = CCX_H264; + if (ps->count < 1) + return 0; + uint32_t ts = st->time_base.den; + CCX_AVCConfig cfg = {avc_nal_unit_size(st->codecpar->extradata, st->codecpar->extradata_size)}; + const uint8_t *e = st->codecpar->extradata; + int en = st->codecpar->extradata_size; + if (e && en > 6) + { + const uint8_t *p = e + 5; + int rem = en - 5; + int ns = (*p) & 0x1F; + p++; + rem--; + for (int i = 0; i < ns && rem >= 2; i++) + { + int l = (p[0] << 8) | p[1]; + p += 2; + rem -= 2; + if (l <= rem) + { + do_NAL(enc, dec, (unsigned char *)p, l, sub); + p += l; + rem -= l; + } + } + if (rem >= 1) + { + int np = *p & 0xFF; + p++; + rem--; + for (int i = 0; i < np && rem >= 2; i++) + { + int l = (p[0] << 8) | p[1]; + p += 2; + rem -= 2; + if (l <= rem) + { + do_NAL(enc, dec, (unsigned char *)p, l, sub); + p += l; + rem -= l; + } + } + } + } + int status = 0; + for (int i = 0; i < ps->count; i++) + { + CCX_ISOSample *s = sample_from_packet(ps->pkts[i]); + if (!s) + continue; + status = process_avc_sample(ctx, ts, (GF_AVCConfig *)&cfg, (GF_ISOSample *)s, sub); + sample_free(&s); + if (status != 0) + break; + int prog = (int)((i * 100) / ps->count); + if (ctx->last_reported_progress != prog) + { + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(prog, cs / 60, cs % 60); + ctx->last_reported_progress = prog; + } + } + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(100, cs / 60, cs % 60); + return status; +} + +static int do_hevc_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, struct cc_subtitle *sub) +{ + struct lib_cc_decode *dec = update_decoder_list(ctx); + struct encoder_ctx *enc = update_encoder_list(ctx); + dec->in_bufferdatatype = CCX_H264; + dec->avc_ctx->is_hevc = 1; + if (ps->count < 1) + return 0; + uint32_t ts = st->time_base.den; + CCX_HEVCConfig cfg = {hevc_nal_unit_size(st->codecpar->extradata, st->codecpar->extradata_size)}; + const uint8_t *e = st->codecpar->extradata; + int en = st->codecpar->extradata_size; + if (e && en > 23) + { + const uint8_t *p = e + 22; + int rem = en - 22; + int na = *p; + p++; + rem--; + for (int a = 0; a < na && rem >= 3; a++) + { + p++; + rem--; + int nn = (p[0] << 8) | p[1]; + p += 2; + rem -= 2; + for (int n = 0; n < nn && rem >= 2; n++) + { + int l = (p[0] << 8) | p[1]; + p += 2; + rem -= 2; + if (l <= rem) + { + do_NAL(enc, dec, (unsigned char *)p, l, sub); + p += l; + rem -= l; + } + } + } + } + int status = 0; + for (int i = 0; i < ps->count; i++) + { + CCX_ISOSample *s = sample_from_packet(ps->pkts[i]); + if (!s) + continue; + status = process_hevc_sample(ctx, ts, (GF_HEVCConfig *)&cfg, (GF_ISOSample *)s, sub); + sample_free(&s); + if (status != 0) + break; + int prog = (int)((i * 100) / ps->count); + if (ctx->last_reported_progress != prog) + { + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(prog, cs / 60, cs % 60); + ctx->last_reported_progress = prog; + } + } + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(100, cs / 60, cs % 60); + return status; +} + +static int do_xdvb_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, struct cc_subtitle *sub) +{ + struct lib_cc_decode *dec = update_decoder_list(ctx); + struct encoder_ctx *enc = update_encoder_list(ctx); + dec->in_bufferdatatype = CCX_PES; + if (ps->count < 1) + return 0; + uint32_t ts = st->time_base.den; + for (int i = 0; i < ps->count; i++) + { + CCX_ISOSample *s = sample_from_packet(ps->pkts[i]); + if (!s) + continue; + int32_t sc = (int32_t)s->CTS_Offset; + set_current_pts(dec->timing, (s->DTS + sc) * MPEG_CLOCK_FREQ / ts); + set_fts(dec->timing); + process_m2v(enc, dec, (unsigned char *)s->data, s->dataLength, sub); + sample_free(&s); + int prog = (int)((i * 100) / ps->count); + if (ctx->last_reported_progress != prog) + { + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(prog, cs / 60, cs % 60); + ctx->last_reported_progress = prog; + } + } + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(100, cs / 60, cs % 60); + return 0; +} + +static int do_vobsub_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, struct cc_subtitle *sub) +{ + struct lib_cc_decode *dec = update_decoder_list(ctx); + struct encoder_ctx *enc = update_encoder_list(ctx); + if (ps->count < 1) + return 0; + if (!vobsub_ocr_available()) + fatal(EXIT_NOT_CLASSIFIED, "VOBSUB requires OCR. Rebuild with -DWITH_OCR=ON"); + struct vobsub_ctx *vob = init_vobsub_decoder(); + if (!vob) + fatal(EXIT_NOT_CLASSIFIED, "VOBSUB decoder init failed"); + if (st->codecpar->extradata && st->codecpar->extradata_size > 0) + { + char *hdr = malloc(st->codecpar->extradata_size + 1); + if (hdr) + { + memcpy(hdr, st->codecpar->extradata, st->codecpar->extradata_size); + hdr[st->codecpar->extradata_size] = 0; + vobsub_parse_palette(vob, hdr); + free(hdr); + } + } + uint32_t ts = st->time_base.den; + mprint("Processing VOBSUB track (%d samples)\n", ps->count); + for (int i = 0; i < ps->count; i++) + { + CCX_ISOSample *s = sample_from_packet(ps->pkts[i]); + if (!s) + continue; + int32_t sc = (int32_t)s->CTS_Offset; + LLONG t0 = (LLONG)((s->DTS + sc) * 1000) / ts, t1 = 0; + if (i + 1 < ps->count) + { + CCX_ISOSample *ns = sample_from_packet(ps->pkts[i + 1]); + if (ns) + { + int32_t nc = (int32_t)ns->CTS_Offset; + t1 = (LLONG)((ns->DTS + nc) * 1000) / ts; + sample_free(&ns); + } + } + if (!t1) + t1 = t0 + 5000; + set_current_pts(dec->timing, (s->DTS + sc) * MPEG_CLOCK_FREQ / ts); + set_fts(dec->timing); + struct cc_subtitle vs; + memset(&vs, 0, sizeof(vs)); + int r = vobsub_decode_spu(vob, (unsigned char *)s->data, s->dataLength, t0, t1, &vs); + sample_free(&s); + if (r == 0 && vs.got_output) + { + encode_sub(enc, &vs); + sub->got_output = 1; + if (vs.data) + { + struct cc_bitmap *rect = (struct cc_bitmap *)vs.data; + for (int j = 0; j < vs.nb_data; j++) + { + if (rect[j].data0) + free(rect[j].data0); + if (rect[j].data1) + free(rect[j].data1); + } + free(vs.data); + } + } + int prog = (int)((i * 100) / ps->count); + if (ctx->last_reported_progress != prog) + { + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(prog, cs / 60, cs % 60); + ctx->last_reported_progress = prog; + } + } + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(100, cs / 60, cs % 60); + delete_vobsub_decoder(&vob); + mprint("VOBSUB processing complete\n"); + return 0; +} + +static int do_cc_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, uint32_t type, uint32_t subtype, struct cc_subtitle *dec_sub, int *mp4_ret) +{ + struct lib_cc_decode *dec = update_decoder_list(ctx); + struct encoder_ctx *enc = update_encoder_list(ctx); + if (ps->count < 1) + return 0; + uint32_t ts = st->time_base.den; + uint64_t track_type = CCX_MEDIA_TYPE(type, subtype); + for (int k = 0; k < ps->count; k++) + { + CCX_ISOSample *sample = sample_from_packet(ps->pkts[k]); + if (!sample) + continue; + int32_t sc = (int32_t)sample->CTS_Offset; + set_current_pts(dec->timing, (sample->DTS + sc) * MPEG_CLOCK_FREQ / ts); + if (type == CCX_MEDIA_CLOSED_CAPTION) + dec->timing->current_picture_coding_type = CCX_FRAME_TYPE_I_FRAME; + set_fts(dec->timing); + int atomStart = 0; + while (atomStart < (int)sample->dataLength) + { + char *data = sample->data + atomStart; + int al = -1; + switch (track_type) + { + case CCX_MEDIA_TYPE(CCX_MEDIA_TEXT, CCX_SUBTYPE_TX3G): + case CCX_MEDIA_TYPE(CCX_MEDIA_SUBT, CCX_SUBTYPE_TX3G): + al = process_tx3g(ctx, enc, dec, dec_sub, mp4_ret, data, sample->dataLength, 0); + break; + case CCX_MEDIA_TYPE(CCX_MEDIA_CLOSED_CAPTION, CCX_SUBTYPE_C608): + case CCX_MEDIA_TYPE(CCX_MEDIA_CLOSED_CAPTION, CCX_SUBTYPE_C708): + al = process_clcp(ctx, enc, dec, dec_sub, mp4_ret, subtype, data, sample->dataLength); + break; + default: + { + static int ur = 0; + if (!ur) + { + mprint("\nUnsupported CC track. Please report.\n"); + ur = 1; + } + al = -1; + break; + } + } + if (al == -1) + break; + atomStart += al; + } + sample_free(&sample); + int prog = (int)((k * 100) / ps->count); + if (ctx->last_reported_progress != prog) + { + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(prog, cs / 60, cs % 60); + ctx->last_reported_progress = prog; + } + } + if (subtype == CCX_SUBTYPE_TX3G) + { + struct encoder_ctx *e2 = update_encoder_list(ctx); + struct lib_cc_decode *d2 = update_decoder_list(ctx); + process_tx3g(ctx, e2, d2, dec_sub, mp4_ret, NULL, 0, 1); + } + int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); + activity_progress(100, cs / 60, cs % 60); + return 0; +} + +int processmp4_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) +{ + int mp4_ret = 0; + struct cc_subtitle dec_sub; + struct lib_cc_decode *dec = update_decoder_list(ctx); + struct encoder_ctx *enc = update_encoder_list(ctx); + if (enc) + enc->timing = dec->timing; +#ifndef DISABLE_RUST + ccxr_dtvcc_set_encoder(dec->dtvcc_rust, enc); +#else + dec->dtvcc->encoder = (void *)enc; +#endif + memset(&dec_sub, 0, sizeof(dec_sub)); + if (!file) + { + mprint("Error: NULL file\n"); + return -1; + } + mprint("Opening '%s': ", file); + av_log_set_level(AV_LOG_ERROR); + AVFormatContext *fmt = NULL; + if (avformat_open_input(&fmt, file, NULL, NULL) < 0) + { + mprint("failed\n"); + freep(&dec->xds_ctx); + return -2; + } + if (avformat_find_stream_info(fmt, NULL) < 0) + { + mprint("failed\n"); + avformat_close_input(&fmt); + freep(&dec->xds_ctx); + return -2; + } + mprint("ok\n"); + int tc = (int)fmt->nb_streams; + int avc_c = 0, hevc_c = 0, cc_c = 0, vob_c = 0; + for (int i = 0; i < tc; i++) + { + uint32_t type, subtype; + ffmpeg_stream_to_type_subtype(fmt->streams[i], &type, &subtype); + mprint("Track %d, type=%c%c%c%c subtype=%c%c%c%c\n", i + 1, (unsigned char)((type >> 24) & 0xFF), (unsigned char)((type >> 16) & 0xFF), (unsigned char)((type >> 8) & 0xFF), (unsigned char)(type & 0xFF), (unsigned char)((subtype >> 24) & 0xFF), (unsigned char)((subtype >> 16) & 0xFF), (unsigned char)((subtype >> 8) & 0xFF), (unsigned char)(subtype & 0xFF)); + if (type == CCX_MEDIA_CLOSED_CAPTION || type == CCX_MEDIA_SUBT || type == CCX_MEDIA_TEXT) + cc_c++; + if (type == CCX_MEDIA_VISUAL && subtype == CCX_SUBTYPE_AVC_H264) + avc_c++; + if (type == CCX_MEDIA_VISUAL && (subtype == CCX_SUBTYPE_HEV1 || subtype == CCX_SUBTYPE_HVC1)) + hevc_c++; + if (type == CCX_MEDIA_SUBPIC && subtype == CCX_SUBTYPE_MPEG4) + vob_c++; + } + mprint("MP4: found %d tracks: %d avc, %d hevc, %d cc, %d vobsub\n", tc, avc_c, hevc_c, cc_c, vob_c); + PacketStore **stores = collect_packets(fmt); + if (!stores) + { + avformat_close_input(&fmt); + freep(&dec->xds_ctx); + return -2; + } + for (int i = 0; i < tc; i++) + { + AVStream *st = fmt->streams[i]; + uint32_t type, subtype; + ffmpeg_stream_to_type_subtype(st, &type, &subtype); + uint64_t tt = CCX_MEDIA_TYPE(type, subtype); + switch (tt) + { + case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_XDVB): + if (cc_c && !cfg->mp4vidtrack) + continue; + if (avc_c > 1) + switch_output_file(ctx, enc, i); + if (do_xdvb_track(ctx, st, stores[i], &dec_sub) != 0) + { + mprint("Error xdvb\n"); + goto cleanup; + } + if (dec_sub.got_output) + { + mp4_ret = 1; + encode_sub(enc, &dec_sub); + dec_sub.got_output = 0; + } + break; + case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_AVC_H264): + if (cc_c && !cfg->mp4vidtrack) + continue; + if (avc_c > 1) + switch_output_file(ctx, enc, i); + if (do_avc_track(ctx, st, stores[i], &dec_sub) != 0) + { + mprint("Error avc\n"); + goto cleanup; + } + if (dec_sub.got_output) + { + mp4_ret = 1; + encode_sub(enc, &dec_sub); + dec_sub.got_output = 0; + } + break; + case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_HEV1): + case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_HVC1): + if (cc_c && !cfg->mp4vidtrack) + continue; + if (hevc_c > 1) + switch_output_file(ctx, enc, i); + dec->avc_ctx->is_hevc = 1; + if (do_hevc_track(ctx, st, stores[i], &dec_sub) != 0) + { + mprint("Error hevc\n"); + goto cleanup; + } + if (dec_sub.got_output) + { + mp4_ret = 1; + encode_sub(enc, &dec_sub); + dec_sub.got_output = 0; + } + break; + case CCX_MEDIA_TYPE(CCX_MEDIA_SUBPIC, CCX_SUBTYPE_MPEG4): + if (vob_c > 1) + switch_output_file(ctx, enc, i); + if (do_vobsub_track(ctx, st, stores[i], &dec_sub) != 0) + { + mprint("Error vobsub\n"); + goto cleanup; + } + if (dec_sub.got_output) + mp4_ret = 1; + break; + default: + if (type != CCX_MEDIA_CLOSED_CAPTION && type != CCX_MEDIA_SUBT && type != CCX_MEDIA_TEXT) + break; + if (avc_c && cfg->mp4vidtrack) + continue; + if (cc_c > 1) + switch_output_file(ctx, enc, i); + do_cc_track(ctx, st, stores[i], type, subtype, &dec_sub, &mp4_ret); + break; + } + } +cleanup: + free_stores(stores, tc); + avformat_close_input(&fmt); + freep(&dec->xds_ctx); + mprint("\nDone processing '%s'\n", file); + if (avc_c) + mprint("Found %d AVC track(s). ", avc_c); + else + mprint("Found no AVC track(s). "); + if (hevc_c) + mprint("Found %d HEVC track(s). ", hevc_c); + else + mprint("Found no HEVC track(s). "); + if (cc_c) + mprint("Found %d CC track(s). ", cc_c); + else + mprint("Found no dedicated CC track(s). "); + if (vob_c) + mprint("Found %d VOBSUB track(s).\n", vob_c); + else + mprint("\n"); + ctx->freport.mp4_cc_track_cnt = cc_c; + if ((dec->write_format == CCX_OF_MCC) && (dec->saw_caption_block == CCX_TRUE)) + mp4_ret = 1; + return mp4_ret; +} + +int dumpchapters_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) +{ + (void)ctx; + (void)cfg; + mprint("Opening '%s': ", file); + av_log_set_level(AV_LOG_ERROR); + AVFormatContext *fmt = NULL; + if (avformat_open_input(&fmt, file, NULL, NULL) < 0) + { + mprint("failed\n"); + return 5; + } + avformat_find_stream_info(fmt, NULL); + mprint("ok\n"); + int count = (int)fmt->nb_chapters; + if (count == 0) + { + mprint("No chapters found!\n"); + avformat_close_input(&fmt); + return 0; + } + char szName[1024]; + snprintf(szName, sizeof(szName), "%s.txt", get_basename(file)); + FILE *t = fopen(szName, "wt"); + if (!t) + { + avformat_close_input(&fmt); + return 5; + } + printf("Writing chapters into %s\n", szName); + for (int i = 0; i < count; i++) + { + AVChapter *ch = fmt->chapters[i]; + double ss = (double)ch->start * ch->time_base.num / ch->time_base.den; + int h = (int)(ss / 3600), m = (int)(ss / 60) - h * 60, s = (int)ss - h * 3600 - m * 60, ms = (int)((ss - (int)ss) * 1000); + AVDictionaryEntry *title = av_dict_get(ch->metadata, "title", NULL, 0); + fprintf(t, "CHAPTER%02d=%02d:%02d:%02d.%03d\n", i + 1, h, m, s, ms); + fprintf(t, "CHAPTER%02dNAME=%s\n", i + 1, title ? title->value : ""); + } + fclose(t); + avformat_close_input(&fmt); + return 1; +} + +#endif // ENABLE_FFMPEG_MP4 + int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) { +#ifdef ENABLE_FFMPEG_MP4 + return processmp4_ffmpeg(ctx, cfg, file); +#else int mp4_ret = 0; GF_ISOFile *f; u32 i, j, track_count, avc_track_count, hevc_track_count, cc_track_count; @@ -909,16 +1689,27 @@ int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); #endif +#ifdef ENABLE_FFMPEG_MP4 + AVFormatContext *fmt_ctx = NULL; + if (avformat_open_input(&fmt_ctx, file, NULL, NULL) < 0) + { + mprint("Failed to open input file (avformat_open_input() returned error)\n"); + free(dec_ctx->xds_ctx); + return -2; + } + avformat_find_stream_info(fmt_ctx, NULL); + mprint("ok\n"); + track_count = fmt_ctx->nb_streams; +#else if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) { mprint("Failed to open input file (gf_isom_open() returned error)\n"); free(dec_ctx->xds_ctx); return -2; } - mprint("ok\n"); - track_count = gf_isom_get_track_count(f); +#endif avc_track_count = 0; hevc_track_count = 0; @@ -1235,61 +2026,69 @@ int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) } return mp4_ret; + +#endif // ENABLE_FFMPEG_MP4 } int dumpchapters(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) { - int mp4_ret = 0; - GF_ISOFile *f; - mprint("Opening \'%s\': ", file); +#ifdef ENABLE_FFMPEG_MP4 + return dumpchapters_ffmpeg(ctx, cfg, file); +#else + { + int mp4_ret = 0; + GF_ISOFile *f; + mprint("Opening \'%s\': ", file); #ifdef MP4_DEBUG - gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); + gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); #endif - if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) - { - mprint("failed to open\n"); - return 5; - } + if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) + { + mprint("failed to open\n"); + return 5; + } - mprint("ok\n"); + mprint("ok\n"); - char szName[1024]; - FILE *t; - u32 i, count; - count = gf_isom_get_chapter_count(f, 0); - if (count > 0) - { - if (file) + char szName[1024]; + FILE *t; + u32 i, count; + count = gf_isom_get_chapter_count(f, 0); + if (count > 0) { - snprintf(szName, sizeof(szName), "%s.txt", get_basename(file)); + if (file) + { + snprintf(szName, sizeof(szName), "%s.txt", get_basename(file)); - t = gf_fopen(szName, "wt"); - if (!t) - return 5; + t = gf_fopen(szName, "wt"); + if (!t) + return 5; + } + else + { + t = stdout; + } + mp4_ret = 1; + printf("Writing chapters into %s\n", szName); } else { - t = stdout; + mprint("No chapters information found!\n"); } - mp4_ret = 1; - printf("Writing chapters into %s\n", szName); - } - else - { - mprint("No chapters information found!\n"); - } - for (i = 0; i < count; i++) - { - u64 chapter_time; - const char *name; - char szDur[64]; - gf_isom_get_chapter(f, 0, i + 1, &chapter_time, &name); - fprintf(t, "CHAPTER%02d=%s\n", i + 1, format_duration(chapter_time, 1000, szDur, sizeof(szDur))); - fprintf(t, "CHAPTER%02dNAME=%s\n", i + 1, name); + for (i = 0; i < count; i++) + { + u64 chapter_time; + const char *name; + char szDur[64]; + gf_isom_get_chapter(f, 0, i + 1, &chapter_time, &name); + fprintf(t, "CHAPTER%02d=%s\n", i + 1, format_duration(chapter_time, 1000, szDur, sizeof(szDur))); + fprintf(t, "CHAPTER%02dNAME=%s\n", i + 1, name); + } + if (file) + gf_fclose(t); + return mp4_ret; } - if (file) - gf_fclose(t); - return mp4_ret; +#endif // ENABLE_FFMPEG_MP4 } From 9e95cd7c603d11a56e9da2f8c1d674975802c0be Mon Sep 17 00:00:00 2001 From: Dhanush Varma Date: Fri, 6 Mar 2026 12:06:39 +0530 Subject: [PATCH 2/2] ci: retrigger builds --- .gitignore | 3 ++ src/ccextractor.c | 2 +- src/lib_ccx/mp4.c | 104 ++++++++++++++++++++++------------------------ 3 files changed, 54 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index 925e24025..22a4275be 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,6 @@ plans/ tess.log **/tess.log ut=srt* +*.bak +mac/libccx_rust.a +tests/samples.zip diff --git a/src/ccextractor.c b/src/ccextractor.c index 4b766dde4..cfcc58397 100644 --- a/src/ccextractor.c +++ b/src/ccextractor.c @@ -223,7 +223,7 @@ int start_ccx() break; case CCX_SM_MP4: mprint("\rAnalyzing data in MP4 mode\n"); - close_input_file(ctx); // No need to have it open. GPAC will do it for us + close_input_file(ctx); // No need to have it open. The MP4 library will reopen it if (ctx->current_file == -1) // We don't have a file to open, must be stdin, and GPAC is incompatible with stdin { fatal(EXIT_INCOMPATIBLE_PARAMETERS, "MP4 requires an actual file, it's not possible to read from a stream, including stdin.\n"); diff --git a/src/lib_ccx/mp4.c b/src/lib_ccx/mp4.c index 5a314ad89..cd7b6e0a8 100644 --- a/src/lib_ccx/mp4.c +++ b/src/lib_ccx/mp4.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -30,7 +31,6 @@ typedef struct } GF_HEVCConfig; typedef void GF_ISOFile; typedef void GF_GenericSampleDescription; -#include #define GF_4CC(a, b, c, d) ((((u32)(a)) << 24) | (((u32)(b)) << 16) | (((u32)(c)) << 8) | ((u32)(d))) #include #include @@ -1015,6 +1015,7 @@ static void ffmpeg_stream_to_type_subtype(AVStream *stream, uint32_t *type, uint *type = CCX_MEDIA_CLOSED_CAPTION; *subtype = CCX_SUBTYPE_C608; } + else if (par->codec_id == AV_CODEC_ID_DVD_SUBTITLE) { *type = CCX_MEDIA_SUBPIC; @@ -1089,37 +1090,25 @@ static void sample_free(CCX_ISOSample **s) *s = NULL; } -static PacketStore **collect_packets(AVFormatContext *fmt_ctx) +static PacketStore *collect_packets_for_stream(AVFormatContext *fmt_ctx, int stream_idx) { - int n = fmt_ctx->nb_streams; - PacketStore **stores = calloc(n, sizeof(PacketStore *)); - if (!stores) + PacketStore *ps = packet_store_new(); + if (!ps) return NULL; - for (int i = 0; i < n; i++) - stores[i] = packet_store_new(); + av_seek_frame(fmt_ctx, stream_idx, 0, AVSEEK_FLAG_BACKWARD); AVPacket *pkt = av_packet_alloc(); while (av_read_frame(fmt_ctx, pkt) >= 0) { - int idx = pkt->stream_index; - if (idx >= 0 && idx < n) + if (pkt->stream_index == stream_idx) { AVPacket *copy = av_packet_alloc(); av_packet_ref(copy, pkt); - packet_store_push(stores[idx], copy); + packet_store_push(ps, copy); } av_packet_unref(pkt); } av_packet_free(&pkt); - return stores; -} - -static void free_stores(PacketStore **stores, int n) -{ - if (!stores) - return; - for (int i = 0; i < n; i++) - packet_store_free(stores[i]); - free(stores); + return ps; } static int do_avc_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, struct cc_subtitle *sub) @@ -1177,11 +1166,11 @@ static int do_avc_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, CCX_ISOSample *s = sample_from_packet(ps->pkts[i]); if (!s) continue; - status = process_avc_sample(ctx, ts, (GF_AVCConfig *)&cfg, (GF_ISOSample *)s, sub); + status = process_avc_sample(ctx, ts, (GF_AVCConfig *)(void *)&cfg, (GF_ISOSample *)(void *)s, sub); sample_free(&s); if (status != 0) break; - int prog = (int)((i * 100) / ps->count); + int prog = (int)(((int64_t)i * 100) / ps->count); if (ctx->last_reported_progress != prog) { int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); @@ -1240,11 +1229,11 @@ static int do_hevc_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, CCX_ISOSample *s = sample_from_packet(ps->pkts[i]); if (!s) continue; - status = process_hevc_sample(ctx, ts, (GF_HEVCConfig *)&cfg, (GF_ISOSample *)s, sub); + status = process_hevc_sample(ctx, ts, (GF_HEVCConfig *)(void *)&cfg, (GF_ISOSample *)(void *)s, sub); sample_free(&s); if (status != 0) break; - int prog = (int)((i * 100) / ps->count); + int prog = (int)(((int64_t)i * 100) / ps->count); if (ctx->last_reported_progress != prog) { int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); @@ -1275,7 +1264,7 @@ static int do_xdvb_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, set_fts(dec->timing); process_m2v(enc, dec, (unsigned char *)s->data, s->dataLength, sub); sample_free(&s); - int prog = (int)((i * 100) / ps->count); + int prog = (int)(((int64_t)i * 100) / ps->count); if (ctx->last_reported_progress != prog) { int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); @@ -1354,7 +1343,7 @@ static int do_vobsub_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *p free(vs.data); } } - int prog = (int)((i * 100) / ps->count); + int prog = (int)(((int64_t)i * 100) / ps->count); if (ctx->last_reported_progress != prog) { int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); @@ -1419,7 +1408,7 @@ static int do_cc_track(struct lib_ccx_ctx *ctx, AVStream *st, PacketStore *ps, u atomStart += al; } sample_free(&sample); - int prog = (int)((k * 100) / ps->count); + int prog = (int)(((int64_t)k * 100) / ps->count); if (ctx->last_reported_progress != prog) { int cs = (int)(get_fts(dec->timing, dec->current_field) / 1000); @@ -1491,29 +1480,32 @@ int processmp4_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *f vob_c++; } mprint("MP4: found %d tracks: %d avc, %d hevc, %d cc, %d vobsub\n", tc, avc_c, hevc_c, cc_c, vob_c); - PacketStore **stores = collect_packets(fmt); - if (!stores) - { - avformat_close_input(&fmt); - freep(&dec->xds_ctx); - return -2; - } for (int i = 0; i < tc; i++) { AVStream *st = fmt->streams[i]; uint32_t type, subtype; ffmpeg_stream_to_type_subtype(st, &type, &subtype); uint64_t tt = CCX_MEDIA_TYPE(type, subtype); + /* Skip streams we don't handle */ + if (type == 0) + continue; + PacketStore *ps = collect_packets_for_stream(fmt, i); + if (!ps) + continue; switch (tt) { case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_XDVB): if (cc_c && !cfg->mp4vidtrack) + { + packet_store_free(ps); continue; + } if (avc_c > 1) switch_output_file(ctx, enc, i); - if (do_xdvb_track(ctx, st, stores[i], &dec_sub) != 0) + if (do_xdvb_track(ctx, st, ps, &dec_sub) != 0) { mprint("Error xdvb\n"); + packet_store_free(ps); goto cleanup; } if (dec_sub.got_output) @@ -1525,12 +1517,16 @@ int processmp4_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *f break; case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_AVC_H264): if (cc_c && !cfg->mp4vidtrack) + { + packet_store_free(ps); continue; + } if (avc_c > 1) switch_output_file(ctx, enc, i); - if (do_avc_track(ctx, st, stores[i], &dec_sub) != 0) + if (do_avc_track(ctx, st, ps, &dec_sub) != 0) { mprint("Error avc\n"); + packet_store_free(ps); goto cleanup; } if (dec_sub.got_output) @@ -1543,13 +1539,17 @@ int processmp4_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *f case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_HEV1): case CCX_MEDIA_TYPE(CCX_MEDIA_VISUAL, CCX_SUBTYPE_HVC1): if (cc_c && !cfg->mp4vidtrack) + { + packet_store_free(ps); continue; + } if (hevc_c > 1) switch_output_file(ctx, enc, i); dec->avc_ctx->is_hevc = 1; - if (do_hevc_track(ctx, st, stores[i], &dec_sub) != 0) + if (do_hevc_track(ctx, st, ps, &dec_sub) != 0) { mprint("Error hevc\n"); + packet_store_free(ps); goto cleanup; } if (dec_sub.got_output) @@ -1562,9 +1562,10 @@ int processmp4_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *f case CCX_MEDIA_TYPE(CCX_MEDIA_SUBPIC, CCX_SUBTYPE_MPEG4): if (vob_c > 1) switch_output_file(ctx, enc, i); - if (do_vobsub_track(ctx, st, stores[i], &dec_sub) != 0) + if (do_vobsub_track(ctx, st, ps, &dec_sub) != 0) { mprint("Error vobsub\n"); + packet_store_free(ps); goto cleanup; } if (dec_sub.got_output) @@ -1574,15 +1575,18 @@ int processmp4_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *f if (type != CCX_MEDIA_CLOSED_CAPTION && type != CCX_MEDIA_SUBT && type != CCX_MEDIA_TEXT) break; if (avc_c && cfg->mp4vidtrack) + { + packet_store_free(ps); continue; + } if (cc_c > 1) switch_output_file(ctx, enc, i); - do_cc_track(ctx, st, stores[i], type, subtype, &dec_sub, &mp4_ret); + do_cc_track(ctx, st, ps, type, subtype, &dec_sub, &mp4_ret); break; } + packet_store_free(ps); } cleanup: - free_stores(stores, tc); avformat_close_input(&fmt); freep(&dec->xds_ctx); mprint("\nDone processing '%s'\n", file); @@ -1620,7 +1624,12 @@ int dumpchapters_ffmpeg(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char mprint("failed\n"); return 5; } - avformat_find_stream_info(fmt, NULL); + if (avformat_find_stream_info(fmt, NULL) < 0) + { + mprint("failed to find stream info\n"); + avformat_close_input(&fmt); + return 5; + } mprint("ok\n"); int count = (int)fmt->nb_chapters; if (count == 0) @@ -1689,18 +1698,6 @@ int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) gf_log_set_tool_level(GF_LOG_CONTAINER, GF_LOG_DEBUG); #endif -#ifdef ENABLE_FFMPEG_MP4 - AVFormatContext *fmt_ctx = NULL; - if (avformat_open_input(&fmt_ctx, file, NULL, NULL) < 0) - { - mprint("Failed to open input file (avformat_open_input() returned error)\n"); - free(dec_ctx->xds_ctx); - return -2; - } - avformat_find_stream_info(fmt_ctx, NULL); - mprint("ok\n"); - track_count = fmt_ctx->nb_streams; -#else if ((f = gf_isom_open(file, GF_ISOM_OPEN_READ, NULL)) == NULL) { mprint("Failed to open input file (gf_isom_open() returned error)\n"); @@ -1709,7 +1706,6 @@ int processmp4(struct lib_ccx_ctx *ctx, struct ccx_s_mp4Cfg *cfg, char *file) } mprint("ok\n"); track_count = gf_isom_get_track_count(f); -#endif avc_track_count = 0; hevc_track_count = 0;