mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 19:51:11 +00:00
f31581544c
Fixes #3430 Fixes #3576 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6959>
1493 lines
47 KiB
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;
|
|
}
|