From ab51f9d3d91b8210a4e8d5796eeaba21ccbda6d8 Mon Sep 17 00:00:00 2001 From: Denis Shimizu Date: Mon, 27 Nov 2023 11:25:54 -0500 Subject: [PATCH 64/68] codecs: Introduce a PID based rate controller This is a simplistic rate controller using a PID and some boundaries. This has only been tested for H.264 for now and may need some more work to support various quantizer scale. Upstream-Status: Pending --- gst-libs/gst/codecs/gstratecontroller.c | 241 ++++++++++++++++++++++++ gst-libs/gst/codecs/gstratecontroller.h | 70 +++++++ gst-libs/gst/codecs/meson.build | 1 + 3 files changed, 312 insertions(+) create mode 100644 gst-libs/gst/codecs/gstratecontroller.c create mode 100644 gst-libs/gst/codecs/gstratecontroller.h diff --git a/gst-libs/gst/codecs/gstratecontroller.c b/gst-libs/gst/codecs/gstratecontroller.c new file mode 100644 index 0000000..42f7d0c --- /dev/null +++ b/gst-libs/gst/codecs/gstratecontroller.c @@ -0,0 +1,241 @@ +/* GStreamer + * Copyright (C) 2023 Denis Yuji Shimizu + * + * 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. + */ + +#include "gstratecontroller.h" + +#include + +#define KP_CONST 0.0001 +#define KI_CONST 0.0000000 +#define KD_CONST 0.0000000 + +typedef struct +{ + gdouble prev_error; + gdouble integral; +} PIDController; + +struct _GstRateController +{ + GstObject parent; + + PIDController pid; + gdouble qp; + gsize total_macroblocks; + GstClockTime frame_duration; + + /* configuration */ + gint max_qp; + gint min_qp; + gint init_qp; + gint qp_step; + GstRateControlMode mode; + gint bitrate; +}; + +GType +gst_rate_control_mode_get_type (void) +{ + const static GEnumValue rate_control_modes[] = { + {GST_RC_CONSTANT_QP, "cqp", "Constant QP"}, + {GST_RC_CONSTANT_BITRATE, "cbr", "Constant Bitrate"}, + {0, NULL, NULL}, + }; + static GType rc_mode_type = 0; + + if (g_once_init_enter (&rc_mode_type)) { + GType _type = g_enum_register_static ("GstRateControlMode", + rate_control_modes); + g_once_init_leave (&rc_mode_type, _type); + } + + return rc_mode_type; +} + +G_DEFINE_TYPE (GstRateController, gst_rc, G_TYPE_OBJECT); + +static void +gst_rc_init (GstRateController * self) +{ +} + +static void +gst_rc_class_init (GstRateControllerClass * klass) +{ +} + +GstRateController * +gst_rc_new (void) +{ + return g_object_new (GST_RATE_CONTROLLER_TYPE, NULL); +} + +static gdouble +pid_control (GstRateController * self, gdouble measured_value, + gdouble target_bitrate) +{ + PIDController *pid = &self->pid; + gdouble error = target_bitrate - measured_value; + gdouble derivative = error - pid->prev_error; + pid->integral += error; + pid->prev_error = error; + + /* Output is inverted, since QP and bitrate are inversely related (higher QP + * means lower bitrate) */ + gdouble output = + 0.0 - (KP_CONST * error + + KI_CONST * pid->integral + KD_CONST * derivative); + + return output; +} + +void +gst_rc_record (GstRateController * self, GstRcFrameType frame_type, + gsize coded_size, GstClockTime duration) +{ + /* If there is no duration, use frame rate */ + if (!GST_CLOCK_TIME_IS_VALID (duration) || duration == 0) + duration = self->frame_duration; + + /* We simply ignore keyframe for now */ + if (frame_type != GST_RC_KEY_FRAME) { + GstClockTime bps = + gst_util_uint64_scale (coded_size * 8, GST_SECOND, duration); + + double target_bps_per_macroblock = + (gdouble) self->bitrate / self->total_macroblocks; + double bps_per_macroblock = + gst_guint64_to_gdouble (bps) / self->total_macroblocks; + + gdouble pid_output = + pid_control (self, bps_per_macroblock, target_bps_per_macroblock); + gdouble qp_increase = CLAMP (pid_output, + 0.0 - (gdouble) self->qp_step, (gdouble) self->qp_step); + self->qp += qp_increase; + } +} + +gint +gst_rc_get_max_qp (GstRateController * self) +{ + return self->max_qp; +} + +gint +gst_rc_get_min_qp (GstRateController * self) +{ + return self->min_qp; +} + +gint +gst_rc_get_qp_step (GstRateController * self) +{ + return self->qp_step; +} + +gint +gst_rc_get_init_qp (GstRateController * self) +{ + return self->init_qp; +} + +GstRateControlMode +gst_rc_get_mode (GstRateController * self) +{ + return self->mode; +} + +gint +gst_rc_get_bitrate (GstRateController * self) +{ + return self->bitrate; +} + +void +gst_rc_set_format (GstRateController * self, const GstVideoInfo * vinfo) +{ + gint width = GST_VIDEO_INFO_WIDTH (vinfo); + gint height = GST_VIDEO_INFO_HEIGHT (vinfo); + gint fps_n = GST_VIDEO_INFO_FPS_N (vinfo); + gint fps_d = GST_VIDEO_INFO_FPS_D (vinfo); + + gint width_in_macroblocks = (width + 15) / 16; + gint height_in_macroblocks = (height + 15) / 16; + + self->total_macroblocks = width_in_macroblocks * height_in_macroblocks; + self->pid.prev_error = 0; + self->pid.integral = 0; + self->qp = self->init_qp; + + if (!fps_n || !fps_d) { + fps_n = 30; + fps_d = 1; + } + + self->frame_duration = gst_util_uint64_scale (fps_d, GST_SECOND, fps_n); +} + +void +gst_rc_set_max_qp (GstRateController * self, gint max_qp) +{ + self->max_qp = max_qp; +} + +void +gst_rc_set_min_qp (GstRateController * self, gint min_qp) +{ + self->min_qp = min_qp; +} + +void +gst_rc_set_qp_step (GstRateController * self, gint qp_step) +{ + self->qp_step = qp_step; +} + +void +gst_rc_set_init_qp (GstRateController * self, gint init_qp) +{ + self->init_qp = self->qp = init_qp; +} + +void +gst_rc_set_mode (GstRateController * self, GstRateControlMode mode) +{ + self->mode = mode; +} + +void +gst_rc_set_bitrate (GstRateController * self, gint bitrate) +{ + self->bitrate = bitrate; +} + +gint +gst_rc_get_qp (GstRateController * self) +{ + switch (self->mode) { + case GST_RC_CONSTANT_BITRATE: + return CLAMP (round (self->qp), self->min_qp, self->max_qp); + case GST_RC_CONSTANT_QP: + return self->init_qp; + } + + g_assert_not_reached (); +} diff --git a/gst-libs/gst/codecs/gstratecontroller.h b/gst-libs/gst/codecs/gstratecontroller.h new file mode 100644 index 0000000..f71cb8e --- /dev/null +++ b/gst-libs/gst/codecs/gstratecontroller.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. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +typedef enum +{ + GST_RC_CONSTANT_QP, + GST_RC_CONSTANT_BITRATE, +} GstRateControlMode; + +typedef enum +{ + GST_RC_KEY_FRAME, + GST_RC_INTER_FRAME, +} GstRcFrameType; + +GType gst_rate_control_mode_get_type (void); + +#define GST_RATE_CONTROLLER_TYPE (gst_rc_get_type()) +G_DECLARE_FINAL_TYPE(GstRateController, gst_rc, + GST, RC, GObject); + +GstRateController* gst_rc_new (void); + +void gst_rc_record (GstRateController * self, + GstRcFrameType frame_type, + gsize coded_size, + GstClockTime duration); + +gint gst_rc_get_max_qp (GstRateController *self); +gint gst_rc_get_min_qp (GstRateController *self); +gint gst_rc_get_qp_step (GstRateController *self); +gint gst_rc_get_init_qp (GstRateController *self); +GstRateControlMode gst_rc_get_mode (GstRateController *self); +gint gst_rc_get_bitrate (GstRateController *self); + +void gst_rc_set_format (GstRateController *self, + const GstVideoInfo * vinfo); +void gst_rc_set_max_qp (GstRateController *self, gint max_qp); +void gst_rc_set_min_qp (GstRateController *self, gint min_qp); +void gst_rc_set_qp_step (GstRateController *self, gint qp_step); +void gst_rc_set_init_qp (GstRateController *self, gint init_qp); +void gst_rc_set_mode (GstRateController *self, GstRateControlMode mode); +void gst_rc_set_bitrate (GstRateController *self, gint bitrate); + +gint gst_rc_get_qp(GstRateController *self); + +G_END_DECLS diff --git a/gst-libs/gst/codecs/meson.build b/gst-libs/gst/codecs/meson.build index 3ea3bdc..c04600e 100644 --- a/gst-libs/gst/codecs/meson.build +++ b/gst-libs/gst/codecs/meson.build @@ -16,6 +16,7 @@ codecs_sources = files( 'gstvp8frame.c', 'gsth264encoder.c', 'gsth264frame.c', + 'gstratecontroller.c', ) codecs_headers = files( -- 2.25.1