From: James Hutchinson Date: Wed, 29 Oct 2025 10:04:21 +0000 (+0000) Subject: transcode: fix frame rescale logic for FFmpeg versions older than 6.x X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=240d97377969898a6e68de8450b696558c645a91;p=thirdparty%2Ftvheadend.git transcode: fix frame rescale logic for FFmpeg versions older than 6.x Older FFmpeg releases (4.x–5.x) do not propagate reliable time_base values through the filter graph, causing incorrect PTS and duration scaling after commit 0af87f11. This led to invalid timestamps, ultimately resulting in blank, frozen or missing video (player dependant). This patch restores correct behaviour for older ffmpeg versions by deriving the source time_base from the encoder time_base and deinterlacing mode, while retaining the existing logic for FFmpeg 6.x and newer where the filter time_base is fully defined. Fixes #1963 --- diff --git a/src/transcoding/transcode/video.c b/src/transcoding/transcode/video.c index 92223224c..484d52c57 100644 --- a/src/transcoding/transcode/video.c +++ b/src/transcoding/transcode/video.c @@ -369,29 +369,40 @@ tvh_video_context_encode(TVHContext *self, AVFrame *avframe) return -1; } - const AVFilterLink *outlink = NULL; + AVRational src_time_base; #if LIBAVUTIL_VERSION_MAJOR >= 58 + // FFmpeg 6.x+ — proper duration field and reliable time_base attached to deint filter int64_t *frame_duration = &avframe->duration; + if (self->oavfltctx && self->oavfltctx->nb_inputs > 0) { + src_time_base = self->oavfltctx->inputs[0]->time_base; + } else { + // Fallback if filter graph input not available (e.g. early pipeline) + int rate_factor = ((TVHVideoCodecProfile *)self->profile)->deinterlace_field_rate == 1 ? 2 : 1; + src_time_base = av_mul_q(self->oavctx->time_base, (AVRational){1, rate_factor}); + tvh_context_log(self, LOG_TRACE, + "No valid input link found, falling back to scaled encoder time_base {%d/%d}", + src_time_base.num, src_time_base.den); + } #else + // FFmpeg 4.x–5.x — older API, VAAPI filter time_base not API exposed int64_t *frame_duration = &avframe->pkt_duration; + + // Compute correct source time_base based on deinterlacing mode + int field_rate = ((TVHVideoCodecProfile *)self->profile)->deinterlace_field_rate == 1 ? 2 : 1; + src_time_base = av_mul_q(self->oavctx->time_base, (AVRational) { 1, field_rate }); #endif tvh_context_log(self, LOG_TRACE, "Decoded frame: pts=%" PRId64 ", dts=%" PRId64 ", duration=%" PRId64, avframe->pts, avframe->pkt_dts, *frame_duration); - if (self->oavfltctx && self->oavfltctx->nb_inputs > 0) { - outlink = self->oavfltctx->inputs[0]; - } - - // filters exist and their time base differs from the encoder (e.g field-rate deinterlacer) - if (outlink && outlink->time_base.num > 0 && outlink->time_base.den > 0 && - av_cmp_q(outlink->time_base, self->oavctx->time_base) != 0) { + // source time base differs from the encoder (e.g field-rate deinterlacer) + if (av_cmp_q(src_time_base, self->oavctx->time_base) != 0) { // Rescale PTS from filter graph time_base to encoder time_base if (avframe->pts != AV_NOPTS_VALUE) { avframe->pts = av_rescale_q(avframe->pts, - outlink->time_base, + src_time_base, self->oavctx->time_base); // Deinterlace filters don't update DTS, so align DTS with PTS // This prevents duplicate or incorrect DTS values reaching the encoder @@ -401,7 +412,7 @@ tvh_video_context_encode(TVHContext *self, AVFrame *avframe) if (*frame_duration > 0) { // Rescale current frame duration from filter output time base -> encoder time base *frame_duration = av_rescale_q(*frame_duration, - outlink->time_base, + src_time_base, self->oavctx->time_base); } else if (self->oavctx->framerate.num > 0 && self->oavctx->framerate.den > 0) { // If duration is blank then fallback to expected duration based on encoder frame rate @@ -411,7 +422,7 @@ tvh_video_context_encode(TVHContext *self, AVFrame *avframe) tvh_context_log(self, LOG_TRACE, "Rescaled frame {%d/%d}->{%d/%d}: pts=%" PRId64 ", dts=%" PRId64 ", duration=%" PRId64, - outlink->time_base.num, outlink->time_base.den, + src_time_base.num, src_time_base.den, self->oavctx->time_base.num, self->oavctx->time_base.den, avframe->pts, avframe->pkt_dts, *frame_duration); }