gstreamer/subprojects/gst-plugins-bad/sys/va/gstvavp8enc.c

1493 lines
47 KiB
C

/* GStreamer
* Copyright (C) 2024 Centricular Ltd
* Author: Jochen Henneberg <jochen@centricular.com>
*
* 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 <math.h>
#include <gst/codecparsers/gstvp8parser.h>
#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 <jochen@centricular.com>");
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;
}