From dfe8a89861ce2ea180c67b916afa6b91fc6fca9c Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 20 Feb 2023 23:39:33 +0100 Subject: [PATCH 46/68] codecs: Add base class for stateless h264 encoder Add stateless h264 encoder base class and h264 frame structure. Upstream-Status: Pending --- gst-libs/gst/codecs/gsth264encoder.c | 352 +++++++++++++++++++++++++++ gst-libs/gst/codecs/gsth264encoder.h | 81 ++++++ gst-libs/gst/codecs/gsth264frame.c | 66 +++++ gst-libs/gst/codecs/gsth264frame.h | 70 ++++++ gst-libs/gst/codecs/meson.build | 5 + 5 files changed, 574 insertions(+) create mode 100644 gst-libs/gst/codecs/gsth264encoder.c create mode 100644 gst-libs/gst/codecs/gsth264encoder.h create mode 100644 gst-libs/gst/codecs/gsth264frame.c create mode 100644 gst-libs/gst/codecs/gsth264frame.h diff --git a/gst-libs/gst/codecs/gsth264encoder.c b/gst-libs/gst/codecs/gsth264encoder.c new file mode 100644 index 0000000..b024939 --- /dev/null +++ b/gst-libs/gst/codecs/gsth264encoder.c @@ -0,0 +1,352 @@ +/* GStreamer + * Copyright (C) 2023 Michael Grzeschik + * + * 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 "gsth264encoder.h" + +#include +#include +#include +#include + +GST_DEBUG_CATEGORY (gst_h264_encoder_debug); +#define GST_CAT_DEFAULT gst_h264_encoder_debug + +#define H264ENC_DEFAULT_KEYFRAME_INTERVAL 30 + +#define H264_MAX_QUALITY 63 +#define H264_MIN_QUALITY 0 + +#define H264_DEFAULT_BITRATE 100000 + +enum +{ + PROP_0, + PROP_KEYFRAME_INTERVAL, + PROP_MAX_QUALITY, + PROP_MIN_QUALITY, + PROP_BITRATE, +}; + +struct _GstH264EncoderPrivate +{ + 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_h264_encoder_parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstH264Encoder, gst_h264_encoder, + GST_TYPE_VIDEO_ENCODER, + G_ADD_PRIVATE (GstH264Encoder); + GST_DEBUG_CATEGORY_INIT (gst_h264_encoder_debug, "h264encoder", 0, + "H264 Video Encoder")); + +static void +gst_h264_encoder_init (GstH264Encoder * self) +{ + self->priv = gst_h264_encoder_get_instance_private (self); +} + +static void +gst_h264_encoder_finalize (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_h264_encoder_start (GstVideoEncoder * encoder) +{ + GstH264Encoder *self = GST_H264_ENCODER (encoder); + GstH264EncoderPrivate *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_h264_encoder_stop (GstVideoEncoder * encoder) +{ + return TRUE; +} + +static gboolean +gst_h264_encoder_set_format (GstVideoEncoder * encoder, + GstVideoCodecState * state) +{ + return TRUE; +} + +static GstFlowReturn +gst_h264_encoder_set_quality (GstH264Encoder * self, GstH264Frame * h264_frame) +{ + GstH264EncoderPrivate *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; + + h264_frame->quality = qp; + + return GST_FLOW_OK; +} + +static GstFlowReturn +gst_h264_encoder_set_frame_type (GstH264Encoder * self, GstH264Frame * h264_frame) +{ + GstH264EncoderPrivate *priv = self->priv; + GstVideoCodecFrame *frame = h264_frame->frame; + + if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) { + h264_frame->type = GstH264Keyframe; + 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"); + h264_frame->type = GstH264Keyframe; + return GST_FLOW_OK; + } + + /* Generate a interframe */ + GST_DEBUG_OBJECT (self, "Generate a interframe"); + h264_frame->type = GstH264Inter; + return GST_FLOW_OK; +} + +static void +gst_h264_encoder_mark_frame (GstH264Encoder * self, GstH264Frame * h264_frame) +{ + GstVideoCodecFrame *frame = h264_frame->frame; + GstH264EncoderPrivate *priv = self->priv; + + switch (h264_frame->type) { + case GstH264Keyframe: + priv->last_keyframe = frame->system_frame_number; + break; + } + + priv->current_quality = h264_frame->quality; + priv->used_bytes += gst_buffer_get_size (frame->output_buffer); + priv->nb_frames++; +} + +static GstFlowReturn +gst_h264_encoder_handle_frame (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame) +{ + GstH264Encoder *self = GST_H264_ENCODER (encoder); + GstH264EncoderClass *klass = GST_H264_ENCODER_GET_CLASS (self); + GstFlowReturn ret = GST_FLOW_OK; + GstH264Frame *h264_frame = gst_h264_frame_new (frame); + + ret = gst_h264_encoder_set_frame_type (self, h264_frame); + if (ret != GST_FLOW_OK) + return ret; + + ret = gst_h264_encoder_set_quality (self, h264_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, h264_frame); + if (ret == GST_FLOW_OK) + gst_h264_encoder_mark_frame (self, h264_frame); + } + + gst_h264_frame_unref (h264_frame); + + return ret; +} + +static void +gst_h264_encoder_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstH264Encoder *self = GST_H264_ENCODER (object); + GstH264EncoderPrivate *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_h264_encoder_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstH264Encoder *self = GST_H264_ENCODER (object); + GstH264EncoderPrivate *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_h264_encoder_class_init (GstH264EncoderClass * klass) +{ + GstVideoEncoderClass *encoder_class = GST_VIDEO_ENCODER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = GST_DEBUG_FUNCPTR (gst_h264_encoder_finalize); + object_class->get_property = gst_h264_encoder_get_property; + object_class->set_property = gst_h264_encoder_set_property; + + encoder_class->start = GST_DEBUG_FUNCPTR (gst_h264_encoder_start); + encoder_class->stop = GST_DEBUG_FUNCPTR (gst_h264_encoder_stop); + encoder_class->set_format = GST_DEBUG_FUNCPTR (gst_h264_encoder_set_format); + encoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_h264_encoder_handle_frame); + + /** + * GstH264Encoder: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, H264ENC_DEFAULT_KEYFRAME_INTERVAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); + + /** + * GstH264Encoder: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)", + H264_MIN_QUALITY, H264_MAX_QUALITY, H264_MAX_QUALITY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); + + /** + * GstH264Encoder: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)", + H264_MIN_QUALITY, H264_MAX_QUALITY, H264_MIN_QUALITY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); + + /** + * GstH264Encoder: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, H264_DEFAULT_BITRATE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT)); +} diff --git a/gst-libs/gst/codecs/gsth264encoder.h b/gst-libs/gst/codecs/gsth264encoder.h new file mode 100644 index 0000000..49f9fd3 --- /dev/null +++ b/gst-libs/gst/codecs/gsth264encoder.h @@ -0,0 +1,81 @@ +/* GStreamer + * Copyright (C) 2023 Michael Grzeschik + * + * 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_H264_ENCODER_H__ +#define __GST_H264_ENCODER_H__ + +#include + +#include +#include + +#include "gsth264frame.h" + +G_BEGIN_DECLS +#define GST_TYPE_H264_ENCODER (gst_h264_encoder_get_type()) +#define GST_H264_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_H264_ENCODER,GstH264Encoder)) +#define GST_H264_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_H264_ENCODER,GstH264EncoderClass)) +#define GST_H264_ENCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_H264_ENCODER,GstH264EncoderClass)) +#define GST_IS_H264_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_H264_ENCODER)) +#define GST_IS_H264_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_H264_ENCODER)) +#define GST_H264_ENCODER_CAST(obj) ((GstH264Encoder*)obj) +typedef struct _GstH264Encoder GstH264Encoder; +typedef struct _GstH264EncoderClass GstH264EncoderClass; +typedef struct _GstH264EncoderPrivate GstH264EncoderPrivate; + +/** + * GstH264Encoder: + * + * The opaque #GstH264Encoder data structure. + */ +struct _GstH264Encoder +{ + /*< private > */ + GstVideoEncoder parent; + + /*< private > */ + GstH264EncoderPrivate *priv; + gpointer padding[GST_PADDING_LARGE]; +}; + +/** + * GstH264EncoderClass: + */ +struct _GstH264EncoderClass +{ + GstVideoEncoderClass parent_class; + + /** + * GstH264EncoderClass::encode_frame: + * @encoder: a #GstH264Encoder + * @frame: a #GstH264Frame + * + * Provide the frame to be encoded with the encode parameters (to be defined) + */ + GstFlowReturn (*encode_frame) (GstH264Encoder * encoder, + GstH264Frame * frame); + /*< private > */ + gpointer padding[GST_PADDING_LARGE]; +}; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstH264Encoder, gst_object_unref) + GST_CODECS_API GType gst_h264_encoder_get_type (void); + +G_END_DECLS +#endif /* __GST_H264_ENCODER_H__ */ diff --git a/gst-libs/gst/codecs/gsth264frame.c b/gst-libs/gst/codecs/gsth264frame.c new file mode 100644 index 0000000..81420d7 --- /dev/null +++ b/gst-libs/gst/codecs/gsth264frame.c @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) 2023 Michael Grzeschik + * + * 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 "gsth264frame.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_h264_encoder_debug); +#define GST_CAT_DEFAULT gst_h264_encoder_debug + +GST_DEFINE_MINI_OBJECT_TYPE (GstH264Frame, gst_h264_frame); + +static void +_gst_h264_frame_free (GstH264Frame * frame) +{ + GST_TRACE ("Free frame %p", frame); + + gst_video_codec_frame_unref (frame->frame); + + g_free (frame); +} + +/** + * gst_h264_frame_new: + * + * Create new #GstH264Frame + * + * Returns: a new #GstH264Frame + */ +GstH264Frame * +gst_h264_frame_new (GstVideoCodecFrame * f) +{ + GstH264Frame *frame; + + if (!f) + return NULL; + + frame = g_new0 (GstH264Frame, 1); + + gst_mini_object_init (GST_MINI_OBJECT_CAST (frame), 0, + GST_TYPE_H264_FRAME, NULL, NULL, + (GstMiniObjectFreeFunction) _gst_h264_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/gsth264frame.h b/gst-libs/gst/codecs/gsth264frame.h new file mode 100644 index 0000000..3c90d53 --- /dev/null +++ b/gst-libs/gst/codecs/gsth264frame.h @@ -0,0 +1,70 @@ +/* GStreamer + * Copyright (C) 2023 Michael Grzeschik + * + * 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_H264_FRAME_H__ +#define __GTS_H264_FRAME_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_H264_FRAME (gst_h264_frame_get_type()) +#define GST_IS_H264_FRAME(obj) (GST_IS_MINI_OBJECT_TYPE(obj, GST_TYPE_H264_FRAME)) +#define GST_H264_FRAME(obj) ((GstH264Frame *)obj) +#define GST_H264_FRAME_CAST(obj) (GST_H264_FRAME(obj)) + +typedef struct _GstH264Frame GstH264Frame; + +enum +{ + GstH264Keyframe, + GstH264Inter, +}; + +struct _GstH264Frame +{ + GstMiniObject parent; + gint type; + gint quality; + + GstVideoCodecFrame *frame; +}; + +GST_CODECS_API +GType gst_h264_frame_get_type (void); + +GST_CODECS_API +GstH264Frame * gst_h264_frame_new (GstVideoCodecFrame *f); + +static inline GstH264Frame * +gst_h264_frame_ref (GstH264Frame * frame) +{ + return (GstH264Frame *) gst_mini_object_ref (GST_MINI_OBJECT_CAST (frame)); +} + +static inline void +gst_h264_frame_unref (GstH264Frame * frame) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (frame)); +} + +G_END_DECLS + +#endif /* __GTS_H264_FRAME_H__ */ diff --git a/gst-libs/gst/codecs/meson.build b/gst-libs/gst/codecs/meson.build index 458d90d..3ea3bdc 100644 --- a/gst-libs/gst/codecs/meson.build +++ b/gst-libs/gst/codecs/meson.build @@ -14,6 +14,8 @@ codecs_sources = files( 'gstvp9statefulparser.c', 'gstvp8encoder.c', 'gstvp8frame.c', + 'gsth264encoder.c', + 'gsth264frame.c', ) codecs_headers = files( @@ -32,6 +34,8 @@ codecs_headers = files( 'gstvp9statefulparser.h', 'gstvp8encoder.h', 'gstvp8frame.h', + 'gsth264encoder.h', + 'gsth264frame.h', ) cp_args = [ @@ -71,6 +75,7 @@ if build_gir '--c-include=gst/codecs/gstvp8decoder.h', '--c-include=gst/codecs/gstmpeg2decoder.h', '--c-include=gst/codecs/gstvp8encoder.h', + '--c-include=gst/codecs/gsth264encoder.h', ], 'dependencies' : [gstvideo_dep, gstcodecparsers_dep] } -- 2.25.1