/*
|
* Copyright 2017 Rockchip Electronics Co., Ltd
|
* Author: Randy Li <randy.li@rock-chips.com>
|
*
|
* Copyright 2021 Rockchip Electronics Co., Ltd
|
* Author: Jeffy Chen <jeffy.chen@rock-chips.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.
|
*
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
#include "config.h"
|
#endif
|
|
#include <string.h>
|
|
#include "gstmppallocator.h"
|
#include "gstmppenc.h"
|
|
#define GST_CAT_DEFAULT mpp_enc_debug
|
GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
|
|
#define parent_class gst_mpp_enc_parent_class
|
G_DEFINE_ABSTRACT_TYPE (GstMppEnc, gst_mpp_enc, GST_TYPE_VIDEO_ENCODER);
|
|
#define MPP_PENDING_MAX 2 /* Max number of MPP pending frame */
|
|
#define GST_MPP_ENC_TASK_STARTED(encoder) \
|
(gst_pad_get_task_state ((encoder)->srcpad) == GST_TASK_STARTED)
|
|
#define GST_MPP_ENC_MUTEX(encoder) (&GST_MPP_ENC (encoder)->mutex)
|
|
#define GST_MPP_ENC_LOCK(encoder) \
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder); \
|
g_mutex_lock (GST_MPP_ENC_MUTEX (encoder)); \
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
|
#define GST_MPP_ENC_UNLOCK(encoder) \
|
g_mutex_unlock (GST_MPP_ENC_MUTEX (encoder));
|
|
#define GST_MPP_ENC_EVENT_MUTEX(encoder) (&GST_MPP_ENC (encoder)->event_mutex)
|
#define GST_MPP_ENC_EVENT_COND(encoder) (&GST_MPP_ENC (encoder)->event_cond)
|
|
#define GST_MPP_ENC_BROADCAST(encoder) \
|
g_mutex_lock (GST_MPP_ENC_EVENT_MUTEX (encoder)); \
|
g_cond_broadcast (GST_MPP_ENC_EVENT_COND (encoder)); \
|
g_mutex_unlock (GST_MPP_ENC_EVENT_MUTEX (encoder));
|
|
#define GST_MPP_ENC_WAIT(encoder, condition) \
|
g_mutex_lock (GST_MPP_ENC_EVENT_MUTEX (encoder)); \
|
while (!(condition)) \
|
g_cond_wait (GST_MPP_ENC_EVENT_COND (encoder), \
|
GST_MPP_ENC_EVENT_MUTEX (encoder)); \
|
g_mutex_unlock (GST_MPP_ENC_EVENT_MUTEX (encoder));
|
|
#define DEFAULT_PROP_HEADER_MODE MPP_ENC_HEADER_MODE_DEFAULT /* First frame */
|
#define DEFAULT_PROP_SEI_MODE MPP_ENC_SEI_MODE_DISABLE
|
#define DEFAULT_PROP_RC_MODE MPP_ENC_RC_MODE_CBR
|
#define DEFAULT_PROP_ROTATION 0
|
#define DEFAULT_PROP_GOP -1 /* Same as FPS */
|
#define DEFAULT_PROP_MAX_REENC 1
|
#define DEFAULT_PROP_BPS 0 /* Auto */
|
#define DEFAULT_PROP_BPS_MIN 0 /* Auto */
|
#define DEFAULT_PROP_BPS_MAX 0 /* Auto */
|
#define DEFAULT_PROP_WIDTH 0 /* Original */
|
#define DEFAULT_PROP_HEIGHT 0 /* Original */
|
#define DEFAULT_PROP_ZERO_COPY_PKT TRUE
|
|
#define DEFAULT_FPS 30
|
|
enum
|
{
|
PROP_0,
|
PROP_HEADER_MODE,
|
PROP_RC_MODE,
|
PROP_ROTATION,
|
PROP_SEI_MODE,
|
PROP_GOP,
|
PROP_MAX_REENC,
|
PROP_BPS,
|
PROP_BPS_MIN,
|
PROP_BPS_MAX,
|
PROP_WIDTH,
|
PROP_HEIGHT,
|
PROP_ZERO_COPY_PKT,
|
PROP_LAST,
|
};
|
|
static const MppFrameFormat gst_mpp_enc_formats[] = {
|
MPP_FMT_YUV420SP,
|
MPP_FMT_YUV420P,
|
MPP_FMT_YUV422_YUYV,
|
MPP_FMT_YUV422_UYVY,
|
MPP_FMT_RGB565LE,
|
MPP_FMT_BGR565LE,
|
MPP_FMT_ARGB8888,
|
MPP_FMT_ABGR8888,
|
MPP_FMT_RGBA8888,
|
MPP_FMT_BGRA8888,
|
};
|
|
static gboolean
|
gst_mpp_enc_format_supported (MppFrameFormat format)
|
{
|
guint i;
|
|
for (i = 0; i < ARRAY_SIZE (gst_mpp_enc_formats); i++) {
|
if (format == gst_mpp_enc_formats[i])
|
return TRUE;
|
}
|
|
return FALSE;
|
}
|
|
gboolean
|
gst_mpp_enc_supported (MppCodingType mpp_type)
|
{
|
MppCtx mpp_ctx;
|
MppApi *mpi;
|
|
if (mpp_create (&mpp_ctx, &mpi))
|
return FALSE;
|
|
if (mpp_init (mpp_ctx, MPP_CTX_ENC, mpp_type)) {
|
mpp_destroy (mpp_ctx);
|
return FALSE;
|
}
|
|
mpp_destroy (mpp_ctx);
|
return TRUE;
|
}
|
|
static gboolean
|
gst_mpp_enc_video_info_matched (GstVideoInfo * info, GstVideoInfo * other)
|
{
|
guint i;
|
|
if (GST_VIDEO_INFO_FORMAT (info) != GST_VIDEO_INFO_FORMAT (other))
|
return FALSE;
|
|
if (GST_VIDEO_INFO_SIZE (info) != GST_VIDEO_INFO_SIZE (other))
|
return FALSE;
|
|
if (GST_VIDEO_INFO_WIDTH (info) != GST_VIDEO_INFO_WIDTH (other))
|
return FALSE;
|
|
if (GST_VIDEO_INFO_HEIGHT (info) != GST_VIDEO_INFO_HEIGHT (other))
|
return FALSE;
|
|
for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) {
|
if (GST_VIDEO_INFO_PLANE_STRIDE (info,
|
i) != GST_VIDEO_INFO_PLANE_STRIDE (other, i))
|
return FALSE;
|
if (GST_VIDEO_INFO_PLANE_OFFSET (info,
|
i) != GST_VIDEO_INFO_PLANE_OFFSET (other, i))
|
return FALSE;
|
}
|
|
return TRUE;
|
}
|
|
gboolean
|
gst_mpp_enc_video_info_align (GstVideoInfo * info)
|
{
|
gint vstride = 0;
|
|
/* Allow skipping vstride aligning for RKVENC */
|
if (g_getenv ("GST_MPP_ENC_UNALIGNED_VSTRIDE"))
|
vstride = GST_MPP_VIDEO_INFO_VSTRIDE (info);
|
|
return gst_mpp_video_info_align (info, 0, vstride);
|
}
|
|
static void
|
gst_mpp_enc_set_property (GObject * object,
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
{
|
GstVideoEncoder *encoder = GST_VIDEO_ENCODER (object);
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
|
switch (prop_id) {
|
case PROP_HEADER_MODE:{
|
MppEncHeaderMode header_mode = g_value_get_enum (value);
|
if (self->header_mode == header_mode)
|
return;
|
|
self->header_mode = header_mode;
|
break;
|
}
|
case PROP_SEI_MODE:{
|
MppEncSeiMode sei_mode = g_value_get_enum (value);
|
if (self->sei_mode == sei_mode)
|
return;
|
|
self->sei_mode = sei_mode;
|
break;
|
}
|
case PROP_RC_MODE:{
|
MppEncRcMode rc_mode = g_value_get_enum (value);
|
if (self->rc_mode == rc_mode)
|
return;
|
|
self->rc_mode = rc_mode;
|
break;
|
}
|
case PROP_GOP:{
|
gint gop = g_value_get_int (value);
|
if (self->gop == gop)
|
return;
|
|
self->gop = gop;
|
break;
|
}
|
case PROP_MAX_REENC:{
|
guint max_reenc = g_value_get_uint (value);
|
if (self->max_reenc == max_reenc)
|
return;
|
|
self->max_reenc = max_reenc;
|
break;
|
}
|
case PROP_BPS:{
|
guint bps = g_value_get_uint (value);
|
if (self->bps == bps)
|
return;
|
|
self->bps = bps;
|
break;
|
}
|
case PROP_BPS_MIN:{
|
guint bps_min = g_value_get_uint (value);
|
if (self->bps_min == bps_min)
|
return;
|
|
self->bps_min = bps_min;
|
break;
|
}
|
case PROP_BPS_MAX:{
|
guint bps_max = g_value_get_uint (value);
|
if (self->bps_max == bps_max)
|
return;
|
|
self->bps_max = bps_max;
|
break;
|
}
|
case PROP_ROTATION:{
|
if (self->input_state)
|
GST_WARNING_OBJECT (encoder, "unable to change rotation");
|
else
|
self->rotation = g_value_get_enum (value);
|
return;
|
}
|
case PROP_WIDTH:{
|
if (self->input_state)
|
GST_WARNING_OBJECT (encoder, "unable to change width");
|
else
|
self->width = g_value_get_uint (value);
|
return;
|
}
|
case PROP_HEIGHT:{
|
if (self->input_state)
|
GST_WARNING_OBJECT (encoder, "unable to change height");
|
else
|
self->height = g_value_get_uint (value);
|
return;
|
}
|
case PROP_ZERO_COPY_PKT:{
|
self->zero_copy_pkt = g_value_get_boolean (value);
|
return;
|
}
|
default:
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
return;
|
}
|
|
self->prop_dirty = TRUE;
|
}
|
|
static void
|
gst_mpp_enc_get_property (GObject * object,
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
{
|
GstVideoEncoder *encoder = GST_VIDEO_ENCODER (object);
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
|
switch (prop_id) {
|
case PROP_HEADER_MODE:
|
g_value_set_enum (value, self->header_mode);
|
break;
|
case PROP_SEI_MODE:
|
g_value_set_enum (value, self->sei_mode);
|
break;
|
case PROP_RC_MODE:
|
g_value_set_enum (value, self->rc_mode);
|
break;
|
case PROP_ROTATION:
|
g_value_set_enum (value, self->rotation);
|
break;
|
case PROP_GOP:
|
g_value_set_int (value, self->gop);
|
break;
|
case PROP_MAX_REENC:
|
g_value_set_uint (value, self->max_reenc);
|
break;
|
case PROP_BPS:
|
g_value_set_uint (value, self->bps);
|
break;
|
case PROP_BPS_MIN:
|
g_value_set_uint (value, self->bps_min);
|
break;
|
case PROP_BPS_MAX:
|
g_value_set_uint (value, self->bps_max);
|
break;
|
case PROP_WIDTH:
|
g_value_set_uint (value, self->width);
|
break;
|
case PROP_HEIGHT:
|
g_value_set_uint (value, self->height);
|
break;
|
case PROP_ZERO_COPY_PKT:
|
g_value_set_boolean (value, self->zero_copy_pkt);
|
break;
|
default:
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
return;
|
}
|
}
|
|
gboolean
|
gst_mpp_enc_apply_properties (GstVideoEncoder * encoder)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstVideoInfo *info = &self->info;
|
gint fps_num = GST_VIDEO_INFO_FPS_N (info);
|
gint fps_denorm = GST_VIDEO_INFO_FPS_D (info);
|
gint fps = fps_num / fps_denorm;
|
|
if (!self->prop_dirty)
|
return TRUE;
|
|
self->prop_dirty = FALSE;
|
|
if (self->mpi->control (self->mpp_ctx, MPP_ENC_SET_SEI_CFG, &self->sei_mode))
|
GST_WARNING_OBJECT (self, "failed to set sei mode");
|
|
if (self->mpi->control (self->mpp_ctx, MPP_ENC_SET_HEADER_MODE,
|
&self->header_mode))
|
GST_WARNING_OBJECT (self, "failed to set header mode");
|
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:gop",
|
self->gop < 0 ? fps : self->gop);
|
mpp_enc_cfg_set_u32 (self->mpp_cfg, "rc:max_reenc_times", self->max_reenc);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:mode", self->rc_mode);
|
|
if (!self->bps)
|
self->bps =
|
GST_VIDEO_INFO_WIDTH (info) * GST_VIDEO_INFO_HEIGHT (info) / 8 * fps;
|
|
if (!self->bps || self->rc_mode == MPP_ENC_RC_MODE_FIXQP) {
|
/* BPS settings are ignored */
|
} else if (self->rc_mode == MPP_ENC_RC_MODE_CBR) {
|
/* CBR mode has narrow bound */
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:bps_target", self->bps);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:bps_max",
|
self->bps_max ? : self->bps * 17 / 16);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:bps_min",
|
self->bps_min ? : self->bps * 15 / 16);
|
} else if (self->rc_mode == MPP_ENC_RC_MODE_VBR) {
|
/* VBR mode has wide bound */
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:bps_target", self->bps);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:bps_max",
|
self->bps_max ? : self->bps * 17 / 16);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:bps_min",
|
self->bps_min ? : self->bps * 1 / 16);
|
}
|
|
if (self->mpi->control (self->mpp_ctx, MPP_ENC_SET_CFG, self->mpp_cfg)) {
|
GST_WARNING_OBJECT (self, "failed to set enc cfg");
|
return FALSE;
|
}
|
|
return TRUE;
|
}
|
|
gboolean
|
gst_mpp_enc_set_src_caps (GstVideoEncoder * encoder, GstCaps * caps)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstVideoInfo *info = &self->info;
|
GstVideoCodecState *output_state;
|
|
gst_caps_set_simple (caps,
|
"width", G_TYPE_INT, GST_VIDEO_INFO_WIDTH (info),
|
"height", G_TYPE_INT, GST_VIDEO_INFO_HEIGHT (info), NULL);
|
|
GST_DEBUG_OBJECT (self, "output caps: %" GST_PTR_FORMAT, caps);
|
|
output_state = gst_video_encoder_set_output_state (encoder,
|
caps, self->input_state);
|
|
GST_VIDEO_INFO_WIDTH (&output_state->info) = GST_VIDEO_INFO_WIDTH (info);
|
GST_VIDEO_INFO_HEIGHT (&output_state->info) = GST_VIDEO_INFO_HEIGHT (info);
|
gst_video_codec_state_unref (output_state);
|
|
return gst_video_encoder_negotiate (encoder);
|
}
|
|
static void
|
gst_mpp_enc_stop_task (GstVideoEncoder * encoder, gboolean drain)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstTask *task = encoder->srcpad->task;
|
|
if (!GST_MPP_ENC_TASK_STARTED (encoder))
|
return;
|
|
GST_DEBUG_OBJECT (self, "stopping encoding thread");
|
|
/* Discard pending frames */
|
if (!drain)
|
self->pending_frames = 0;
|
|
GST_MPP_ENC_BROADCAST (encoder);
|
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
|
/* Wait for task thread to pause */
|
if (task) {
|
GST_OBJECT_LOCK (task);
|
while (GST_TASK_STATE (task) == GST_TASK_STARTED)
|
GST_TASK_WAIT (task);
|
GST_OBJECT_UNLOCK (task);
|
}
|
|
gst_pad_stop_task (encoder->srcpad);
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
}
|
|
static void
|
gst_mpp_enc_reset (GstVideoEncoder * encoder, gboolean drain, gboolean final)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
|
GST_MPP_ENC_LOCK (encoder);
|
|
GST_DEBUG_OBJECT (self, "resetting");
|
|
self->flushing = TRUE;
|
self->draining = drain;
|
|
gst_mpp_enc_stop_task (encoder, drain);
|
|
self->flushing = final;
|
self->draining = FALSE;
|
|
self->mpi->reset (self->mpp_ctx);
|
self->task_ret = GST_FLOW_OK;
|
self->pending_frames = 0;
|
|
/* Force re-apply prop */
|
self->prop_dirty = TRUE;
|
|
GST_MPP_ENC_UNLOCK (encoder);
|
}
|
|
static gboolean
|
gst_mpp_enc_start (GstVideoEncoder * encoder)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
|
GST_DEBUG_OBJECT (self, "starting");
|
|
self->allocator = gst_mpp_allocator_new ();
|
if (!self->allocator)
|
return FALSE;
|
|
if (mpp_create (&self->mpp_ctx, &self->mpi))
|
goto err_unref_alloc;
|
|
if (mpp_init (self->mpp_ctx, MPP_CTX_ENC, self->mpp_type))
|
goto err_destroy_mpp;
|
|
if (mpp_frame_init (&self->mpp_frame))
|
goto err_destroy_mpp;
|
|
if (mpp_enc_cfg_init (&self->mpp_cfg))
|
goto err_deinit_frame;
|
|
if (self->mpi->control (self->mpp_ctx, MPP_ENC_GET_CFG, self->mpp_cfg))
|
goto err_deinit_cfg;
|
|
self->task_ret = GST_FLOW_OK;
|
self->input_state = NULL;
|
self->flushing = FALSE;
|
self->pending_frames = 0;
|
|
g_mutex_init (&self->mutex);
|
|
g_mutex_init (&self->event_mutex);
|
g_cond_init (&self->event_cond);
|
|
GST_DEBUG_OBJECT (self, "started");
|
|
return TRUE;
|
|
err_deinit_cfg:
|
mpp_enc_cfg_deinit (self->mpp_cfg);
|
err_deinit_frame:
|
mpp_frame_deinit (&self->mpp_frame);
|
err_destroy_mpp:
|
mpp_destroy (self->mpp_ctx);
|
err_unref_alloc:
|
gst_object_unref (self->allocator);
|
return FALSE;
|
}
|
|
static gboolean
|
gst_mpp_enc_stop (GstVideoEncoder * encoder)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
|
GST_DEBUG_OBJECT (self, "stopping");
|
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
gst_mpp_enc_reset (encoder, FALSE, TRUE);
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
|
|
g_cond_clear (&self->event_cond);
|
g_mutex_clear (&self->event_mutex);
|
|
g_mutex_clear (&self->mutex);
|
|
mpp_enc_cfg_deinit (self->mpp_cfg);
|
mpp_frame_set_buffer (self->mpp_frame, NULL);
|
mpp_frame_deinit (&self->mpp_frame);
|
mpp_destroy (self->mpp_ctx);
|
|
gst_object_unref (self->allocator);
|
|
if (self->input_state)
|
gst_video_codec_state_unref (self->input_state);
|
|
GST_DEBUG_OBJECT (self, "stopped");
|
|
return TRUE;
|
}
|
|
static gboolean
|
gst_mpp_enc_flush (GstVideoEncoder * encoder)
|
{
|
GST_DEBUG_OBJECT (encoder, "flushing");
|
gst_mpp_enc_reset (encoder, FALSE, FALSE);
|
return TRUE;
|
}
|
|
static gboolean
|
gst_mpp_enc_finish (GstVideoEncoder * encoder)
|
{
|
GST_DEBUG_OBJECT (encoder, "finishing");
|
gst_mpp_enc_reset (encoder, TRUE, FALSE);
|
return GST_FLOW_OK;
|
}
|
|
static gboolean
|
gst_mpp_enc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstVideoInfo *info = &self->info;
|
MppFrameFormat format;
|
gint width, height;
|
gboolean convert = FALSE;
|
|
GST_DEBUG_OBJECT (self, "setting format: %" GST_PTR_FORMAT, state->caps);
|
|
if (self->input_state) {
|
if (gst_caps_is_strictly_equal (self->input_state->caps, state->caps))
|
return TRUE;
|
|
gst_mpp_enc_reset (encoder, TRUE, FALSE);
|
|
gst_video_codec_state_unref (self->input_state);
|
self->input_state = NULL;
|
}
|
|
self->input_state = gst_video_codec_state_ref (state);
|
|
*info = state->info;
|
|
if (!gst_mpp_enc_video_info_align (info))
|
return FALSE;
|
|
format = gst_mpp_gst_format_to_mpp_format (GST_VIDEO_INFO_FORMAT (info));
|
width = GST_VIDEO_INFO_WIDTH (info);
|
height = GST_VIDEO_INFO_HEIGHT (info);
|
|
if (self->rotation % 180)
|
SWAP (width, height);
|
|
width = self->width ? : width;
|
height = self->height ? : height;
|
|
/* Check for conversion */
|
if (self->rotation || !gst_mpp_enc_format_supported (format) ||
|
width != GST_VIDEO_INFO_WIDTH (info) ||
|
height != GST_VIDEO_INFO_HEIGHT (info)) {
|
if (!gst_mpp_use_rga ()) {
|
GST_ERROR_OBJECT (self, "unable to convert without RGA");
|
return FALSE;
|
}
|
|
convert = TRUE;
|
}
|
|
/* Check for alignment */
|
if (!gst_mpp_enc_video_info_matched (info, &state->info))
|
convert = TRUE;
|
|
if (convert) {
|
/* Prefer NV12 when using RGA conversion */
|
if (gst_mpp_use_rga ())
|
format = MPP_FMT_YUV420SP;
|
|
gst_video_info_set_format (info, gst_mpp_mpp_format_to_gst_format (format),
|
width, height);
|
|
if (!gst_mpp_enc_video_info_align (info))
|
return FALSE;
|
|
GST_INFO_OBJECT (self, "converting to aligned %s",
|
gst_mpp_video_format_to_string (GST_VIDEO_INFO_FORMAT (info)));
|
}
|
|
mpp_frame_set_fmt (self->mpp_frame, format);
|
mpp_frame_set_width (self->mpp_frame, width);
|
mpp_frame_set_height (self->mpp_frame, height);
|
mpp_frame_set_hor_stride (self->mpp_frame, GST_MPP_VIDEO_INFO_HSTRIDE (info));
|
mpp_frame_set_ver_stride (self->mpp_frame, GST_MPP_VIDEO_INFO_VSTRIDE (info));
|
|
if (!GST_VIDEO_INFO_FPS_N (info) || GST_VIDEO_INFO_FPS_N (info) > 240) {
|
GST_WARNING_OBJECT (self, "framerate (%d/%d) is insane!",
|
GST_VIDEO_INFO_FPS_N (info), GST_VIDEO_INFO_FPS_D (info));
|
GST_VIDEO_INFO_FPS_N (info) = DEFAULT_FPS;
|
}
|
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:format", format);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:width", width);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:height", height);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:hor_stride",
|
GST_MPP_VIDEO_INFO_HSTRIDE (info));
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "prep:ver_stride",
|
GST_MPP_VIDEO_INFO_VSTRIDE (info));
|
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:fps_in_flex", 0);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:fps_in_num",
|
GST_VIDEO_INFO_FPS_N (info));
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:fps_in_denorm",
|
GST_VIDEO_INFO_FPS_D (info));
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:fps_out_flex", 0);
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:fps_out_num",
|
GST_VIDEO_INFO_FPS_N (info));
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:fps_out_denorm",
|
GST_VIDEO_INFO_FPS_D (info));
|
|
return TRUE;
|
}
|
|
static gboolean
|
gst_mpp_enc_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstStructure *config, *params;
|
GstVideoAlignment align;
|
GstBufferPool *pool;
|
GstVideoInfo info;
|
GstCaps *caps;
|
guint size;
|
|
GST_DEBUG_OBJECT (self, "propose allocation");
|
|
gst_query_parse_allocation (query, &caps, NULL);
|
if (caps == NULL)
|
return FALSE;
|
|
if (!gst_video_info_from_caps (&info, caps))
|
return FALSE;
|
|
gst_mpp_enc_video_info_align (&info);
|
size = GST_VIDEO_INFO_SIZE (&info);
|
|
gst_video_alignment_reset (&align);
|
align.padding_right = gst_mpp_get_pixel_stride (&info) -
|
GST_VIDEO_INFO_WIDTH (&info);
|
align.padding_bottom = GST_MPP_VIDEO_INFO_VSTRIDE (&info) -
|
GST_VIDEO_INFO_HEIGHT (&info);
|
|
/* Expose alignment to video-meta */
|
params = gst_structure_new ("video-meta",
|
"padding-top", G_TYPE_UINT, align.padding_top,
|
"padding-bottom", G_TYPE_UINT, align.padding_bottom,
|
"padding-left", G_TYPE_UINT, align.padding_left,
|
"padding-right", G_TYPE_UINT, align.padding_right, NULL);
|
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, params);
|
gst_structure_free (params);
|
|
pool = gst_video_buffer_pool_new ();
|
|
config = gst_buffer_pool_get_config (pool);
|
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
|
gst_buffer_pool_config_set_allocator (config, self->allocator, NULL);
|
|
/* Expose alignment to pool */
|
gst_buffer_pool_config_add_option (config,
|
GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
|
gst_buffer_pool_config_set_video_alignment (config, &align);
|
|
gst_buffer_pool_set_config (pool, config);
|
|
gst_query_add_allocation_pool (query, pool, size, MPP_PENDING_MAX, 0);
|
gst_query_add_allocation_param (query, self->allocator, NULL);
|
|
gst_object_unref (pool);
|
|
return GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder,
|
query);
|
}
|
|
static GstBuffer *
|
gst_mpp_enc_convert (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstVideoInfo src_info = self->input_state->info;
|
GstVideoInfo *dst_info = &self->info;
|
GstVideoFrame src_frame, dst_frame;
|
GstBuffer *outbuf, *inbuf;
|
GstMemory *in_mem, *out_mem;
|
GstVideoMeta *meta;
|
gsize size, maxsize, offset;
|
guint i;
|
|
inbuf = frame->input_buffer;
|
|
meta = gst_buffer_get_video_meta (inbuf);
|
if (meta) {
|
for (i = 0; i < meta->n_planes; i++) {
|
GST_VIDEO_INFO_PLANE_STRIDE (&src_info, i) = meta->stride[i];
|
GST_VIDEO_INFO_PLANE_OFFSET (&src_info, i) = meta->offset[i];
|
}
|
}
|
|
size = gst_buffer_get_sizes (inbuf, &offset, &maxsize);
|
if (size < GST_VIDEO_INFO_SIZE (&src_info)) {
|
GST_ERROR_OBJECT (self, "input buffer too small (%" G_GSIZE_FORMAT
|
" < %" G_GSIZE_FORMAT ")", size, GST_VIDEO_INFO_SIZE (&src_info));
|
return NULL;
|
}
|
|
outbuf = gst_buffer_new ();
|
if (!outbuf)
|
goto err;
|
|
gst_buffer_copy_into (outbuf, inbuf,
|
GST_BUFFER_COPY_FLAGS | GST_BUFFER_COPY_TIMESTAMPS, 0, 0);
|
|
gst_buffer_add_video_meta_full (outbuf, GST_VIDEO_FRAME_FLAG_NONE,
|
GST_VIDEO_INFO_FORMAT (dst_info),
|
GST_VIDEO_INFO_WIDTH (dst_info), GST_VIDEO_INFO_HEIGHT (dst_info),
|
GST_VIDEO_INFO_N_PLANES (dst_info), dst_info->offset, dst_info->stride);
|
|
if (self->rotation || !gst_mpp_enc_video_info_matched (&src_info, dst_info))
|
goto convert;
|
|
if (gst_buffer_n_memory (inbuf) != 1)
|
goto convert;
|
|
in_mem = gst_buffer_peek_memory (inbuf, 0);
|
|
out_mem = gst_mpp_allocator_import_gst_memory (self->allocator, in_mem);
|
if (!out_mem)
|
goto convert;
|
|
gst_buffer_append_memory (outbuf, out_mem);
|
|
/* Keep a ref of the original memory */
|
gst_buffer_append_memory (outbuf, gst_memory_ref (in_mem));
|
|
GST_DEBUG_OBJECT (self, "using imported buffer");
|
return outbuf;
|
|
convert:
|
out_mem = gst_allocator_alloc (self->allocator,
|
GST_VIDEO_INFO_SIZE (dst_info), NULL);
|
if (!out_mem)
|
goto err;
|
|
gst_buffer_append_memory (outbuf, out_mem);
|
|
#ifdef HAVE_RGA
|
if (gst_mpp_use_rga () &&
|
gst_mpp_rga_convert (inbuf, &src_info, out_mem, dst_info,
|
self->rotation)) {
|
GST_DEBUG_OBJECT (self, "using RGA converted buffer");
|
return outbuf;
|
}
|
#endif
|
|
if (self->rotation ||
|
GST_VIDEO_INFO_FORMAT (&src_info) != GST_VIDEO_INFO_FORMAT (dst_info))
|
goto err;
|
|
if (gst_video_frame_map (&src_frame, &src_info, inbuf, GST_MAP_READ)) {
|
if (gst_video_frame_map (&dst_frame, dst_info, outbuf, GST_MAP_WRITE)) {
|
if (!gst_video_frame_copy (&dst_frame, &src_frame)) {
|
gst_video_frame_unmap (&dst_frame);
|
gst_video_frame_unmap (&src_frame);
|
goto err;
|
}
|
gst_video_frame_unmap (&dst_frame);
|
}
|
gst_video_frame_unmap (&src_frame);
|
}
|
|
GST_DEBUG_OBJECT (self, "using software converted buffer");
|
return outbuf;
|
err:
|
if (outbuf)
|
gst_buffer_unref (outbuf);
|
|
GST_ERROR_OBJECT (self, "failed to convert frame");
|
return NULL;
|
}
|
|
static gboolean
|
gst_mpp_enc_force_keyframe (GstVideoEncoder * encoder, gboolean keyframe)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
|
/* HACK: Use gop(1) to force keyframe */
|
|
if (!keyframe) {
|
self->prop_dirty = TRUE;
|
return gst_mpp_enc_apply_properties (encoder);
|
}
|
|
GST_INFO_OBJECT (self, "forcing keyframe");
|
mpp_enc_cfg_set_s32 (self->mpp_cfg, "rc:gop", 1);
|
|
if (self->mpi->control (self->mpp_ctx, MPP_ENC_SET_CFG, self->mpp_cfg)) {
|
GST_WARNING_OBJECT (self, "failed to set enc cfg");
|
return FALSE;
|
}
|
|
return TRUE;
|
}
|
|
static void
|
gst_mpp_enc_loop (GstVideoEncoder * encoder)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstVideoCodecFrame *frame;
|
GstBuffer *buffer;
|
GstMemory *mem;
|
MppFrame mframe;
|
MppPacket mpkt = NULL;
|
MppBuffer mbuf;
|
gboolean keyframe;
|
gint pkt_size;
|
|
GST_MPP_ENC_WAIT (encoder, self->pending_frames || self->flushing);
|
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
|
if (self->flushing && !self->pending_frames)
|
goto flushing;
|
|
frame = gst_video_encoder_get_oldest_frame (encoder);
|
self->pending_frames--;
|
|
GST_MPP_ENC_BROADCAST (encoder);
|
|
/* HACK: get the converted input buffer from frame->output_buffer */
|
mem = gst_buffer_peek_memory (frame->output_buffer, 0);
|
mbuf = gst_mpp_mpp_buffer_from_gst_memory (mem);
|
|
mframe = self->mpp_frame;
|
mpp_frame_set_buffer (mframe, mbuf);
|
|
keyframe = GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame);
|
if (keyframe)
|
gst_mpp_enc_force_keyframe (encoder, TRUE);
|
|
/* Encode one frame */
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
|
if (!self->mpi->encode_put_frame (self->mpp_ctx, mframe))
|
self->mpi->encode_get_packet (self->mpp_ctx, &mpkt);
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
|
if (keyframe)
|
gst_mpp_enc_force_keyframe (encoder, FALSE);
|
|
if (!mpkt)
|
goto error;
|
|
pkt_size = mpp_packet_get_length (mpkt);
|
|
mbuf = mpp_packet_get_buffer (mpkt);
|
|
if (self->zero_copy_pkt) {
|
buffer = gst_buffer_new ();
|
if (!buffer)
|
goto error;
|
|
/* Allocated from the same DRM allocator in MPP */
|
mpp_buffer_set_index (mbuf, gst_mpp_allocator_get_index (self->allocator));
|
|
mem = gst_mpp_allocator_import_mppbuf (self->allocator, mbuf);
|
if (!mem) {
|
gst_buffer_unref (buffer);
|
goto error;
|
}
|
|
gst_memory_resize (mem, 0, pkt_size);
|
gst_buffer_append_memory (buffer, mem);
|
} else {
|
buffer = gst_video_encoder_allocate_output_buffer (encoder, pkt_size);
|
if (!buffer)
|
goto error;
|
|
gst_buffer_fill (buffer, 0, mpp_buffer_get_ptr (mbuf), pkt_size);
|
}
|
|
gst_buffer_replace (&frame->output_buffer, buffer);
|
gst_buffer_unref (buffer);
|
|
if (self->flushing && !self->draining)
|
goto drop;
|
|
GST_DEBUG_OBJECT (self, "finish frame ts=%" GST_TIME_FORMAT,
|
GST_TIME_ARGS (frame->pts));
|
|
gst_video_encoder_finish_frame (encoder, frame);
|
|
out:
|
if (mpkt)
|
mpp_packet_deinit (&mpkt);
|
|
if (self->task_ret != GST_FLOW_OK) {
|
GST_DEBUG_OBJECT (self, "leaving output thread: %s",
|
gst_flow_get_name (self->task_ret));
|
|
gst_pad_pause_task (encoder->srcpad);
|
}
|
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
|
return;
|
flushing:
|
GST_INFO_OBJECT (self, "flushing");
|
self->task_ret = GST_FLOW_FLUSHING;
|
goto out;
|
error:
|
GST_WARNING_OBJECT (self, "can't process this frame");
|
goto drop;
|
drop:
|
GST_DEBUG_OBJECT (self, "drop frame");
|
gst_buffer_replace (&frame->output_buffer, NULL);
|
gst_video_encoder_finish_frame (encoder, frame);
|
goto out;
|
}
|
|
static GstFlowReturn
|
gst_mpp_enc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
|
{
|
GstMppEnc *self = GST_MPP_ENC (encoder);
|
GstBuffer *buffer;
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GST_DEBUG_OBJECT (self, "handling frame %d", frame->system_frame_number);
|
|
GST_MPP_ENC_LOCK (encoder);
|
|
if (G_UNLIKELY (self->flushing))
|
goto flushing;
|
|
if (G_UNLIKELY (!GST_MPP_ENC_TASK_STARTED (encoder))) {
|
GST_DEBUG_OBJECT (self, "starting encoding thread");
|
|
gst_pad_start_task (encoder->srcpad,
|
(GstTaskFunction) gst_mpp_enc_loop, encoder, NULL);
|
}
|
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
|
buffer = gst_mpp_enc_convert (encoder, frame);
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
if (G_UNLIKELY (!buffer))
|
goto not_negotiated;
|
|
/* HACK: store the converted input buffer in frame->output_buffer */
|
frame->output_buffer = buffer;
|
|
/* Avoid holding too much frames */
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
|
GST_MPP_ENC_WAIT (encoder, self->pending_frames < MPP_PENDING_MAX
|
|| self->flushing);
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
|
self->pending_frames++;
|
|
GST_MPP_ENC_BROADCAST (encoder);
|
|
gst_video_codec_frame_unref (frame);
|
|
GST_MPP_ENC_UNLOCK (encoder);
|
|
return self->task_ret;
|
|
flushing:
|
GST_WARNING_OBJECT (self, "flushing");
|
ret = GST_FLOW_FLUSHING;
|
goto drop;
|
not_negotiated:
|
GST_ERROR_OBJECT (self, "not negotiated");
|
ret = GST_FLOW_NOT_NEGOTIATED;
|
goto drop;
|
drop:
|
GST_WARNING_OBJECT (self, "can't handle this frame");
|
gst_video_encoder_finish_frame (encoder, frame);
|
|
GST_MPP_ENC_UNLOCK (encoder);
|
|
return ret;
|
}
|
|
static GstStateChangeReturn
|
gst_mpp_enc_change_state (GstElement * element, GstStateChange transition)
|
{
|
GstVideoEncoder *encoder = GST_VIDEO_ENCODER (element);
|
|
if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) {
|
GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
|
gst_mpp_enc_reset (encoder, FALSE, TRUE);
|
GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
|
}
|
|
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
}
|
|
static void
|
gst_mpp_enc_init (GstMppEnc * self)
|
{
|
self->mpp_type = MPP_VIDEO_CodingUnused;
|
|
self->header_mode = DEFAULT_PROP_HEADER_MODE;
|
self->sei_mode = DEFAULT_PROP_SEI_MODE;
|
self->rc_mode = DEFAULT_PROP_RC_MODE;
|
self->rotation = DEFAULT_PROP_ROTATION;
|
self->gop = DEFAULT_PROP_GOP;
|
self->max_reenc = DEFAULT_PROP_MAX_REENC;
|
self->bps = DEFAULT_PROP_BPS;
|
self->bps_min = DEFAULT_PROP_BPS_MIN;
|
self->bps_max = DEFAULT_PROP_BPS_MAX;
|
self->zero_copy_pkt = DEFAULT_PROP_ZERO_COPY_PKT;
|
self->prop_dirty = TRUE;
|
}
|
|
#define GST_TYPE_MPP_ENC_HEADER_MODE (gst_mpp_enc_header_mode_get_type ())
|
static GType
|
gst_mpp_enc_header_mode_get_type (void)
|
{
|
static GType header_mode = 0;
|
|
if (!header_mode) {
|
static const GEnumValue modes[] = {
|
{MPP_ENC_HEADER_MODE_DEFAULT, "Only in the first frame", "first-frame"},
|
{MPP_ENC_HEADER_MODE_EACH_IDR, "In every IDR frames", "each-idr"},
|
{0, NULL, NULL}
|
};
|
header_mode = g_enum_register_static ("MppEncHeaderMode", modes);
|
}
|
return header_mode;
|
}
|
|
#define GST_TYPE_MPP_ENC_SEI_MODE (gst_mpp_enc_sei_mode_get_type ())
|
static GType
|
gst_mpp_enc_sei_mode_get_type (void)
|
{
|
static GType sei_mode = 0;
|
|
if (!sei_mode) {
|
static const GEnumValue modes[] = {
|
{MPP_ENC_SEI_MODE_DISABLE, "SEI disabled", "disable"},
|
{MPP_ENC_SEI_MODE_ONE_SEQ, "One SEI per sequence", "one-seq"},
|
{MPP_ENC_SEI_MODE_ONE_FRAME, "One SEI per frame(if changed)",
|
"one-frame"},
|
{0, NULL, NULL}
|
};
|
sei_mode = g_enum_register_static ("GstMppEncSeiMode", modes);
|
}
|
return sei_mode;
|
}
|
|
#define GST_TYPE_MPP_ENC_RC_MODE (gst_mpp_enc_rc_mode_get_type ())
|
static GType
|
gst_mpp_enc_rc_mode_get_type (void)
|
{
|
static GType rc_mode = 0;
|
|
if (!rc_mode) {
|
static const GEnumValue modes[] = {
|
{MPP_ENC_RC_MODE_VBR, "Variable bitrate", "vbr"},
|
{MPP_ENC_RC_MODE_CBR, "Constant bitrate", "cbr"},
|
{MPP_ENC_RC_MODE_FIXQP, "Fixed QP", "fixqp"},
|
{0, NULL, NULL}
|
};
|
rc_mode = g_enum_register_static ("GstMppEncRcMode", modes);
|
}
|
return rc_mode;
|
}
|
|
#ifdef HAVE_RGA
|
#define GST_TYPE_MPP_ENC_ROTATION (gst_mpp_enc_rotation_get_type ())
|
static GType
|
gst_mpp_enc_rotation_get_type (void)
|
{
|
static GType rotation = 0;
|
|
if (!rotation) {
|
static const GEnumValue rotations[] = {
|
{0, "Rotate 0", "0"},
|
{90, "Rotate 90", "90"},
|
{180, "Rotate 180", "180"},
|
{270, "Rotate 270", "270"},
|
{0, NULL, NULL}
|
};
|
rotation = g_enum_register_static ("GstMppEncRotation", rotations);
|
}
|
return rotation;
|
}
|
#endif
|
|
static void
|
gst_mpp_enc_class_init (GstMppEncClass * klass)
|
{
|
GstVideoEncoderClass *encoder_class = GST_VIDEO_ENCODER_CLASS (klass);
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "mppenc", 0, "MPP encoder");
|
|
encoder_class->start = GST_DEBUG_FUNCPTR (gst_mpp_enc_start);
|
encoder_class->stop = GST_DEBUG_FUNCPTR (gst_mpp_enc_stop);
|
encoder_class->flush = GST_DEBUG_FUNCPTR (gst_mpp_enc_flush);
|
encoder_class->finish = GST_DEBUG_FUNCPTR (gst_mpp_enc_finish);
|
encoder_class->set_format = GST_DEBUG_FUNCPTR (gst_mpp_enc_set_format);
|
encoder_class->propose_allocation =
|
GST_DEBUG_FUNCPTR (gst_mpp_enc_propose_allocation);
|
encoder_class->handle_frame = GST_DEBUG_FUNCPTR (gst_mpp_enc_handle_frame);
|
|
gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_mpp_enc_set_property);
|
gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_mpp_enc_get_property);
|
|
g_object_class_install_property (gobject_class, PROP_HEADER_MODE,
|
g_param_spec_enum ("header-mode", "Header mode",
|
"Header mode",
|
GST_TYPE_MPP_ENC_HEADER_MODE, DEFAULT_PROP_HEADER_MODE,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_SEI_MODE,
|
g_param_spec_enum ("sei-mode", "SEI mode",
|
"SEI mode",
|
GST_TYPE_MPP_ENC_SEI_MODE, DEFAULT_PROP_SEI_MODE,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_RC_MODE,
|
g_param_spec_enum ("rc-mode", "RC mode",
|
"RC mode",
|
GST_TYPE_MPP_ENC_RC_MODE, DEFAULT_PROP_RC_MODE,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
#ifdef HAVE_RGA
|
if (!gst_mpp_use_rga ())
|
goto no_rga;
|
|
g_object_class_install_property (gobject_class, PROP_ROTATION,
|
g_param_spec_enum ("rotation", "Rotation",
|
"Rotation",
|
GST_TYPE_MPP_ENC_ROTATION, DEFAULT_PROP_ROTATION,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_WIDTH,
|
g_param_spec_uint ("width", "Width",
|
"Width (0 = original)",
|
0, G_MAXINT, DEFAULT_PROP_WIDTH,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_HEIGHT,
|
g_param_spec_uint ("height", "Height",
|
"Height (0 = original)",
|
0, G_MAXINT, DEFAULT_PROP_HEIGHT,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
no_rga:
|
#endif
|
|
g_object_class_install_property (gobject_class, PROP_GOP,
|
g_param_spec_int ("gop", "Group of pictures",
|
"Group of pictures starting with I frame (-1 = FPS, 1 = all I frames)",
|
-1, G_MAXINT, DEFAULT_PROP_GOP,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_MAX_REENC,
|
g_param_spec_uint ("max-reenc", "Max re-encode times",
|
"Max re-encode times for one frame",
|
0, 3, DEFAULT_PROP_MAX_REENC,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_BPS,
|
g_param_spec_uint ("bps", "Target BPS",
|
"Target BPS (0 = auto calculate)",
|
0, G_MAXINT, DEFAULT_PROP_BPS,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_BPS_MIN,
|
g_param_spec_uint ("bps-min", "Min BPS",
|
"Min BPS (0 = auto calculate)",
|
0, G_MAXINT, DEFAULT_PROP_BPS_MIN,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_BPS_MAX,
|
g_param_spec_uint ("bps-max", "Max BPS",
|
"Max BPS (0 = auto calculate)",
|
0, G_MAXINT, DEFAULT_PROP_BPS_MAX,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_ZERO_COPY_PKT,
|
g_param_spec_boolean ("zero-copy-pkt", "Zero-copy encoded packet",
|
"Zero-copy encoded packet", DEFAULT_PROP_ZERO_COPY_PKT,
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
element_class->change_state = GST_DEBUG_FUNCPTR (gst_mpp_enc_change_state);
|
}
|