From f31581544c74989c7403d5248fdf05657217303f Mon Sep 17 00:00:00 2001 From: Jochen Henneberg Date: Thu, 10 Oct 2024 17:22:34 +0200 Subject: [PATCH] va: Added VP8 encoder Fixes #3430 Fixes #3576 Part-of: --- .../gst-plugins-bad/sys/va/gstvavp8enc.c | 1493 +++++++++++++++++ .../gst-plugins-bad/sys/va/gstvavp8enc.h | 34 + .../gst-plugins-bad/sys/va/meson.build | 1 + subprojects/gst-plugins-bad/sys/va/plugin.c | 8 + 4 files changed, 1536 insertions(+) create mode 100644 subprojects/gst-plugins-bad/sys/va/gstvavp8enc.c create mode 100644 subprojects/gst-plugins-bad/sys/va/gstvavp8enc.h diff --git a/subprojects/gst-plugins-bad/sys/va/gstvavp8enc.c b/subprojects/gst-plugins-bad/sys/va/gstvavp8enc.c new file mode 100644 index 0000000000..88d79a9599 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/va/gstvavp8enc.c @@ -0,0 +1,1493 @@ +/* GStreamer + * Copyright (C) 2024 Centricular Ltd + * Author: Jochen Henneberg + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-vavp8enc + * @title: vavp8enc + * @short_description: A VA-API based VP8 video encoder + * + * vavp8enc encodes raw video VA surfaces into VP8 bitstreams using + * the installed and chosen [VA-API](https://01.org/linuxmedia/vaapi) + * driver. + * + * The raw video frames in main memory can be imported into VA surfaces. + * + * ## Example launch line + * ``` + * gst-launch-1.0 videotestsrc num-buffers=60 ! timeoverlay ! vavp8enc ! mp4mux ! filesink location=test.mp4 + * ``` + * + * Since: 1.26 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstvavp8enc.h" + +#include +#include + +#include "gstvabaseenc.h" +#include "gstvapluginutils.h" + +GST_DEBUG_CATEGORY_STATIC (gst_va_vp8enc_debug); +#define GST_CAT_DEFAULT gst_va_vp8enc_debug + +#define GST_VA_VP8_ENC(obj) ((GstVaVp8Enc *) obj) +#define GST_VA_VP8_ENC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_FROM_INSTANCE (obj), GstVaVp8EncClass)) +#define GST_VA_VP8_ENC_CLASS(klass) ((GstVaVp8EncClass *) klass) + +typedef struct _GstVaVp8Enc GstVaVp8Enc; +typedef struct _GstVaVp8EncClass GstVaVp8EncClass; +typedef struct _GstVaVp8EncFrame GstVaVp8EncFrame; +typedef struct _GstVaVp8GFGroup GstVaVp8GFGroup; + +enum +{ + PROP_KEYFRAME_INT = 1, + PROP_BITRATE, + PROP_TARGET_PERCENTAGE, + PROP_TARGET_USAGE, + PROP_CPB_SIZE, + PROP_MBBRC, + PROP_QP, + PROP_MIN_QP, + PROP_MAX_QP, + PROP_LOOP_FILTER_LEVEL, + PROP_SHARPNESS_LEVEL, + PROP_RATE_CONTROL, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES]; + +static GstObjectClass *parent_class = NULL; + +#define DEFAULT_BASE_QINDEX 60 +#define DEFAULT_TARGET_PERCENTAGE 66 +#define DEFAULT_LOOP_FILTER_LEVEL 10 + +#define MAX_FRAME_WIDTH 4096 +#define MAX_FRAME_HEIGHT 4096 +#define MAX_KEY_FRAME_INTERVAL 1024 + +#define FRAME_TYPE_INVALID -1 +#define FRAME_NUM_INVALID -1 + +struct _GstVaVp8EncFrame +{ + GstVaEncFrame base; + GstVp8FrameType type; + gint frame_num; +}; + +struct _GstVaVp8EncClass +{ + GstVaBaseEncClass parent_class; + + GType rate_control_type; + char rate_control_type_name[64]; + GEnumValue rate_control[16]; +}; + +struct _GstVaVp8Enc +{ + /*< private > */ + GstVaBaseEnc parent; + + /* properties */ + struct + { + /* kbps */ + guint bitrate; + /* VA_RC_XXX */ + guint32 rc_ctrl; + guint32 cpb_size; + guint32 target_percentage; + guint32 target_usage; + guint keyframe_interval; + guint32 qp; + guint32 min_qp; + guint32 max_qp; + guint32 mbbrc; + gint32 filter_level; + guint32 sharpness_level; + } prop; + + struct + { + guint keyframe_interval; + gint frame_num; + /* Only one reference frame is kept here thought VP8 has support + for golden and alternate reference frames. This is for + simplicity and because the decision for golden/altref frames + without manual interaction with the codec and knowledge of the + frame content does not seem meaningful. */ + GstVideoCodecFrame *last_ref; + } gop; + + struct + { + guint target_usage; + guint32 target_percentage; + guint32 cpb_size; + guint32 cpb_length_bits; + guint32 rc_ctrl_mode; + guint max_bitrate; + guint max_bitrate_bits; + guint target_bitrate; + guint target_bitrate_bits; + guint32 base_qindex; + guint32 min_qindex; + guint32 max_qindex; + guint32 mbbrc; + gint32 filter_level; + guint32 sharpness_level; + } rc; +}; + +static GstVaVp8EncFrame * +gst_va_vp8_enc_frame_new (void) +{ + GstVaVp8EncFrame *frame; + + frame = g_new (GstVaVp8EncFrame, 1); + frame->type = FRAME_TYPE_INVALID; + frame->base.picture = NULL; + frame->frame_num = FRAME_NUM_INVALID; + + return frame; +} + +static void +gst_va_vp8_enc_frame_free (gpointer pframe) +{ + GstVaVp8EncFrame *frame = pframe; + + g_clear_pointer (&frame->base.picture, gst_va_encode_picture_free); + g_free (frame); +} + +static gboolean +gst_va_vp8_enc_new_frame (GstVaBaseEnc * base, GstVideoCodecFrame * frame) +{ + GstVaVp8EncFrame *frame_in; + + frame_in = gst_va_vp8_enc_frame_new (); + gst_va_set_enc_frame (frame, (GstVaEncFrame *) frame_in, + gst_va_vp8_enc_frame_free); + + return TRUE; +} + +static inline GstVaVp8EncFrame * +_enc_frame (GstVideoCodecFrame * frame) +{ + GstVaVp8EncFrame *enc_frame = gst_video_codec_frame_get_user_data (frame); + + g_assert (enc_frame); + + return enc_frame; +} + +static void +_update_ref_frame (GstVaVp8Enc * self, GstVideoCodecFrame ** ref_frame, + GstVideoCodecFrame * frame) +{ + if (*ref_frame) + gst_video_codec_frame_unref (*ref_frame); + + if (frame) { + *ref_frame = gst_video_codec_frame_ref (frame); + } else { + *ref_frame = NULL; + } +} + +static gboolean +gst_va_vp8_enc_reorder_frame (GstVaBaseEnc * base, GstVideoCodecFrame * frame, + gboolean bump_all, GstVideoCodecFrame ** out_frame) +{ + GstVaVp8Enc *self = GST_VA_VP8_ENC (base); + GstVaVp8EncFrame *va_frame; + + if (bump_all) { + g_return_val_if_fail (frame == NULL, FALSE); + _update_ref_frame (self, &self->gop.last_ref, NULL); + self->gop.frame_num = FRAME_NUM_INVALID; + goto out; + } + + /* No reorder - if there is no new frame there will be no new output + frame. */ + if (frame == NULL) + goto out; + + va_frame = _enc_frame (frame); + self->gop.frame_num++; + + if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) + self->gop.frame_num = 0; + + if (self->gop.frame_num == self->gop.keyframe_interval) + self->gop.frame_num = 0; + + if (self->gop.frame_num == 0) { + va_frame->type = GST_VP8_KEY_FRAME; + _update_ref_frame (self, &self->gop.last_ref, NULL); + } else { + va_frame->type = GST_VP8_INTER_FRAME; + } + + va_frame->frame_num = self->gop.frame_num; + *out_frame = frame; + + GST_LOG_OBJECT (self, "pop frame: system_frame_number %d," + " frame_num: %d, frame_type %s", (*out_frame)->system_frame_number, + va_frame->frame_num, va_frame->type ? "Inter" : "Intra"); + +out: + return TRUE; +} + +static void +gst_va_vp8_enc_reset_state (GstVaBaseEnc * base) +{ + GstVaVp8Enc *self = GST_VA_VP8_ENC (base); + + GST_VA_BASE_ENC_CLASS (parent_class)->reset_state (base); + + GST_OBJECT_LOCK (self); + self->rc.rc_ctrl_mode = self->prop.rc_ctrl; + self->rc.target_usage = self->prop.target_usage; + self->rc.base_qindex = self->prop.qp; + self->rc.min_qindex = self->prop.min_qp; + self->rc.max_qindex = self->prop.max_qp; + self->rc.target_percentage = self->prop.target_percentage; + self->rc.cpb_size = self->prop.cpb_size; + self->rc.mbbrc = self->prop.mbbrc; + self->rc.filter_level = self->prop.filter_level; + self->rc.sharpness_level = self->prop.sharpness_level; + + self->gop.keyframe_interval = self->prop.keyframe_interval; + self->gop.frame_num = FRAME_NUM_INVALID; + GST_OBJECT_UNLOCK (self); + + self->rc.max_bitrate = 0; + self->rc.target_bitrate = 0; + self->rc.max_bitrate_bits = 0; + self->rc.cpb_length_bits = 0; +} + +#define update_property(type, obj, old_val, new_val, prop_id) \ + gst_va_base_enc_update_property_##type (obj, old_val, new_val, properties[prop_id]) +#define update_property_uint(obj, old_val, new_val, prop_id) \ + update_property (uint, obj, old_val, new_val, prop_id) + +static gboolean +_vp8_generate_gop_structure (GstVaVp8Enc * self) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + /* If not set, generate a key frame every 2 seconds. */ + if (self->gop.keyframe_interval == 0) { + self->gop.keyframe_interval = (2 * GST_VIDEO_INFO_FPS_N (&base->in_info) + + GST_VIDEO_INFO_FPS_D (&base->in_info) - 1) / + GST_VIDEO_INFO_FPS_D (&base->in_info); + } + + if (self->gop.keyframe_interval > MAX_KEY_FRAME_INTERVAL) + self->gop.keyframe_interval = MAX_KEY_FRAME_INTERVAL; + + update_property_uint (base, &self->prop.keyframe_interval, + self->gop.keyframe_interval, PROP_KEYFRAME_INT); + + return TRUE; +} + +static void +_vp8_calculate_coded_size (GstVaVp8Enc * self) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + gint width = GST_ROUND_UP_16 (base->width); + gint height = GST_ROUND_UP_16 (base->height); + + /* Safe choice, 2 times 4:2:0 framesize plus header. */ + base->codedbuf_size = 3 * width * height + 1278; + GST_INFO_OBJECT (self, "Calculate codedbuf size: %u", base->codedbuf_size); +} + +/* Normalizes bitrate (and CPB size) for HRD conformance. */ +static void +_vp8_calculate_bitrate_hrd (GstVaVp8Enc * self) +{ + self->rc.max_bitrate_bits = self->rc.max_bitrate * 1000; + GST_DEBUG_OBJECT (self, "Max bitrate: %u bits/sec", + self->rc.max_bitrate_bits); + + self->rc.target_bitrate_bits = self->rc.target_bitrate * 1000; + GST_DEBUG_OBJECT (self, "Target bitrate: %u bits/sec", + self->rc.target_bitrate_bits); + + if (self->rc.cpb_size > 0 && self->rc.cpb_size < (self->rc.max_bitrate / 2)) { + GST_INFO_OBJECT (self, "Too small cpb_size: %d", self->rc.cpb_size); + + /* Cache 2s coded data by default. */ + self->rc.cpb_size = self->rc.max_bitrate * 2; + GST_INFO_OBJECT (self, "Adjust cpb_size to: %d", self->rc.cpb_size); + } else if (self->rc.cpb_size == 0) { + self->rc.cpb_size = self->rc.target_bitrate; + } + + self->rc.cpb_length_bits = self->rc.cpb_size * 1000; + GST_DEBUG_OBJECT (self, "HRD CPB size: %u bits", self->rc.cpb_length_bits); +} + +static guint +_vp8_adjust_loopfilter_level_based_on_qindex (guint qindex) +{ + /* This magic has been copied from the vp9 encoder. */ + if (qindex >= 40) { + return (gint32) (-18.98682 + 0.3967082 * (gfloat) qindex + + 0.0005054 * pow ((float) qindex - 127.5, 2) - + 9.692e-6 * pow ((float) qindex - 127.5, 3)); + } else { + return qindex / 4; + } +} + +/* Estimates a good enough bitrate if none was supplied. */ +static gboolean +_vp8_ensure_rate_control (GstVaVp8Enc * self) +{ + /* User can specify the properties of: "bitrate", "target-percentage", + * "max-qp", "min-qp", "qp", "loop-filter-level", "sharpness-level", + * "mbbrc", "cpb-size", "rate-control" and "target-usage" to control + * the RC behavior. + * + * "target-usage" is different from the others, it controls the encoding + * speed and quality, while the others control encoding bit rate and + * quality. The lower value has better quality(maybe bigger MV search + * range) but slower speed, the higher value has faster speed but lower + * quality. It is valid for all modes. + * + * The possible composition to control the bit rate and quality: + * + * 1. CQP mode: "rate-control=cqp", then "qp"(the qindex in VP8) specify + * the QP of frames(within the "max-qp" and "min-qp" range). The QP + * will not change during the whole stream. "loop-filter-level" and + * "sharpness-level" together determine how much the filtering can + * change the sample values. Other properties related to rate control + * are ignored. + * + * 2. CBR mode: "rate-control=CBR", then the "bitrate" specify the + * target bit rate and the "cpb-size" specifies the max coded + * picture buffer size to avoid overflow. If the "bitrate" is not + * set, it is calculated by the picture resolution and frame + * rate. If "cpb-size" is not set, it is set to the size of + * caching 2 second coded data. Encoder will try its best to make + * the QP with in the ["max-qp", "min-qp"] range. "mbbrc" can + * enable bit rate control in macro block level. Other paramters + * are ignored. + * + * 3. VBR mode: "rate-control=VBR", then the "bitrate" specify the + * target bit rate, "target-percentage" is used to calculate the + * max bit rate of VBR mode by ("bitrate" * 100) / + * "target-percentage". It is also used by driver to calculate + * the min bit rate. The "cpb-size" specifies the max coded + * picture buffer size to avoid overflow. If the "bitrate" is not + * set, the target bit rate will be calculated by the picture + * resolution and frame rate. Encoder will try its best to make + * the QP with in the ["max-qp", "min-qp"] range. "mbbrc" can + * enable bit rate control in macro block level. Other paramters + * are ignored. + */ + + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + guint bitrate; + guint32 rc_ctrl, rc_mode, quality_level; + + quality_level = gst_va_encoder_get_quality_level (base->encoder, + base->profile, GST_VA_BASE_ENC_ENTRYPOINT (base)); + if (self->rc.target_usage > quality_level) { + GST_INFO_OBJECT (self, "User setting target-usage: %d is not supported, " + "fallback to %d", self->rc.target_usage, quality_level); + self->rc.target_usage = quality_level; + + update_property_uint (base, &self->prop.target_usage, self->rc.target_usage, + PROP_TARGET_USAGE); + } + + GST_OBJECT_LOCK (self); + rc_ctrl = self->prop.rc_ctrl; + GST_OBJECT_UNLOCK (self); + + if (rc_ctrl != VA_RC_NONE) { + rc_mode = gst_va_encoder_get_rate_control_mode (base->encoder, + base->profile, GST_VA_BASE_ENC_ENTRYPOINT (base)); + + if (!(rc_mode & rc_ctrl)) { + guint32 defval = + G_PARAM_SPEC_ENUM (properties[PROP_RATE_CONTROL])->default_value; + GST_INFO_OBJECT (self, "The rate control mode %i is not supported, " + "fallback to %i mode", rc_ctrl, defval); + self->rc.rc_ctrl_mode = defval; + + update_property_uint (base, &self->prop.rc_ctrl, self->rc.rc_ctrl_mode, + PROP_RATE_CONTROL); + } + } else { + self->rc.rc_ctrl_mode = VA_RC_NONE; + } + + if (self->rc.min_qindex > self->rc.max_qindex) { + GST_INFO_OBJECT (self, "The min_qindex %d is bigger than the max_qindex" + " %d, set it to the max_qindex", self->rc.min_qindex, + self->rc.max_qindex); + self->rc.min_qindex = self->rc.max_qindex; + + update_property_uint (base, &self->prop.min_qp, self->rc.min_qindex, + PROP_MIN_QP); + } + + /* Make the qp in the valid range. */ + if (self->rc.base_qindex < self->rc.min_qindex) { + if (self->rc.base_qindex != DEFAULT_BASE_QINDEX) + GST_INFO_OBJECT (self, "The base_qindex %d is smaller than the" + " min_qindex %d, set it to the min_qindex", self->rc.base_qindex, + self->rc.min_qindex); + self->rc.base_qindex = self->rc.min_qindex; + } + if (self->rc.base_qindex > self->rc.max_qindex) { + if (self->rc.base_qindex != DEFAULT_BASE_QINDEX) + GST_INFO_OBJECT (self, "The base_qindex %d is bigger than the" + " max_qindex %d, set it to the max_qindex", self->rc.base_qindex, + self->rc.max_qindex); + self->rc.base_qindex = self->rc.max_qindex; + } + + /* Calculate the loop filter level. */ + if (self->rc.rc_ctrl_mode == VA_RC_CQP) { + if (self->rc.filter_level == -1) + self->rc.filter_level = + _vp8_adjust_loopfilter_level_based_on_qindex (self->rc.base_qindex); + } + + GST_OBJECT_LOCK (self); + bitrate = self->prop.bitrate; + GST_OBJECT_UNLOCK (self); + + /* Calculate a bitrate if it is not set. */ + if ((self->rc.rc_ctrl_mode == VA_RC_CBR || self->rc.rc_ctrl_mode == VA_RC_VBR) + && bitrate == 0) { + guint64 factor; + guint bits_per_pix; + + bits_per_pix = 24; + factor = (guint64) base->width * base->height * bits_per_pix / 16; + bitrate = gst_util_uint64_scale (factor, + GST_VIDEO_INFO_FPS_N (&base->in_info), + GST_VIDEO_INFO_FPS_D (&base->in_info)) / 1000; + + GST_INFO_OBJECT (self, "target bitrate computed to %u kbps", bitrate); + } + + /* Adjust the setting based on RC mode. */ + switch (self->rc.rc_ctrl_mode) { + case VA_RC_NONE: + case VA_RC_CQP: + bitrate = 0; + self->rc.max_bitrate = 0; + self->rc.target_bitrate = 0; + self->rc.target_percentage = 0; + self->rc.cpb_size = 0; + self->rc.mbbrc = 0; + break; + case VA_RC_CBR: + self->rc.max_bitrate = bitrate; + self->rc.target_bitrate = bitrate; + self->rc.target_percentage = 100; + self->rc.base_qindex = DEFAULT_BASE_QINDEX; + self->rc.filter_level = DEFAULT_LOOP_FILTER_LEVEL; + self->rc.sharpness_level = 0; + break; + case VA_RC_VBR: + self->rc.base_qindex = DEFAULT_BASE_QINDEX; + self->rc.target_percentage = MAX (10, self->rc.target_percentage); + self->rc.max_bitrate = (guint) gst_util_uint64_scale_int (bitrate, + 100, self->rc.target_percentage); + self->rc.target_bitrate = bitrate; + self->rc.filter_level = DEFAULT_LOOP_FILTER_LEVEL; + self->rc.sharpness_level = 0; + break; + default: + GST_WARNING_OBJECT (self, "Unsupported rate control"); + return FALSE; + break; + } + + GST_DEBUG_OBJECT (self, "Max bitrate: %u kbps, target bitrate: %u kbps", + self->rc.max_bitrate, self->rc.target_bitrate); + + if (self->rc.rc_ctrl_mode == VA_RC_CBR || self->rc.rc_ctrl_mode == VA_RC_VBR) + _vp8_calculate_bitrate_hrd (self); + + /* update & notifications */ + update_property_uint (base, &self->prop.bitrate, bitrate, PROP_BITRATE); + update_property_uint (base, &self->prop.cpb_size, self->rc.cpb_size, + PROP_CPB_SIZE); + update_property_uint (base, &self->prop.target_percentage, + self->rc.target_percentage, PROP_TARGET_PERCENTAGE); + update_property_uint (base, &self->prop.qp, self->rc.base_qindex, PROP_QP); + update_property_uint (base, ((guint *) (&self->prop.filter_level)), + self->rc.filter_level, PROP_LOOP_FILTER_LEVEL); + update_property_uint (base, &self->prop.sharpness_level, + self->rc.sharpness_level, PROP_SHARPNESS_LEVEL); + update_property_uint (base, &self->prop.mbbrc, self->rc.mbbrc, PROP_MBBRC); + + return TRUE; +} + +static gboolean +gst_va_vp8_enc_reconfig (GstVaBaseEnc * base) +{ + GstVaBaseEncClass *klass = GST_VA_BASE_ENC_GET_CLASS (base); + GstVideoEncoder *venc = GST_VIDEO_ENCODER (base); + GstVaVp8Enc *self = GST_VA_VP8_ENC (base); + GstCaps *out_caps, *reconf_caps = NULL; + GstVideoCodecState *output_state; + GstVideoFormat format, reconf_format = GST_VIDEO_FORMAT_UNKNOWN; + const GstVideoFormatInfo *format_info; + gboolean do_renegotiation = TRUE, do_reopen, need_negotiation; + guint max_ref_frames, max_surfaces = 0, codedbuf_size, latency_num; + gint width, height; + GstClockTime latency; + + width = GST_VIDEO_INFO_WIDTH (&base->in_info); + height = GST_VIDEO_INFO_HEIGHT (&base->in_info); + format = GST_VIDEO_INFO_FORMAT (&base->in_info); + codedbuf_size = base->codedbuf_size; + latency_num = base->preferred_output_delay; + + /* VP8 only support 4:2:0 formats so check that first */ + format_info = gst_video_format_get_info (format); + if (GST_VIDEO_FORMAT_INFO_W_SUB (format_info, 1) != 1 || + GST_VIDEO_FORMAT_INFO_H_SUB (format_info, 1) != 1) + return FALSE; + + need_negotiation = + !gst_va_encoder_get_reconstruct_pool_config (base->encoder, &reconf_caps, + &max_surfaces); + + if (!need_negotiation && reconf_caps) { + GstVideoInfo vi; + if (!gst_video_info_from_caps (&vi, reconf_caps)) + return FALSE; + reconf_format = GST_VIDEO_INFO_FORMAT (&vi); + } + + /* First check */ + do_reopen = !(format == reconf_format && width == base->width + && height == base->height && self->prop.rc_ctrl == self->rc.rc_ctrl_mode); + + if (do_reopen && gst_va_encoder_is_open (base->encoder)) + gst_va_encoder_close (base->encoder); + + gst_va_base_enc_reset_state (base); + + if (base->is_live) { + base->preferred_output_delay = 0; + } else { + base->preferred_output_delay = 1; + } + + base->profile = VAProfileVP8Version0_3; + base->width = width; + base->height = height; + + /* Frame rate is needed for rate control and PTS setting. */ + if (GST_VIDEO_INFO_FPS_N (&base->in_info) == 0 + || GST_VIDEO_INFO_FPS_D (&base->in_info) == 0) { + GST_INFO_OBJECT (self, "Unknown framerate, just set to 30 fps"); + GST_VIDEO_INFO_FPS_N (&base->in_info) = 30; + GST_VIDEO_INFO_FPS_D (&base->in_info) = 1; + } + base->frame_duration = gst_util_uint64_scale (GST_SECOND, + GST_VIDEO_INFO_FPS_D (&base->in_info), + GST_VIDEO_INFO_FPS_N (&base->in_info)); + + GST_DEBUG_OBJECT (self, "resolution:%dx%d, frame duration is %" + GST_TIME_FORMAT, base->width, base->height, + GST_TIME_ARGS (base->frame_duration)); + + if (!_vp8_ensure_rate_control (self)) + return FALSE; + + if (!_vp8_generate_gop_structure (self)) + return FALSE; + + _vp8_calculate_coded_size (self); + + /* Let the downstream know the new latency. */ + if (latency_num != base->preferred_output_delay + 1) { + need_negotiation = TRUE; + latency_num = base->preferred_output_delay + 1; + } + + /* Set the latency */ + latency = gst_util_uint64_scale (latency_num, + GST_VIDEO_INFO_FPS_D (&base->input_state->info) * GST_SECOND, + GST_VIDEO_INFO_FPS_N (&base->input_state->info)); + gst_video_encoder_set_latency (venc, latency, latency); + + max_ref_frames = GST_VP8_MAX_REF_FRAMES; + max_ref_frames += base->preferred_output_delay; + base->min_buffers = max_ref_frames; + max_ref_frames += 3; /* scratch frames */ + + /* Second check after calculations. */ + do_reopen |= !(codedbuf_size == base->codedbuf_size); + if (do_reopen && gst_va_encoder_is_open (base->encoder)) + gst_va_encoder_close (base->encoder); + + if (!gst_va_encoder_is_open (base->encoder) + && !gst_va_encoder_open (base->encoder, base->profile, + GST_VIDEO_INFO_FORMAT (&base->in_info), base->rt_format, + base->width, base->height, base->codedbuf_size, + max_ref_frames, self->rc.rc_ctrl_mode, 0)) { + GST_ERROR_OBJECT (self, "Failed to open the VA encoder."); + return FALSE; + } + + /* Add some tags. */ + gst_va_base_enc_add_codec_tag (base, "VP8"); + + out_caps = gst_va_profile_caps (base->profile, klass->entrypoint); + g_assert (out_caps); + out_caps = gst_caps_fixate (out_caps); + + gst_caps_set_simple (out_caps, "width", G_TYPE_INT, base->width, + "height", G_TYPE_INT, base->height, NULL); + + if (!need_negotiation) { + output_state = gst_video_encoder_get_output_state (venc); + do_renegotiation = TRUE; + if (output_state) { + do_renegotiation = !gst_caps_is_subset (output_state->caps, out_caps); + gst_video_codec_state_unref (output_state); + } + if (!do_renegotiation) { + gst_caps_unref (out_caps); + return TRUE; + } + } + + GST_DEBUG_OBJECT (self, "output caps is %" GST_PTR_FORMAT, out_caps); + + output_state = + gst_video_encoder_set_output_state (venc, out_caps, base->input_state); + gst_video_codec_state_unref (output_state); + + if (!gst_video_encoder_negotiate (venc)) { + GST_ERROR_OBJECT (self, "Failed to negotiate with the downstream"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_va_vp8_enc_flush (GstVideoEncoder * venc) +{ + GstVaVp8Enc *self = GST_VA_VP8_ENC (venc); + + _update_ref_frame (self, &self->gop.last_ref, NULL); + self->gop.frame_num = FRAME_NUM_INVALID; + + return GST_VIDEO_ENCODER_CLASS (parent_class)->flush (venc); +} + +static void +_vp8_fill_sequence_param (GstVaVp8Enc * self, + VAEncSequenceParameterBufferVP8 * sequence) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + /* *INDENT-OFF* */ + *sequence = (VAEncSequenceParameterBufferVP8) { + .frame_width = base->width, + .frame_height = base->height, + .frame_width_scale = 0, + .frame_height_scale = 0, + .error_resilient = 0, + .kf_auto = 0, + .kf_min_dist = 0, + .kf_max_dist = 0, + .bits_per_second = self->rc.target_bitrate_bits, + .intra_period = self->gop.keyframe_interval, + .reference_frames = {VA_INVALID_SURFACE, VA_INVALID_SURFACE, + VA_INVALID_SURFACE, VA_INVALID_SURFACE}, + }; + /* *INDENT-ON* */ +} + +static gboolean +_vp8_add_sequence_param (GstVaVp8Enc * self, GstVaEncodePicture * picture, + VAEncSequenceParameterBufferVP8 * sequence) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + if (!gst_va_encoder_add_param (base->encoder, picture, + VAEncSequenceParameterBufferType, sequence, sizeof (*sequence))) { + GST_ERROR_OBJECT (self, "Failed to create the sequence parameter"); + return FALSE; + } + + return TRUE; +} + +static gboolean +_vp8_fill_quant_param (GstVaVp8Enc * self, GstVaVp8EncFrame * va_frame, + VAQMatrixBufferVP8 * quant_param) +{ + int i, q; + + q = self->rc.base_qindex; + /* A hint for the driver to use a higher qindex for the key frame */ + if (va_frame->type == GST_VP8_KEY_FRAME) + q = MIN (q + 5, self->rc.max_qindex); + + for (i = 0; i < 4; i++) + quant_param->quantization_index[i] = q; + for (i = 0; i < 5; i++) + quant_param->quantization_index_delta[i] = 0; + + return TRUE; +} + +static gboolean +_vp8_fill_frame_param (GstVaVp8Enc * self, GstVaVp8EncFrame * va_frame, + VAEncPictureParameterBufferVP8 * pic_param) +{ + /* *INDENT-OFF* */ + *pic_param = (VAEncPictureParameterBufferVP8) { + .reconstructed_frame = + gst_va_encode_picture_get_reconstruct_surface (va_frame->base.picture), + /* Set it later for inter frame. */ + .ref_last_frame = VA_INVALID_SURFACE, + .ref_gf_frame = VA_INVALID_SURFACE, + .ref_arf_frame = VA_INVALID_SURFACE, + + .coded_buf = va_frame->base.picture->coded_buffer, + .ref_flags.bits = { + .force_kf = (va_frame->type == GST_VP8_KEY_FRAME), + /* Set all the refs later if inter frame. */ + .no_ref_last = (va_frame->type == GST_VP8_KEY_FRAME), + .no_ref_gf = (va_frame->type == GST_VP8_KEY_FRAME), + .no_ref_arf = (va_frame->type == GST_VP8_KEY_FRAME), + .temporal_id = 0, + .first_ref = 0, + .second_ref = 0, + .reserved = 0, + }, + .pic_flags.bits = { + .frame_type = (va_frame->type == GST_VP8_INTER_FRAME), + .version = 0, /* bicubic */ + .show_frame = 1, + .color_space = 0, + .recon_filter_type = 0, /* bicubic */ + .loop_filter_type = 0, + .auto_partitions = 0, + .num_token_partitions = 0, + .clamping_type = 0, + .segmentation_enabled = 0, + .update_mb_segmentation_map = 0, + .update_segment_feature_data = 0, + .loop_filter_adj_enable = 0, + .refresh_entropy_probs = 0, + .refresh_golden_frame = 1, + .refresh_alternate_frame = 1, + .refresh_last = 1, + .copy_buffer_to_golden = 0, + .copy_buffer_to_alternate = 0, + .sign_bias_golden = 0, + .sign_bias_alternate = 0, + .mb_no_coeff_skip = 0, + .forced_lf_adjustment = (va_frame->type == GST_VP8_INTER_FRAME), + }, + .loop_filter_level = {0, }, /* set later */ + .ref_lf_delta = {0, }, + .mode_lf_delta = {0, }, + .sharpness_level = self->rc.sharpness_level, + .clamp_qindex_high = 127, + .clamp_qindex_low = 0, + .va_reserved = {0, } + }; + /* *INDENT-ON* */ + + for (gint i = 0; i < 4; ++i) + pic_param->loop_filter_level[i] = self->rc.filter_level; + + if (va_frame->type == GST_VP8_INTER_FRAME) { + g_assert (self->gop.last_ref != NULL); + + pic_param->ref_last_frame = gst_va_encode_picture_get_reconstruct_surface + (_enc_frame (self->gop.last_ref)->base.picture); + pic_param->ref_gf_frame = pic_param->ref_arf_frame = + pic_param->ref_last_frame; + } + + return TRUE; +} + +static gboolean +_vp8_encode_frame (GstVaVp8Enc * self, GstVaVp8EncFrame * va_frame) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + VAEncPictureParameterBufferVP8 pic_param; + VAQMatrixBufferVP8 quant_param; + + if (!_vp8_fill_frame_param (self, va_frame, &pic_param)) { + GST_ERROR_OBJECT (self, "Fails to fill the frame parameter."); + return FALSE; + } + + if (!gst_va_encoder_add_param (base->encoder, va_frame->base.picture, + VAEncPictureParameterBufferType, &pic_param, sizeof (pic_param))) { + GST_ERROR_OBJECT (self, "Failed to create the frame parameter"); + return FALSE; + } + + if (!_vp8_fill_quant_param (self, va_frame, &quant_param)) { + GST_ERROR_OBJECT (self, "Fails to fill the quantization parameter."); + return FALSE; + } + + if (!gst_va_encoder_add_param (base->encoder, va_frame->base.picture, + VAQMatrixBufferType, &quant_param, sizeof (quant_param))) { + GST_ERROR_OBJECT (self, "Failed to create the quantization parameter"); + return FALSE; + } + + if (!gst_va_encoder_encode (base->encoder, va_frame->base.picture)) { + GST_ERROR_OBJECT (self, "Encode frame error"); + return FALSE; + } + + return TRUE; +} + +static GstFlowReturn +gst_va_vp8_enc_encode_frame (GstVaBaseEnc * base, + GstVideoCodecFrame * gst_frame, gboolean is_last) +{ + GstVaVp8Enc *self = GST_VA_VP8_ENC (base); + GstVaVp8EncFrame *va_frame = _enc_frame (gst_frame); + VAEncSequenceParameterBufferVP8 seq_param; + + GST_LOG_OBJECT (self, "Encode frame."); + + g_assert (va_frame->base.picture == NULL); + va_frame->base.picture = gst_va_encode_picture_new (base->encoder, + gst_frame->input_buffer); + + if (va_frame->frame_num == 0) { + _vp8_fill_sequence_param (self, &seq_param); + if (!_vp8_add_sequence_param (self, va_frame->base.picture, &seq_param)) + return GST_FLOW_ERROR; + + if (!gst_va_base_enc_add_rate_control_parameter (base, + va_frame->base.picture, self->rc.rc_ctrl_mode, + self->rc.max_bitrate_bits, self->rc.target_percentage, + self->rc.base_qindex, self->rc.min_qindex, self->rc.max_qindex, + self->rc.mbbrc)) + return GST_FLOW_ERROR; + + if (!gst_va_base_enc_add_quality_level_parameter (base, + va_frame->base.picture, self->rc.target_usage)) + return GST_FLOW_ERROR; + + if (!gst_va_base_enc_add_frame_rate_parameter (base, + va_frame->base.picture)) + return GST_FLOW_ERROR; + + if (!gst_va_base_enc_add_hrd_parameter (base, va_frame->base.picture, + self->rc.rc_ctrl_mode, self->rc.cpb_length_bits)) + return GST_FLOW_ERROR; + } + + if (!_vp8_encode_frame (self, va_frame)) { + GST_ERROR_OBJECT (self, "Fails to encode one frame."); + return GST_FLOW_ERROR; + } + + /* The last frame will always change to this. */ + _update_ref_frame (self, &self->gop.last_ref, gst_frame); + + g_queue_push_tail (&base->output_list, gst_video_codec_frame_ref (gst_frame)); + return GST_FLOW_OK; +} + +static gboolean +gst_va_vp8_enc_prepare_output (GstVaBaseEnc * base, + GstVideoCodecFrame * frame, gboolean * complete) +{ + GstVaVp8EncFrame *frame_enc; + GstBuffer *buf; + + frame_enc = _enc_frame (frame); + + GST_LOG_OBJECT (base, "Prepare to output: frame system_frame_number: %d," + "frame_num: %d, frame type: %s", frame->system_frame_number, + frame_enc->frame_num, frame_enc->type ? "Inter" : "Intra"); + + buf = gst_va_base_enc_create_output_buffer (base, frame_enc->base.picture, + NULL, 0); + if (!buf) { + GST_ERROR_OBJECT (base, "Failed to create output buffer"); + return FALSE; + } + + *complete = TRUE; + + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_MARKER); + if (frame_enc->frame_num == 0) { + GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame); + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + } else { + GST_VIDEO_CODEC_FRAME_UNSET_SYNC_POINT (frame); + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + } + + gst_buffer_replace (&frame->output_buffer, buf); + gst_clear_buffer (&buf); + + return TRUE; +} + +/* *INDENT-OFF* */ +static const gchar *sink_caps_str = + GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_VA, + "{ NV12 }") " ;" + GST_VIDEO_CAPS_MAKE ("{ NV12 }"); +/* *INDENT-ON* */ + +static const gchar *src_caps_str = "video/x-vp8"; + +static gpointer +_register_debug_category (gpointer data) +{ + GST_DEBUG_CATEGORY_INIT (gst_va_vp8enc_debug, "vavp8enc", 0, + "VA vp8 encoder"); + + return NULL; +} + +static void +gst_va_vp8_enc_init (GTypeInstance * instance, gpointer g_class) +{ + GstVaVp8Enc *self = GST_VA_VP8_ENC (instance); + + /* default values */ + self->prop.bitrate = 0; + self->prop.target_usage = 4; + self->prop.cpb_size = 0; + self->prop.target_percentage = DEFAULT_TARGET_PERCENTAGE; + self->prop.keyframe_interval = MAX_KEY_FRAME_INTERVAL; + self->prop.qp = DEFAULT_BASE_QINDEX; + self->prop.min_qp = 0; + self->prop.max_qp = 127; + self->prop.mbbrc = 0; + self->prop.filter_level = -1; + self->prop.sharpness_level = 0; + + if (properties[PROP_RATE_CONTROL]) { + self->prop.rc_ctrl = + G_PARAM_SPEC_ENUM (properties[PROP_RATE_CONTROL])->default_value; + } else { + self->prop.rc_ctrl = VA_RC_NONE; + } +} + +static void +gst_va_vp8_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstVaVp8Enc *const self = GST_VA_VP8_ENC (object); + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + GstVaEncoder *encoder = NULL; + gboolean no_effect; + + gst_object_replace ((GstObject **) (&encoder), (GstObject *) base->encoder); + no_effect = (encoder && gst_va_encoder_is_open (encoder)); + if (encoder) + gst_object_unref (encoder); + + GST_OBJECT_LOCK (self); + + switch (prop_id) { + case PROP_KEYFRAME_INT: + self->prop.keyframe_interval = g_value_get_uint (value); + break; + case PROP_QP: + self->prop.qp = g_value_get_uint (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_MAX_QP: + self->prop.max_qp = g_value_get_uint (value); + break; + case PROP_MIN_QP: + self->prop.min_qp = g_value_get_uint (value); + break; + case PROP_BITRATE: + self->prop.bitrate = g_value_get_uint (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_TARGET_USAGE: + self->prop.target_usage = g_value_get_uint (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_TARGET_PERCENTAGE: + self->prop.target_percentage = g_value_get_uint (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_CPB_SIZE: + self->prop.cpb_size = g_value_get_uint (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_RATE_CONTROL: + self->prop.rc_ctrl = g_value_get_enum (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_LOOP_FILTER_LEVEL: + self->prop.filter_level = g_value_get_int (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_SHARPNESS_LEVEL: + self->prop.sharpness_level = g_value_get_uint (value); + no_effect = FALSE; + g_atomic_int_set (&GST_VA_BASE_ENC (self)->reconf, TRUE); + break; + case PROP_MBBRC:{ + /* Macroblock-level rate control. + * 0: use default, + * 1: always enable, + * 2: always disable, + * other: reserved. */ + switch (g_value_get_enum (value)) { + case GST_VA_FEATURE_DISABLED: + self->prop.mbbrc = 2; + break; + case GST_VA_FEATURE_ENABLED: + self->prop.mbbrc = 1; + break; + case GST_VA_FEATURE_AUTO: + self->prop.mbbrc = 0; + break; + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + + GST_OBJECT_UNLOCK (self); + + if (no_effect) { +#ifndef GST_DISABLE_GST_DEBUG + GST_WARNING_OBJECT (self, "Property `%s` change may not take effect " + "until the next encoder reconfig.", pspec->name); +#endif + } +} + +static void +gst_va_vp8_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstVaVp8Enc *const self = GST_VA_VP8_ENC (object); + + GST_OBJECT_LOCK (self); + + switch (prop_id) { + case PROP_KEYFRAME_INT: + g_value_set_uint (value, self->prop.keyframe_interval); + break; + case PROP_QP: + g_value_set_uint (value, self->prop.qp); + break; + case PROP_MIN_QP: + g_value_set_uint (value, self->prop.min_qp); + break; + case PROP_MAX_QP: + g_value_set_uint (value, self->prop.max_qp); + break; + case PROP_BITRATE: + g_value_set_uint (value, self->prop.bitrate); + break; + case PROP_TARGET_USAGE: + g_value_set_uint (value, self->prop.target_usage); + break; + case PROP_TARGET_PERCENTAGE: + g_value_set_uint (value, self->prop.target_percentage); + break; + case PROP_CPB_SIZE: + g_value_set_uint (value, self->prop.cpb_size); + break; + case PROP_RATE_CONTROL: + g_value_set_enum (value, self->prop.rc_ctrl); + break; + case PROP_MBBRC: + g_value_set_enum (value, self->prop.mbbrc); + break; + case PROP_LOOP_FILTER_LEVEL: + g_value_set_int (value, self->prop.filter_level); + break; + case PROP_SHARPNESS_LEVEL: + g_value_set_uint (value, self->prop.sharpness_level); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + + GST_OBJECT_UNLOCK (self); +} + +static void +gst_va_vp8_enc_class_init (gpointer g_klass, gpointer class_data) +{ + GstCaps *src_doc_caps, *sink_doc_caps; + GstPadTemplate *sink_pad_templ, *src_pad_templ; + GObjectClass *object_class = G_OBJECT_CLASS (g_klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (g_klass); + GstVideoEncoderClass *venc_class = GST_VIDEO_ENCODER_CLASS (g_klass); + GstVaBaseEncClass *va_enc_class = GST_VA_BASE_ENC_CLASS (g_klass); + GstVaVp8EncClass *vavp8enc_class = GST_VA_VP8_ENC_CLASS (g_klass); + GstVaDisplay *display; + GstVaEncoder *encoder; + struct CData *cdata = class_data; + gchar *long_name; + const gchar *name, *desc; + gint n_props = N_PROPERTIES; + GParamFlags param_flags = + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT; + + if (cdata->entrypoint == VAEntrypointEncSlice) { + desc = "VA-API based VP8 video encoder"; + name = "VA-API VP8 Encoder"; + } else { + desc = "VA-API based VP8 low power video encoder"; + name = "VA-API VP8 Low Power Encoder"; + } + + if (cdata->description) + long_name = g_strdup_printf ("%s in %s", name, cdata->description); + else + long_name = g_strdup (name); + + gst_element_class_set_metadata (element_class, long_name, + "Codec/Encoder/Video/Hardware", desc, + "Jochen Henneberg "); + + sink_doc_caps = gst_caps_from_string (sink_caps_str); + src_doc_caps = gst_caps_from_string (src_caps_str); + + parent_class = g_type_class_peek_parent (g_klass); + + va_enc_class->codec = VP8; + va_enc_class->entrypoint = cdata->entrypoint; + va_enc_class->render_device_path = g_strdup (cdata->render_device_path); + sink_pad_templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + cdata->sink_caps); + gst_element_class_add_pad_template (element_class, sink_pad_templ); + + gst_pad_template_set_documentation_caps (sink_pad_templ, sink_doc_caps); + gst_caps_unref (sink_doc_caps); + + src_pad_templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + cdata->src_caps); + gst_element_class_add_pad_template (element_class, src_pad_templ); + + gst_pad_template_set_documentation_caps (src_pad_templ, src_doc_caps); + gst_caps_unref (src_doc_caps); + + object_class->set_property = gst_va_vp8_enc_set_property; + object_class->get_property = gst_va_vp8_enc_get_property; + + venc_class->flush = GST_DEBUG_FUNCPTR (gst_va_vp8_enc_flush); + va_enc_class->reset_state = GST_DEBUG_FUNCPTR (gst_va_vp8_enc_reset_state); + va_enc_class->reconfig = GST_DEBUG_FUNCPTR (gst_va_vp8_enc_reconfig); + va_enc_class->new_frame = GST_DEBUG_FUNCPTR (gst_va_vp8_enc_new_frame); + va_enc_class->reorder_frame = + GST_DEBUG_FUNCPTR (gst_va_vp8_enc_reorder_frame); + va_enc_class->encode_frame = GST_DEBUG_FUNCPTR (gst_va_vp8_enc_encode_frame); + va_enc_class->prepare_output = + GST_DEBUG_FUNCPTR (gst_va_vp8_enc_prepare_output); + + { + display = gst_va_display_platform_new (va_enc_class->render_device_path); + encoder = gst_va_encoder_new (display, va_enc_class->codec, + va_enc_class->entrypoint); + if (gst_va_encoder_get_rate_control_enum (encoder, + vavp8enc_class->rate_control)) { + g_snprintf (vavp8enc_class->rate_control_type_name, + G_N_ELEMENTS (vavp8enc_class->rate_control_type_name) - 1, + "GstVaEncoderRateControl_%" GST_FOURCC_FORMAT "%s_%s", + GST_FOURCC_ARGS (va_enc_class->codec), + (va_enc_class->entrypoint == VAEntrypointEncSliceLP) ? "_LP" : "", + g_path_get_basename (va_enc_class->render_device_path)); + vavp8enc_class->rate_control_type = + g_enum_register_static (vavp8enc_class->rate_control_type_name, + vavp8enc_class->rate_control); + gst_type_mark_as_plugin_api (vavp8enc_class->rate_control_type, 0); + } + gst_object_unref (encoder); + gst_object_unref (display); + } + + g_free (long_name); + g_free (cdata->description); + g_free (cdata->render_device_path); + gst_caps_unref (cdata->src_caps); + gst_caps_unref (cdata->sink_caps); + g_free (cdata); + + /** + * GstVaVp8Enc:key-int-max: + * + * The maximal distance between two keyframes. + */ + properties[PROP_KEYFRAME_INT] = g_param_spec_uint ("key-int-max", + "Key frame maximal interval", + "The maximal distance between two keyframes. It decides the size of GOP" + " (0: auto-calculate)", 0, MAX_KEY_FRAME_INTERVAL, 0, param_flags); + + /** + * GstVaVp8Enc:min-qp: + * + * The minimum quantizer value. + */ + properties[PROP_MIN_QP] = g_param_spec_uint ("min-qp", "Minimum QP", + "Minimum quantizer value for each frame", 0, 126, 0, param_flags); + + /** + * GstVaVp8Enc:max-qp: + * + * The maximum quantizer value. + */ + properties[PROP_MAX_QP] = g_param_spec_uint ("max-qp", "Maximum QP", + "Maximum quantizer value for each frame", 1, 127, 127, param_flags); + + /** + * GstVaVp8Enc:qp: + * + * The basic quantizer value for all frames. + */ + properties[PROP_QP] = g_param_spec_uint ("qp", "The frame QP", + "In CQP mode, it specifies the basic quantizer value for all frames. " + "In other modes, it is ignored", 0, 255, DEFAULT_BASE_QINDEX, + param_flags | GST_PARAM_MUTABLE_PLAYING); + + /** + * GstVaVp8Enc:bitrate: + * + * The desired target bitrate, expressed in kbps. + * This is not available in CQP mode. + * + * CBR: This applies equally to the minimum, maximum and target bitrate. + * VBR: This applies to the target bitrate. The driver will use the + * "target-percentage" together to calculate the minimum and maximum bitrate. + */ + properties[PROP_BITRATE] = g_param_spec_uint ("bitrate", "Bitrate (kbps)", + "The desired bitrate expressed in kbps (0: auto-calculate)", + 0, 2000 * 1024, 0, param_flags | GST_PARAM_MUTABLE_PLAYING); + + /** + * GstVaVp8Enc:target-percentage: + * + * The target percentage of the max bitrate, and expressed in uint, + * equal to "target percentage"*100. + * "target percentage" = "target bitrate" * 100 / "max bitrate" + * This is available only when rate-control is VBR. + * The driver uses it to calculate the minimum and maximum bitrate. + */ + properties[PROP_TARGET_PERCENTAGE] = g_param_spec_uint ("target-percentage", + "target bitrate percentage", + "The percentage for 'target bitrate'/'maximum bitrate' (Only in VBR)", + 50, 100, 66, param_flags | GST_PARAM_MUTABLE_PLAYING); + + /** + * GstVaVp8Enc:cpb-size: + * + * The desired max CPB size in Kb (0: auto-calculate). + */ + properties[PROP_CPB_SIZE] = g_param_spec_uint ("cpb-size", + "max CPB size in Kb", + "The desired max CPB size in Kb (0: auto-calculate)", 0, 2000 * 1024, 0, + param_flags | GST_PARAM_MUTABLE_PLAYING); + + /** + * GstVaVp8Enc:target-usage: + * + * The target usage of the encoder. It controls and balances the encoding + * speed and the encoding quality. The lower value has better quality but + * slower speed, the higher value has faster speed but lower quality. + */ + properties[PROP_TARGET_USAGE] = g_param_spec_uint ("target-usage", + "target usage", + "The target usage to control and balance the encoding speed/quality", + 1, 7, 4, param_flags | GST_PARAM_MUTABLE_PLAYING); + + /** + * GstVaVp8Enc:mbbrc: + * + * Macroblock level bitrate control. + * This is not compatible with Constant QP rate control. + */ + properties[PROP_MBBRC] = g_param_spec_enum ("mbbrc", + "Macroblock level Bitrate Control", + "Macroblock level Bitrate Control. It is not compatible with CQP", + GST_TYPE_VA_FEATURE, GST_VA_FEATURE_DISABLED, param_flags); + + /** + * GstVaVp8Enc:loop-filter-level: + * + * Controls the deblocking filter strength, -1 means auto calculation. + */ + properties[PROP_LOOP_FILTER_LEVEL] = g_param_spec_int ("loop-filter-level", + "Loop Filter Level", + "Controls the deblocking filter strength, -1 means auto calculation", + -1, 63, -1, param_flags | GST_PARAM_MUTABLE_PLAYING); + + /** + * GstVaVp8Enc:sharpness-level: + * + * Controls the deblocking filter sensitivity. + */ + properties[PROP_SHARPNESS_LEVEL] = g_param_spec_uint ("sharpness-level", + "Sharpness Level", + "Controls the deblocking filter sensitivity", + 0, 7, 0, param_flags | GST_PARAM_MUTABLE_PLAYING); + + if (vavp8enc_class->rate_control_type > 0) { + properties[PROP_RATE_CONTROL] = g_param_spec_enum ("rate-control", + "rate control mode", + "The desired rate control mode for the encoder", + vavp8enc_class->rate_control_type, + vavp8enc_class->rate_control[0].value, + GST_PARAM_CONDITIONALLY_AVAILABLE | GST_PARAM_MUTABLE_PLAYING + | param_flags); + } else { + n_props--; + properties[PROP_RATE_CONTROL] = NULL; + } + + g_object_class_install_properties (object_class, n_props, properties); + + /** + * GstVaFeature: + * @GST_VA_FEATURE_DISABLED: The feature is disabled. + * @GST_VA_FEATURE_ENABLED: The feature is enabled. + * @GST_VA_FEATURE_AUTO: The feature is enabled automatically. + * + * Since: 1.24 + */ + gst_type_mark_as_plugin_api (GST_TYPE_VA_FEATURE, 0); +} + +static GstCaps * +_complete_src_caps (GstCaps * srccaps) +{ + GstCaps *caps = gst_caps_copy (srccaps); + GValue val = G_VALUE_INIT; + + g_value_init (&val, G_TYPE_STRING); + gst_caps_set_value (caps, "alignment", &val); + g_value_unset (&val); + + return caps; +} + +gboolean +gst_va_vp8_enc_register (GstPlugin * plugin, GstVaDevice * device, + GstCaps * sink_caps, GstCaps * src_caps, guint rank, + VAEntrypoint entrypoint) +{ + static GOnce debug_once = G_ONCE_INIT; + GType type; + GTypeInfo type_info = { + .class_size = sizeof (GstVaVp8EncClass), + .class_init = gst_va_vp8_enc_class_init, + .instance_size = sizeof (GstVaVp8Enc), + .instance_init = gst_va_vp8_enc_init, + }; + struct CData *cdata; + gboolean ret; + gchar *type_name, *feature_name; + + g_return_val_if_fail (GST_IS_PLUGIN (plugin), FALSE); + g_return_val_if_fail (GST_IS_VA_DEVICE (device), FALSE); + g_return_val_if_fail (GST_IS_CAPS (sink_caps), FALSE); + g_return_val_if_fail (GST_IS_CAPS (src_caps), FALSE); + g_return_val_if_fail (entrypoint == VAEntrypointEncSlice || + entrypoint == VAEntrypointEncSliceLP, FALSE); + + cdata = g_new (struct CData, 1); + cdata->entrypoint = entrypoint; + cdata->description = NULL; + cdata->render_device_path = g_strdup (device->render_device_path); + cdata->sink_caps = gst_caps_ref (sink_caps); + cdata->src_caps = _complete_src_caps (src_caps); + + /* Class data will be leaked if the element never gets instantiated. */ + GST_MINI_OBJECT_FLAG_SET (cdata->sink_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + GST_MINI_OBJECT_FLAG_SET (cdata->src_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + + type_info.class_data = cdata; + if (entrypoint == VAEntrypointEncSlice) { + gst_va_create_feature_name (device, "GstVaVP8Enc", "GstVa%sVP8Enc", + &type_name, "vavp8enc", "va%svp8enc", &feature_name, + &cdata->description, &rank); + } else { + gst_va_create_feature_name (device, "GstVaVP8LPEnc", "GstVa%sVP8LPEnc", + &type_name, "vavp8lpenc", "va%svp8lpenc", &feature_name, + &cdata->description, &rank); + } + + g_once (&debug_once, _register_debug_category, NULL); + type = g_type_register_static (GST_TYPE_VA_BASE_ENC, + type_name, &type_info, 0); + ret = gst_element_register (plugin, feature_name, rank, type); + + g_free (type_name); + g_free (feature_name); + + return ret; +} diff --git a/subprojects/gst-plugins-bad/sys/va/gstvavp8enc.h b/subprojects/gst-plugins-bad/sys/va/gstvavp8enc.h new file mode 100644 index 0000000000..a84c43d114 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/va/gstvavp8enc.h @@ -0,0 +1,34 @@ +/* GStreamer + * Copyright (C) 2024 Centricular Ltd + * Author: Jochen Henneberg + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "gstvadevice.h" + +G_BEGIN_DECLS + +gboolean gst_va_vp8_enc_register (GstPlugin * plugin, + GstVaDevice * device, + GstCaps * sink_caps, + GstCaps * src_caps, + guint rank, + VAEntrypoint entrypoint); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/va/meson.build b/subprojects/gst-plugins-bad/sys/va/meson.build index 0f2a262e92..86c61cd4a0 100644 --- a/subprojects/gst-plugins-bad/sys/va/meson.build +++ b/subprojects/gst-plugins-bad/sys/va/meson.build @@ -23,6 +23,7 @@ va_sources = [ 'gstvapluginutils.c', 'gstvaprofile.c', 'gstvavp8dec.c', + 'gstvavp8enc.c', 'gstvavp9dec.c', 'gstvavp9enc.c', 'gstvavpp.c', diff --git a/subprojects/gst-plugins-bad/sys/va/plugin.c b/subprojects/gst-plugins-bad/sys/va/plugin.c index 5eb65cbea5..c8ac72d74e 100644 --- a/subprojects/gst-plugins-bad/sys/va/plugin.c +++ b/subprojects/gst-plugins-bad/sys/va/plugin.c @@ -43,6 +43,7 @@ #include "gstvampeg2dec.h" #include "gstvaprofile.h" #include "gstvavp8dec.h" +#include "gstvavp8enc.h" #include "gstvavp9dec.h" #include "gstvavp9enc.h" #include "gstvavpp.h" @@ -217,6 +218,13 @@ plugin_register_encoders (GstPlugin * plugin, GstVaDevice * device, device->render_device_path); } break; + case VP8: + if (!gst_va_vp8_enc_register (plugin, device, sinkcaps, srccaps, + GST_RANK_NONE, entrypoint)) { + GST_WARNING ("Failed to register VP8 encoder: %s", + device->render_device_path); + } + break; case VP9: if (!gst_va_vp9_enc_register (plugin, device, sinkcaps, srccaps, GST_RANK_NONE, entrypoint)) {