From ebb197aacf8cb21a79704851b045d28592848953 Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Mon, 16 Jan 2023 17:34:10 +0100 Subject: [PATCH 1/5] codecs: Add base class for stateless vp8 encoder Add stateless vp8 encoder base class and vp8 frame structure. --- gst-libs/gst/codecs/gstvp8encoder.c | 351 ++++++++++++++++++++++++++++ gst-libs/gst/codecs/gstvp8encoder.h | 81 +++++++ gst-libs/gst/codecs/gstvp8frame.c | 66 ++++++ gst-libs/gst/codecs/gstvp8frame.h | 70 ++++++ gst-libs/gst/codecs/meson.build | 7 +- 5 files changed, 574 insertions(+), 1 deletion(-) create mode 100644 gst-libs/gst/codecs/gstvp8encoder.c create mode 100644 gst-libs/gst/codecs/gstvp8encoder.h create mode 100644 gst-libs/gst/codecs/gstvp8frame.c create mode 100644 gst-libs/gst/codecs/gstvp8frame.h diff --git a/gst-libs/gst/codecs/gstvp8encoder.c b/gst-libs/gst/codecs/gstvp8encoder.c new file mode 100644 index 0000000..b105786 --- /dev/null +++ b/gst-libs/gst/codecs/gstvp8encoder.c @@ -0,0 +1,351 @@ +/* GStreamer + * Copyright (C) 2022 Benjamin Gaignard + * + * 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 "gstvp8encoder.h" + +#include +#include +#include + +GST_DEBUG_CATEGORY (gst_vp8_encoder_debug); +#define GST_CAT_DEFAULT gst_vp8_encoder_debug + +#define VP8ENC_DEFAULT_KEYFRAME_INTERVAL 30 + +#define VP8_MAX_QUALITY 63 +#define VP8_MIN_QUALITY 0 + +#define VP8_DEFAULT_BITRATE 100000 + +enum +{ + PROP_0, + PROP_KEYFRAME_INTERVAL, + PROP_MAX_QUALITY, + PROP_MIN_QUALITY, + PROP_BITRATE, +}; + +struct _GstVp8EncoderPrivate +{ + gint keyframe_interval; + + guint32 last_keyframe; + + guint64 targeted_bitrate; + gint max_quality; + gint min_quality; + gint current_quality; + guint64 used_bytes; + guint64 nb_frames; +}; + +#define parent_class gst_vp8_encoder_parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstVp8Encoder, gst_vp8_encoder, + GST_TYPE_VIDEO_ENCODER, + G_ADD_PRIVATE (GstVp8Encoder); + GST_DEBUG_CATEGORY_INIT (gst_vp8_encoder_debug, "vp8encoder", 0, + "Vp8 Video Encoder")); + +static void +gst_vp8_encoder_init (GstVp8Encoder * self) +{ + self->priv = gst_vp8_encoder_get_instance_private (self); +} + +static void +gst_vp8_encoder_finalize (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_vp8_encoder_start (GstVideoEncoder * encoder) +{ + GstVp8Encoder *self = GST_VP8_ENCODER (encoder); + GstVp8EncoderPrivate *priv = self->priv; + + priv->last_keyframe = 0; + priv->current_quality = priv->min_quality; + priv->used_bytes = 0; + priv->nb_frames = 0; + + return TRUE; +} + +static gboolean +gst_vp8_encoder_stop (GstVideoEncoder * encoder) +{ + return TRUE; +} + +static gboolean +gst_vp8_encoder_set_format (GstVideoEncoder * encoder, + GstVideoCodecState * state) +{ + return TRUE; +} + +static GstFlowReturn +gst_vp8_encoder_set_quality (GstVp8Encoder * self, GstVp8Frame * vp8_frame) +{ + GstVp8EncoderPrivate *priv = self->priv; + GstVideoEncoder *encoder = GST_VIDEO_ENCODER (self); + GstVideoCodecState *output_state = + gst_video_encoder_get_output_state (encoder); + gint qp = priv->current_quality; + guint64 bitrate = 0; + guint fps_n = 30, fps_d = 1; + + if (output_state == NULL) + return qp; + + if (GST_VIDEO_INFO_FPS_N (&output_state->info) != 0) { + fps_n = GST_VIDEO_INFO_FPS_N (&output_state->info); + fps_d = GST_VIDEO_INFO_FPS_D (&output_state->info); + } + gst_video_codec_state_unref (output_state); + + bitrate = (priv->used_bytes * 8 * fps_n) / (priv->nb_frames * fps_d); + if (bitrate > priv->targeted_bitrate) { + qp++; + } + + if (bitrate < priv->targeted_bitrate) { + qp--; + } + + if (qp > priv->max_quality) + qp = priv->max_quality; + if (qp < priv->min_quality) + qp = priv->min_quality; + + vp8_frame->quality = qp; + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_vp8_encoder_set_frame_type (GstVp8Encoder * self, GstVp8Frame * vp8_frame) +{ + GstVp8EncoderPrivate *priv = self->priv; + GstVideoCodecFrame *frame = vp8_frame->frame; + + if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) { + vp8_frame->type = GstVp8Keyframe; + return GST_FLOW_OK; + } + + if ((frame->system_frame_number - priv->last_keyframe) > + priv->keyframe_interval || frame->system_frame_number == 0) { + /* Generate a keyframe */ + GST_DEBUG_OBJECT (self, "Generate a keyframe"); + vp8_frame->type = GstVp8Keyframe; + return GST_FLOW_OK; + } + + /* Generate a interframe */ + GST_DEBUG_OBJECT (self, "Generate a interframe"); + vp8_frame->type = GstVp8Inter; + return GST_FLOW_OK; +} + +static void +gst_vp8_encoder_mark_frame (GstVp8Encoder * self, GstVp8Frame * vp8_frame) +{ + GstVideoCodecFrame *frame = vp8_frame->frame; + GstVp8EncoderPrivate *priv = self->priv; + + switch (vp8_frame->type) { + case GstVp8Keyframe: + priv->last_keyframe = frame->system_frame_number; + break; + } + + priv->current_quality = vp8_frame->quality; + priv->used_bytes += gst_buffer_get_size (frame->output_buffer); + priv->nb_frames++; +} + +static GstFlowReturn +gst_vp8_encoder_handle_frame (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame) +{ + GstVp8Encoder *self = GST_VP8_ENCODER (encoder); + GstVp8EncoderClass *klass = GST_VP8_ENCODER_GET_CLASS (self); + GstFlowReturn ret = GST_FLOW_OK; + GstVp8Frame *vp8_frame = gst_vp8_frame_new (frame); + + ret = gst_vp8_encoder_set_frame_type (self, vp8_frame); + if (ret != GST_FLOW_OK) + return ret; + + ret = gst_vp8_encoder_set_quality (self, vp8_frame); + if (ret != GST_FLOW_OK) + return ret; + + /* TODO: add encoding parameters management here + * for now just send the frame to encode */ + if (klass->encode_frame) { + ret = klass->encode_frame (self, vp8_frame); + if (ret == GST_FLOW_OK) + gst_vp8_encoder_mark_frame (self, vp8_frame); + } + + gst_vp8_frame_unref (vp8_frame); + + return ret; +} + +static void +gst_vp8_encoder_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstVp8Encoder *self = GST_VP8_ENCODER (object); + GstVp8EncoderPrivate *priv = self->priv; + + switch (property_id) { + case PROP_KEYFRAME_INTERVAL: + GST_OBJECT_LOCK (self); + g_value_set_int (value, priv->keyframe_interval); + GST_OBJECT_UNLOCK (self); + break; + case PROP_MAX_QUALITY: + GST_OBJECT_LOCK (self); + g_value_set_int (value, priv->max_quality); + GST_OBJECT_UNLOCK (self); + break; + case PROP_MIN_QUALITY: + GST_OBJECT_LOCK (self); + g_value_set_int (value, priv->min_quality); + GST_OBJECT_UNLOCK (self); + break; + case PROP_BITRATE: + GST_OBJECT_LOCK (self); + g_value_set_uint64 (value, priv->targeted_bitrate); + GST_OBJECT_UNLOCK (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gst_vp8_encoder_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstVp8Encoder *self = GST_VP8_ENCODER (object); + GstVp8EncoderPrivate *priv = self->priv; + + switch (property_id) { + case PROP_KEYFRAME_INTERVAL: + GST_OBJECT_LOCK (self); + priv->keyframe_interval = g_value_get_int (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_MAX_QUALITY: + GST_OBJECT_LOCK (self); + priv->max_quality = g_value_get_int (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_MIN_QUALITY: + GST_OBJECT_LOCK (self); + priv->min_quality = g_value_get_int (value); + GST_OBJECT_UNLOCK (self); + break; + case PROP_BITRATE: + GST_OBJECT_LOCK (self); + priv->targeted_bitrate = g_value_get_uint64 (value); + GST_OBJECT_UNLOCK (self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gst_vp8_encoder_class_init (GstVp8EncoderClass * klass) +{ + GstVideoEncoderClass *encoder_class = GST_VIDEO_ENCODER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = GST_DEBUG_FUNCPTR (gst_vp8_encoder_finalize); + object_class->get_property = gst_vp8_encoder_get_property; + object_class->set_property = gst_vp8_encoder_set_property; + + encoder_class->start = GST_DEBUG_FUNCPTR (gst_vp8_encoder_start); + encoder_class->stop = GST_DEBUG_FUNCPTR (gst_vp8_encoder_stop); + encoder_class->set_format = GST_DEBUG_FUNCPTR (gst_vp8_encoder_set_format); + encoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_vp8_encoder_handle_frame); + + /** + * GstVp8Encoder:keyframe-interval: + * + * + * Since: 1.2x + */ + g_object_class_install_property (object_class, PROP_KEYFRAME_INTERVAL, + g_param_spec_int ("keyframe-interval", "Keyframe Interval", + "Interval between keyframes", + 0, G_MAXINT, VP8ENC_DEFAULT_KEYFRAME_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); + + /** + * GstVp8Encoder:max-quality: + * + * + * Since: 1.2x + */ + g_object_class_install_property (object_class, PROP_MAX_QUALITY, + g_param_spec_int ("max-quality", "Max Quality Level", + "Set upper quality limit (lower number equates to higher quality but more bits)", + VP8_MIN_QUALITY, VP8_MAX_QUALITY, VP8_MAX_QUALITY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); + + /** + * GstVp8Encoder:min-quality: + * + * + * Since: 1.2x + */ + g_object_class_install_property (object_class, PROP_MIN_QUALITY, + g_param_spec_int ("min-quality", "Min Quality Level", + "Set lower quality limit (lower number equates to higher quality but more bits)", + VP8_MIN_QUALITY, VP8_MAX_QUALITY, VP8_MIN_QUALITY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); + + /** + * GstVp8Encoder:bitrate: + * + * + * Since: 1.2x + */ + g_object_class_install_property (object_class, PROP_BITRATE, + g_param_spec_uint64 ("bitrate", "Targeted bitrate", + "Set bitrate target", + 0, UINT_MAX, VP8_DEFAULT_BITRATE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); +} diff --git a/gst-libs/gst/codecs/gstvp8encoder.h b/gst-libs/gst/codecs/gstvp8encoder.h new file mode 100644 index 0000000..3999ed3 --- /dev/null +++ b/gst-libs/gst/codecs/gstvp8encoder.h @@ -0,0 +1,81 @@ +/* GStreamer + * Copyright (C) 2022 Benjamin Gaignard + * + * 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. + */ + +#ifndef __GST_VP8_ENCODER_H__ +#define __GST_VP8_ENCODER_H__ + +#include + +#include +#include + +#include "gstvp8frame.h" + +G_BEGIN_DECLS +#define GST_TYPE_VP8_ENCODER (gst_vp8_encoder_get_type()) +#define GST_VP8_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VP8_ENCODER,GstVp8Encoder)) +#define GST_VP8_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VP8_ENCODER,GstVp8EncoderClass)) +#define GST_VP8_ENCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_VP8_ENCODER,GstVp8EncoderClass)) +#define GST_IS_VP8_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VP8_ENCODER)) +#define GST_IS_VP8_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VP8_ENCODER)) +#define GST_VP8_ENCODER_CAST(obj) ((GstVp8Encoder*)obj) +typedef struct _GstVp8Encoder GstVp8Encoder; +typedef struct _GstVp8EncoderClass GstVp8EncoderClass; +typedef struct _GstVp8EncoderPrivate GstVp8EncoderPrivate; + +/** + * GstVp8Encoder: + * + * The opaque #GstVp8Encoder data structure. + */ +struct _GstVp8Encoder +{ + /*< private > */ + GstVideoEncoder parent; + + /*< private > */ + GstVp8EncoderPrivate *priv; + gpointer padding[GST_PADDING_LARGE]; +}; + +/** + * GstVp8EncoderClass: + */ +struct _GstVp8EncoderClass +{ + GstVideoEncoderClass parent_class; + + /** + * GstVp8EncoderClass::encode_frame: + * @encoder: a #GstVp8Encoder + * @frame: a #GstVp8Frame + * + * Provide the frame to be encoded with the encode parameters (to be defined) + */ + GstFlowReturn (*encode_frame) (GstVp8Encoder * encoder, + GstVp8Frame * frame); + /*< private > */ + gpointer padding[GST_PADDING_LARGE]; +}; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstVp8Encoder, gst_object_unref) + GST_CODECS_API GType gst_vp8_encoder_get_type (void); + +G_END_DECLS +#endif /* __GST_VP8_ENCODER_H__ */ diff --git a/gst-libs/gst/codecs/gstvp8frame.c b/gst-libs/gst/codecs/gstvp8frame.c new file mode 100644 index 0000000..6f26bd5 --- /dev/null +++ b/gst-libs/gst/codecs/gstvp8frame.c @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2022 Benjamin Gaignard + * + * 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 +#endif + +#include "gstvp8frame.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_vp8_encoder_debug); +#define GST_CAT_DEFAULT gst_vp8_encoder_debug + +GST_DEFINE_MINI_OBJECT_TYPE (GstVp8Frame, gst_vp8_frame); + +static void +_gst_vp8_frame_free (GstVp8Frame * frame) +{ + GST_TRACE ("Free frame %p", frame); + + gst_video_codec_frame_unref (frame->frame); + + g_free (frame); +} + +/** + * gst_vp8_frame_new: + * + * Create new #GstVp8Frame + * + * Returns: a new #GstVp8Frame + */ +GstVp8Frame * +gst_vp8_frame_new (GstVideoCodecFrame * f) +{ + GstVp8Frame *frame; + + if (!f) + return NULL; + + frame = g_new0 (GstVp8Frame, 1); + + gst_mini_object_init (GST_MINI_OBJECT_CAST (frame), 0, + GST_TYPE_VP8_FRAME, NULL, NULL, + (GstMiniObjectFreeFunction) _gst_vp8_frame_free); + + frame->frame = gst_video_codec_frame_ref (f); + + GST_TRACE ("New frame %p", frame); + + return frame; +} diff --git a/gst-libs/gst/codecs/gstvp8frame.h b/gst-libs/gst/codecs/gstvp8frame.h new file mode 100644 index 0000000..a3d5c21 --- /dev/null +++ b/gst-libs/gst/codecs/gstvp8frame.h @@ -0,0 +1,70 @@ +/* GStreamer + * Copyright (C) 2022 Benjamin Gaignard + * + * 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. + */ + +#ifndef __GTS_VP8_FRAME_H__ +#define __GTS_VP8_FRAME_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_VP8_FRAME (gst_vp8_frame_get_type()) +#define GST_IS_VP8_FRAME(obj) (GST_IS_MINI_OBJECT_TYPE(obj, GST_TYPE_VP8_FRAME)) +#define GST_VP8_FRAME(obj) ((GstVp8Frame *)obj) +#define GST_VP8_FRAME_CAST(obj) (GST_VP8_FRAME(obj)) + +typedef struct _GstVp8Frame GstVp8Frame; + +enum +{ + GstVp8Keyframe, + GstVp8Inter, +}; + +struct _GstVp8Frame +{ + GstMiniObject parent; + gint type; + gint quality; + + GstVideoCodecFrame *frame; +}; + +GST_CODECS_API +GType gst_vp8_frame_get_type (void); + +GST_CODECS_API +GstVp8Frame * gst_vp8_frame_new (GstVideoCodecFrame *f); + +static inline GstVp8Frame * +gst_vp8_frame_ref (GstVp8Frame * frame) +{ + return (GstVp8Frame *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (frame)); +} + +static inline void +gst_vp8_frame_unref (GstVp8Frame * frame) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (frame)); +} + +G_END_DECLS + +#endif /* __GTS_VP8_FRAME_H__ */ diff --git a/gst-libs/gst/codecs/meson.build b/gst-libs/gst/codecs/meson.build index f2bec85..458d90d 100644 --- a/gst-libs/gst/codecs/meson.build +++ b/gst-libs/gst/codecs/meson.build @@ -12,6 +12,8 @@ codecs_sources = files( 'gstav1decoder.c', 'gstav1picture.c', 'gstvp9statefulparser.c', + 'gstvp8encoder.c', + 'gstvp8frame.c', ) codecs_headers = files( @@ -28,6 +30,8 @@ codecs_headers = files( 'gstav1decoder.h', 'gstav1picture.h', 'gstvp9statefulparser.h', + 'gstvp8encoder.h', + 'gstvp8frame.h', ) cp_args = [ @@ -66,6 +70,7 @@ if build_gir '--c-include=gst/codecs/gstvp9decoder.h', '--c-include=gst/codecs/gstvp8decoder.h', '--c-include=gst/codecs/gstmpeg2decoder.h', + '--c-include=gst/codecs/gstvp8encoder.h', ], 'dependencies' : [gstvideo_dep, gstcodecparsers_dep] } @@ -81,4 +86,4 @@ gstcodecs_dep = declare_dependency(link_with : gstcodecs, include_directories : [libsinc], sources: gen_sources, dependencies : [gstvideo_dep, gstcodecparsers_dep]) -meson.override_dependency(pkg_name, gstcodecs_dep) \ No newline at end of file +meson.override_dependency(pkg_name, gstcodecs_dep) -- 2.25.1