#define VAAPI_ENC_B_REFERENCE_I_P 1
#define VAAPI_ENC_B_REFERENCE_I_P_B 2
+#define VAAPI_DEINT_MODE_DEFAULT 0
+#define VAAPI_DEINT_MODE_BOB 1
+#define VAAPI_DEINT_MODE_WEAVE 2
+#define VAAPI_DEINT_MODE_MADI 3
+#define VAAPI_DEINT_MODE_MCDI 4
+
#define UI_CODEC_AVAILABLE_OFFSET 0
#define UI_MAX_B_FRAMES_OFFSET 1
#define UI_MAX_QUALITY_OFFSET 4
return strtab2htsmsg(tab, 1, lang);
}
+static htsmsg_t *
+deinterlace_vaapi_mode_get_list( void *o, const char *lang )
+{
+ static const struct strtab tab[] = {
+ { N_("Default"), VAAPI_DEINT_MODE_DEFAULT },
+ { N_("Bob Deinterlacing"), VAAPI_DEINT_MODE_BOB },
+ { N_("Weave Deinterlacing"), VAAPI_DEINT_MODE_WEAVE },
+ { N_("Motion Adaptive Deinterlacing (MADI)"), VAAPI_DEINT_MODE_MADI },
+ { N_("Motion Compensated Deinterlacing (MCDI)"), VAAPI_DEINT_MODE_MCDI },
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
// h264
static htsmsg_t *
/* vaapi ==================================================================== */
-typedef struct {
- TVHVideoCodecProfile;
- int qp;
- int quality;
- int global_quality;
- int async_depth;
-/**
- * VAAPI Encoder availablity.
- * @note
- * return:
- * bit0 - will show if normal encoder is available (VAEntrypointEncSlice)
- */
- int ui;
-/**
- * VAAPI Encoder Low power availablity.
- * @note
- * return:
- * bit0 - will show if low power encoder is available (VAEntrypointEncSliceLP)
- */
- int uilp;
-/**
- * VAAPI Frame used as reference for B-frame [b_depth]
- * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
- * @note
- * int:
- * 0 - skip
- * 1 - all B-frames will refer only to P- or I-frames
- * 2 - multiple layers of B-frames will be present
- */
- int b_reference;
-/**
- * VAAPI Maximum consecutive B-frame [bf]
- * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
- * @note
- * int:
- * 0 - no B-Frames allowed
- * >0 - number of consecutive B-frames (valid with b_reference = 1 --> "use P- or I-frames")
- */
- int desired_b_depth;
-/**
- * VAAPI Maximum bitrate [maxrate]
- * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
- * @note
- * int:
- * VALUE - max bitrate in bps
- */
- double max_bit_rate;
-/**
- * VAAPI Maximum bitrate [maxrate]
- * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
- * @note
- * double:
- * VALUE - max bitrate in bps
- */
- double bit_rate_scale_factor;
-/**
- * VAAPI Platform hardware [not ffmpeg parameter]
- * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
- * @note
- * int:
- * 0 - Unconstrained (usefull for debug)
- * 1 - Intel
- * 2 - AMD
- */
- int platform;
- int loop_filter_level;
- int loop_filter_sharpness;
- double buff_factor;
- int rc_mode;
- int tier;
- int level;
- int qmin;
- int qmax;
- int super_frame;
-} tvh_codec_profile_vaapi_t;
-
#if defined(__linux__)
#include <linux/types.h>
#include <asm/ioctl.h>
.off = offsetof(tvh_codec_profile_vaapi_t, bit_rate_scale_factor),
.def.d = 0,
},
+ {
+ .type = PT_INT,
+ .id = "deinterlace_vaapi_mode",
+ .name = N_("VAAPI Deinterlace mode"),
+ .desc = N_("Mode to use for VAAPI Deinterlacing. "
+ "'Default' selects the most advanced deinterlacer, i.e. the mode appearing last in this list. "
+ "Tip: MADI and MCDI usually yield the smoothest results, especially when used with field rate output."),
+ .group = 2,
+ .opts = PO_ADVANCED,
+ .off = offsetof(tvh_codec_profile_vaapi_t, deinterlace_vaapi_mode),
+ .list = deinterlace_vaapi_mode_get_list,
+ .def.i = VAAPI_DEINT_MODE_DEFAULT,
+ },
{
.type = PT_INT,
.id = "hw_denoise", // Don't change
#define HWACCEL_PRIORITIZE_MMAL 3
#endif
+#define DEINT_RATE_FRAME 0
+#define DEINT_RATE_FIELD 1
+
+#define DEINT_AUTO_OFF 0
+#define DEINT_AUTO_ON 1
+
/* codec_profile_class ====================================================== */
uint32_t
* - 1 - enabled
*/
int deinterlace;
+
+ /**
+ * SW or HW deinterlace enable field rate (applies to deinterlace filters)
+ * @note
+ * int:
+ * - 0 - Output at frame rate (one frame of output for each field-pair)
+ * - 1 - Output at field rate (one frame of output for each field)
+ */
+ int deinterlace_field_rate;
+
+ /**
+ * SW or HW deinterlace 'auto' mode (applies to deinterlace filters)
+ * @note
+ * int:
+ * - 0 - Disabled (deinterlace all content, including progressive frames)
+ * - 1 - Enabled (only deinterlace interlaced fields; progressive frames are passed through unchanged)
+ */
+ int deinterlace_enable_auto;
+
int height;
/**
* SW or HW scaling mode (applies for decoding)
AVRational size;
} TVHVideoCodecProfile;
+typedef struct {
+ TVHVideoCodecProfile;
+ int qp;
+ int quality;
+ int global_quality;
+ int async_depth;
+/**
+ * VAAPI Encoder availablity.
+ * @note
+ * return:
+ * bit0 - will show if normal encoder is available (VAEntrypointEncSlice)
+ */
+ int ui;
+/**
+ * VAAPI Encoder Low power availablity.
+ * @note
+ * return:
+ * bit0 - will show if low power encoder is available (VAEntrypointEncSliceLP)
+ */
+ int uilp;
+/**
+ * VAAPI Frame used as reference for B-frame [b_depth]
+ * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
+ * @note
+ * int:
+ * 0 - skip
+ * 1 - all B-frames will refer only to P- or I-frames
+ * 2 - multiple layers of B-frames will be present
+ */
+ int b_reference;
+/**
+ * VAAPI Maximum consecutive B-frame [bf]
+ * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
+ * @note
+ * int:
+ * 0 - no B-Frames allowed
+ * >0 - number of consecutive B-frames (valid with b_reference = 1 --> "use P- or I-frames")
+ */
+ int desired_b_depth;
+/**
+ * VAAPI Maximum bitrate [maxrate]
+ * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
+ * @note
+ * int:
+ * VALUE - max bitrate in bps
+ */
+ double max_bit_rate;
+/**
+ * VAAPI Maximum bitrate [maxrate]
+ * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
+ * @note
+ * double:
+ * VALUE - max bitrate in bps
+ */
+ double bit_rate_scale_factor;
+/**
+ * VAAPI Platform hardware [not ffmpeg parameter]
+ * https://www.ffmpeg.org/ffmpeg-codecs.html#toc-VAAPI-encoders
+ * @note
+ * int:
+ * 0 - Unconstrained (usefull for debug)
+ * 1 - Intel
+ * 2 - AMD
+ */
+ int platform;
+/**
+ * VAAPI Deinterlace mode [deinterlace_vaapi mode parameter]
+ * https://ffmpeg.org/doxygen/6.1/vf__deinterlace__vaapi_8c.html
+ * @note
+ * int:
+ * 0 - Default: Use the highest-numbered (and therefore most advanced) deinterlacing algorithm
+ * 1 - Use the bob deinterlacing algorithm
+ * 2 - Use the weave deinterlacing algorithm
+ * 3 - Use the motion adaptive deinterlacing algorithm
+ * 4 - Use the motion compensated deinterlacing algorithm
+ */
+ int deinterlace_vaapi_mode;
+
+ int loop_filter_level;
+ int loop_filter_sharpness;
+ double buff_factor;
+ int rc_mode;
+ int tier;
+ int level;
+ int qmin;
+ int qmax;
+ int super_frame;
+} tvh_codec_profile_vaapi_t;
/* audio */
return strtab2htsmsg(tab, 1, lang);
}
+static htsmsg_t *
+deinterlace_field_rate_get_list( void *o, const char *lang )
+{
+ static const struct strtab tab[] = {
+ { N_("Frame Rate"), DEINT_RATE_FRAME },
+ { N_("Field Rate"), DEINT_RATE_FIELD },
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
+static htsmsg_t *
+deinterlace_enable_auto_get_list( void *o, const char *lang )
+{
+ static const struct strtab tab[] = {
+ { N_("Disable"), DEINT_AUTO_OFF },
+ { N_("Enable"), DEINT_AUTO_ON },
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
/* TVHCodec ================================================================= */
static int
tvh_codec_profile_video_open(TVHVideoCodecProfile *self, AVDictionary **opts)
{
- AV_DICT_SET_INT(opts, "tvh_filter_deint", self->deinterlace, 0);
// video_size
AV_DICT_SET_INT(opts, "width", self->size.num, 0);
AV_DICT_SET_INT(opts, "height", self->size.den, 0);
.ic_class = "codec_profile_video",
.ic_caption = N_("video"),
.ic_properties = (const property_t[]) {
- {
- .type = PT_BOOL,
- .id = "deinterlace",
- .name = N_("Deinterlace"),
- .desc = N_("Deinterlace."),
- .group = 2,
- .off = offsetof(TVHVideoCodecProfile, deinterlace),
- .set = codec_profile_video_class_deinterlace_set,
- .def.i = 1,
- },
{
.type = PT_INT,
.id = "height",
.list = hwaccel_get_list,
.def.i = 0,
},
+ {
+ .type = PT_BOOL,
+ .id = "deinterlace",
+ .name = N_("Deinterlace"),
+ .desc = N_("Deinterlace."),
+ .group = 2,
+ .off = offsetof(TVHVideoCodecProfile, deinterlace),
+ .set = codec_profile_video_class_deinterlace_set,
+ .def.i = 1,
+ },
+ {
+ .type = PT_INT,
+ .id = "deinterlace_field_rate",
+ .name = N_("Deinterlace rate type"),
+ .desc = N_("Frame rate combines the two interlaced fields to create a single frame. "
+ "Field rate processes each field independently, outputting as individual frames, "
+ "which enables higher temporal resolution by producing one frame per field. "
+ "Note: with field rate deinterlacing the resulting stream will have double "
+ "frame-rate (for example 25i becomes 50p), which can result in smoother video "
+ "since the original temporal properties of the interlaced video are retained."),
+ .group = 2,
+ .opts = PO_ADVANCED,
+ .off = offsetof(TVHVideoCodecProfile, deinterlace_field_rate),
+ .list = deinterlace_field_rate_get_list,
+ .def.i = DEINT_RATE_FRAME,
+ },
+ {
+ .type = PT_INT,
+ .id = "deinterlace_enable_auto",
+ .name = N_("Deinterlace fields only"),
+ .desc = N_("Enable this option to only deinterlace fields, passing progressive frames "
+ "unchanged. This is useful for mixed content, allowing progressive frames "
+ "to bypass deinterlacing for improved efficiency and quality."),
+ .group = 2,
+ .opts = PO_EXPERT,
+ .off = offsetof(TVHVideoCodecProfile, deinterlace_enable_auto),
+ .list = deinterlace_enable_auto_get_list,
+ .def.i = DEINT_AUTO_OFF,
+ },
{
.type = PT_INT,
.id = "pix_fmt",
int
hwaccels_get_deint_filter(AVCodecContext *avctx, char *filter, size_t filter_len)
{
- TVHContext *ctx = avctx->opaque;
+ const TVHContext *ctx = avctx->opaque;
if (ctx->hw_accel_ictx) {
switch (avctx->pix_fmt) {
break;
}
}
-
+
return -1;
}
int
vaapi_get_deint_filter(AVCodecContext *avctx, char *filter, size_t filter_len)
{
- snprintf(filter, filter_len, "deinterlace_vaapi");
+ const TVHContext *ctx = avctx->opaque;
+
+ // Map user selected rate (0=frame,1=field) to VAAPI rate (1=frame,2=field)
+ int rate = (((TVHVideoCodecProfile *)ctx->profile)->deinterlace_field_rate == 1) ? 2 : 1;
+ int enable_auto = ((TVHVideoCodecProfile *)ctx->profile)->deinterlace_enable_auto;
+ int mode = ((tvh_codec_profile_vaapi_t *)ctx->profile)->deinterlace_vaapi_mode;
+
+ if (str_snprintf(filter, filter_len, "deinterlace_vaapi=mode=%d:rate=%d:auto=%d",
+ mode, rate, enable_auto)) {
+ return -1;
+ }
+
return 0;
}
return 0;
}
+static int
+_video_get_sw_deint_filter(TVHContext *self, char *deint, size_t deint_len)
+{
+ if (str_snprintf(deint, deint_len, "yadif=mode=%d:deint=%d",
+ ((TVHVideoCodecProfile *)self->profile)->deinterlace_field_rate,
+ ((TVHVideoCodecProfile *)self->profile)->deinterlace_enable_auto )) {
+ return -1;
+ }
+ return 0;
+}
static int
_video_filters_get_filters(TVHContext *self, AVDictionary **opts, char **filters)
{
char download[48];
- char deint[8];
+ char deint[64];
char hw_deint[64];
char scale[24];
char hw_scale[64];
int ihw = _video_filters_hw_pix_fmt(self->iavctx->pix_fmt);
int ohw = _video_filters_hw_pix_fmt(self->oavctx->pix_fmt);
int filter_scale = (self->iavctx->height != self->oavctx->height);
- int filter_deint = 0, filter_download = 0, filter_upload = 0;
+ int filter_deint = ((TVHVideoCodecProfile *)self->profile)->deinterlace;
+ int filter_download = 0, filter_upload = 0;
#if ENABLE_HWACCELS
- int filter_denoise = 0;
- int filter_sharpness = 0;
-#endif
-
- if (tvh_context_get_int_opt(opts, "tvh_filter_deint", &filter_deint)) {
- return -1;
- }
-#if ENABLE_HWACCELS
- filter_denoise = ((TVHVideoCodecProfile *)self->profile)->filter_hw_denoise;
- filter_sharpness = ((TVHVideoCodecProfile *)self->profile)->filter_hw_sharpness;
+ int filter_denoise = ((TVHVideoCodecProfile *)self->profile)->filter_hw_denoise;
+ int filter_sharpness = ((TVHVideoCodecProfile *)self->profile)->filter_hw_sharpness;
#endif
// in --> out | download | upload
// -------------|-------------|------------
// when hwaccel is enabled we have two options:
if (ihw) {
// hw deint
- hwaccels_get_deint_filter(self->iavctx, hw_deint, sizeof(hw_deint));
+ if (hwaccels_get_deint_filter(self->iavctx, hw_deint, sizeof(hw_deint))) {
+ return -1;
+ }
}
else {
// sw deint
- if (str_snprintf(deint, sizeof(deint), "yadif")) {
+ if (_video_get_sw_deint_filter(self, deint, sizeof(deint))) {
return -1;
}
}
}
#else
if (filter_deint) {
- if (str_snprintf(deint, sizeof(deint), "yadif")) {
+ if (_video_get_sw_deint_filter(self, deint, sizeof(deint))) {
return -1;
}
}
tvh_video_context_open_encoder(TVHContext *self, AVDictionary **opts)
{
AVRational ticks_per_frame;
+ int deinterlace = ((TVHVideoCodecProfile *)self->profile)->deinterlace;
+ int field_rate = (deinterlace && ((TVHVideoCodecProfile *)self->profile)->deinterlace_field_rate) ? 2 : 1;
if (tvh_context_get_int_opt(opts, "pix_fmt", &self->oavctx->pix_fmt) ||
tvh_context_get_int_opt(opts, "width", &self->oavctx->width) ||
if (!self->iavctx->framerate.num) {
self->iavctx->framerate = av_make_q(30, 1);
}
- self->oavctx->framerate = self->iavctx->framerate;
- self->oavctx->ticks_per_frame = (90000 * self->iavctx->framerate.den) / self->iavctx->framerate.num; // We assume 90kHz as timebase which is mandatory for MPEG-TS
+ self->oavctx->framerate = av_mul_q(self->iavctx->framerate, (AVRational) { field_rate, 1 }); //take into account double rate i.e. field-based deinterlacers
+ self->oavctx->ticks_per_frame = (90000 * self->oavctx->framerate.den) / self->oavctx->framerate.num; // We assume 90kHz as timebase which is mandatory for MPEG-TS
ticks_per_frame = av_make_q(self->oavctx->ticks_per_frame, 1);
self->oavctx->time_base = av_inv_q(av_mul_q(
self->oavctx->framerate, ticks_per_frame));
self->oavctx->gop_size *= 3;
self->oavctx->sample_aspect_ratio = self->iavctx->sample_aspect_ratio;
+
+ tvh_context_log(self, LOG_DEBUG,
+ "Encoder configuration:\n"
+ " framerate: %d/%d (%.3f fps)\n"
+ " time_base: %.0fHz\n"
+ " frame duration: %" PRId64 " ticks (%.6f sec)\n"
+ " gop_size: %d\n"
+ " sample_aspect_ratio: %d/%d",
+ self->oavctx->framerate.num, self->oavctx->framerate.den, av_q2d(self->oavctx->framerate),
+ av_q2d(av_inv_q(self->oavctx->time_base)),
+ av_rescale_q(1, av_inv_q(self->oavctx->framerate), self->oavctx->time_base),
+ av_q2d(av_inv_q(self->oavctx->framerate)),
+ self->oavctx->gop_size,
+ self->oavctx->sample_aspect_ratio.num, self->oavctx->sample_aspect_ratio.den);
+
return 0;
}
static int
tvh_video_context_encode(TVHContext *self, AVFrame *avframe)
{
- avframe->pts = avframe->best_effort_timestamp;
+ if (!self || !self->oavctx) {
+ return -1;
+ }
+
+ AVFilterLink *outlink = NULL;
+#if LIBAVUTIL_VERSION_MAJOR >= 58
+ int64_t *frame_duration = &avframe->duration;
+#else
+ int64_t *frame_duration = &avframe->pkt_duration;
+#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) {
+
+ // 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,
+ 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
+ avframe->pkt_dts = avframe->pts;
+ }
+
+ 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,
+ 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
+ *frame_duration = av_rescale_q(1, av_inv_q(self->oavctx->framerate),
+ self->oavctx->time_base);
+ }
+
+ 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,
+ self->oavctx->time_base.num, self->oavctx->time_base.den,
+ avframe->pts, avframe->pkt_dts, *frame_duration);
+ }
+
if (avframe->pts <= self->pts) {
tvh_context_log(self, LOG_WARNING,
"Invalid pts (%"PRId64") <= last (%"PRId64"), dropping frame",
tvh_context_log(self, LOG_ERR, "encode failed");
return -1;
}
+
+ tvh_context_log(self, LOG_TRACE,
+ "Encoded packet for shipping: pts=%" PRId64 ", dts=%" PRId64 ", duration=%" PRId64,
+ avpkt->pts, avpkt->dts, avpkt->duration);
+
return avpkt->size;
}
form.findField('hw_denoise').setDisabled(!form.findField('hwaccel').getValue());
form.findField('hw_sharpness').setDisabled(!form.findField('hwaccel').getValue());
form.findField('hwaccel_details').setDisabled(!form.findField('hwaccel').getValue());
+ form.findField('deinterlace_field_rate').setDisabled(!form.findField('deinterlace').getValue());
+ form.findField('deinterlace_enable_auto').setDisabled(!form.findField('deinterlace').getValue());
+ form.findField('deinterlace_vaapi_mode').setDisabled(!form.findField('hwaccel').getValue() || !form.findField('deinterlace').getValue());
}
}
updateHWFilters(form);
});
+ // on deinterlace change
+ if (form.findField('deinterlace'))
+ form.findField('deinterlace').on('check', function(checkbox, value) {
+ updateHWFilters(form);
+ });
+
// on desired_b_depth change
if (form.findField('desired_b_depth'))
form.findField('desired_b_depth').on('spin', function(spinner, direction, eOpts) {