]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
transcode: fix frame rescale logic for FFmpeg versions older than 6.x
authorJames Hutchinson <jahutchinson99@googlemail.com>
Wed, 29 Oct 2025 10:04:21 +0000 (10:04 +0000)
committerFlole <Flole998@users.noreply.github.com>
Fri, 31 Oct 2025 21:23:05 +0000 (22:23 +0100)
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

src/transcoding/transcode/video.c

index 92223224cb4bb6d4af7223f24e619db10b9dd85f..484d52c57b45c4ad4ed536cb7a7c436aeeefadf7 100644 (file)
@@ -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);
     }