From: Arun Iyengar via B4 Relay <devnull+aiyengar1990.gmail.com@kernel.org>
To: Mauro Carvalho Chehab <mchehab@kernel.org>,
Paul Kocialkowski <paulk@sys-base.io>
Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org,
Nicolas Dufresne <nicolas.dufresne@collabora.com>,
Hans Verkuil <hverkuil@kernel.org>,
Marco Felsch <m.felsch@pengutronix.de>,
Michael Tretter <m.tretter@pengutronix.de>,
Arun Iyengar <aiyengar1990@gmail.com>
Subject: [PATCH RFC 05/11] media: v4l2-core: Move the H.264 encode CBR rate control to a helper
Date: Tue, 10 Nov 2026 10:05:19 +0000 [thread overview]
Message-ID: <20261110-h264-encode-fixes-v1-5-7a64486df499@gmail.com> (raw)
In-Reply-To: <20261110-h264-encode-fixes-v1-0-7a64486df499@gmail.com>
From: Arun Iyengar <aiyengar1990@gmail.com>
The constant bitrate mode of the H.264 encode core does not depend on the
codec: it plans picture sizes over the GOP from the measured I/P size
ratio and IDR period, and moves one QP state by the QP error 6 *
log2(size / target), which holds for HEVC as well. Only the QP delta of
the I pictures differs between the codecs.
Move it to v4l2-enc-cbr, with the I picture QP delta as a parameter, so
that the HEVC encode core can use the same model. The H.264 core keeps
its mode callbacks and uses a QP delta of 2, as before. No functional
change.
Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
drivers/media/v4l2-core/Kconfig | 5 +
drivers/media/v4l2-core/Makefile | 1 +
drivers/media/v4l2-core/v4l2-enc-cbr.c | 300 +++++++++++++++++++++++++++++
drivers/media/v4l2-core/v4l2-h264-enc-rc.c | 246 ++---------------------
include/media/v4l2-enc-cbr.h | 64 ++++++
include/media/v4l2-h264-enc-rc.h | 35 +---
6 files changed, 387 insertions(+), 264 deletions(-)
diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index a88eaf8af48c..b8b849a44abd 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -44,9 +44,14 @@ config V4L2_JPEG_HELPER
config V4L2_H264
tristate
+# Used by the stateless encode cores that need v4l2-enc-cbr.ko
+config V4L2_ENC_CBR
+ tristate
+
# Used by drivers that need v4l2-h264-enc.ko
config V4L2_H264_ENC
tristate
+ select V4L2_ENC_CBR
# Used by drivers that need v4l2-vp9.ko
config V4L2_VP9
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 81c5582d6a34..3aa0855d7be7 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -26,6 +26,7 @@ videodev-$(CONFIG_VIDEO_V4L2_I2C) += v4l2-i2c.o
obj-$(CONFIG_V4L2_ASYNC) += v4l2-async.o
obj-$(CONFIG_V4L2_CCI) += v4l2-cci.o
+obj-$(CONFIG_V4L2_ENC_CBR) += v4l2-enc-cbr.o
obj-$(CONFIG_V4L2_FLASH_LED_CLASS) += v4l2-flash-led-class.o
obj-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o
obj-$(CONFIG_V4L2_H264) += v4l2-h264.o
diff --git a/drivers/media/v4l2-core/v4l2-enc-cbr.c b/drivers/media/v4l2-core/v4l2-enc-cbr.c
new file mode 100644
index 000000000000..5bd10d494054
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-enc-cbr.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * V4L2 stateless encoder constant bitrate rate control
+ *
+ * One QP state is kept for the stream: it is the QP of the P (and B)
+ * pictures, and I pictures are coded a fixed number of steps below it, so
+ * that the quality of the reference pictures stays even across GOPs. Each
+ * picture gets a size target from a GOP plan, in which an I picture is
+ * "ratio" times the size of a P picture, with the ratio and the IDR period
+ * measured on the stream. The difference between the coded size so far and
+ * the size the plan expects at this point of the GOP is spread over the next
+ * pictures, so that the bitrate converges to the target.
+ *
+ * After each picture the QP state moves by a fraction of the QP error
+ * 6 * log2(size / target) (a QP step of 6 halves the size): slowly for small
+ * errors, so that the QP does not follow the size noise from picture to
+ * picture, and quickly for errors of several QP steps (a scene change, a
+ * poor start).
+ *
+ * The QP to size relation (6 QP steps per halving) is the same for H.264
+ * and HEVC, so the model does not depend on the codec. Only the QP delta of
+ * the I pictures is a parameter.
+ */
+
+#include <linux/export.h>
+#include <linux/int_log.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/string.h>
+#include <media/v4l2-enc-cbr.h>
+
+/* The accumulated size error is spread over this many pictures. */
+#define CBR_WINDOW 8
+/* Limit of the accumulated size error, in seconds of the bitrate */
+#define CBR_BUFFER_SECONDS 3
+/* I/P size ratio, 1/16 units: initial value (10x) and limits (1.5x .. 30x) */
+#define CBR_RATIO_INIT 160
+#define CBR_RATIO_MIN 24
+#define CBR_RATIO_MAX 480
+/* P pictures after an I picture when the I/P size ratio is measured */
+#define CBR_RATIO_PICS 4
+/* QP errors up to this are corrected by 1/8 per picture, 1/256 units */
+#define CBR_ERR_SLOW (3 * 256)
+/*
+ * Largest QP increase and decrease per picture, 1/256 units. Decreases are
+ * slower, so that pictures that are much smaller than their target, such as
+ * repeated pictures, don't pull the QP down far.
+ */
+#define CBR_STEP_UP_MAX (4 * 256)
+#define CBR_STEP_DOWN_MAX (2 * 256)
+
+/* log2(x) in 8.8 fixed point */
+static int cbr_log2_q8(u64 x)
+{
+ unsigned int shift = 0;
+
+ if (!x)
+ return 0;
+
+ while (x > U32_MAX) {
+ x >>= 1;
+ shift++;
+ }
+
+ return (int)(intlog2(x) >> 16) + shift * 256;
+}
+
+static unsigned int cbr_fps(const struct v4l2_fract *timeperframe)
+{
+ if (!timeperframe->numerator || !timeperframe->denominator)
+ return 30;
+
+ return max(DIV_ROUND_CLOSEST(timeperframe->denominator,
+ timeperframe->numerator), 1U);
+}
+
+static u64 cbr_nominal(u32 bitrate, const struct v4l2_fract *timeperframe)
+{
+ if (!timeperframe->numerator || !timeperframe->denominator)
+ return max(bitrate / 30, 1U);
+
+ return max_t(u64, div_u64((u64)bitrate * timeperframe->numerator,
+ timeperframe->denominator), 1);
+}
+
+/* Planned size of an I or P picture of the current GOP */
+static u64 cbr_plan(const struct v4l2_enc_cbr *cbr, unsigned int fps,
+ bool intra)
+{
+ unsigned int gop = cbr->gop;
+ u64 p;
+
+ /* Until the second I picture, assume at least two seconds. */
+ if (!gop)
+ gop = max(cbr->since_i + 1, 2 * fps);
+ if (gop <= 1)
+ return cbr->nominal;
+
+ p = div_u64(cbr->nominal * gop * 16, cbr->ratio + 16 * (gop - 1));
+ if (intra)
+ p = div_u64(p * cbr->ratio, 16);
+
+ return max_t(u64, p, 1);
+}
+
+/*
+ * Start QP of an I picture from its size in bits per luma sample:
+ * 20.3 - 6 * log2(bits per sample), clipped to 15..45. This is the relation
+ * the Rockchip MPP library uses for its first picture at 1080p, for H.264
+ * and HEVC alike.
+ */
+static int cbr_qp_initial(u64 bits, unsigned int samples)
+{
+ int qp_q8 = 20 * 256 + 77 +
+ 6 * (cbr_log2_q8(samples) - cbr_log2_q8(max_t(u64, bits, 1)));
+
+ return clamp(DIV_ROUND_CLOSEST(qp_q8, 256), 15, 45);
+}
+
+/**
+ * v4l2_enc_cbr_reset() - reset the rate control state
+ * @cbr: the rate control state
+ * @qp_delta_intra: QP of the I pictures below the QP of the P pictures
+ *
+ * The next picture starts the rate control afresh.
+ */
+void v4l2_enc_cbr_reset(struct v4l2_enc_cbr *cbr, int qp_delta_intra)
+{
+ memset(cbr, 0, sizeof(*cbr));
+ cbr->qp_delta_intra = qp_delta_intra;
+}
+EXPORT_SYMBOL_GPL(v4l2_enc_cbr_reset);
+
+/**
+ * v4l2_enc_cbr_begin() - set up the size target of the next picture
+ * @cbr: the rate control state
+ * @bitrate: target bitrate in bits per second
+ * @timeperframe: frame interval
+ * @samples: number of luma samples of a picture
+ * @intra: the picture is an I picture
+ * @qp_min: lowest QP
+ * @qp_max: highest QP
+ *
+ * Sets @cbr->target_bits. The QP of the picture is then given by
+ * v4l2_enc_cbr_qp(). A change of the bitrate keeps the QP state and the
+ * measured statistics.
+ *
+ * Return: 0, or -EINVAL if @bitrate is 0.
+ */
+int v4l2_enc_cbr_begin(struct v4l2_enc_cbr *cbr, u32 bitrate,
+ const struct v4l2_fract *timeperframe,
+ unsigned int samples, bool intra, int qp_min,
+ int qp_max)
+{
+ unsigned int fps = cbr_fps(timeperframe);
+ u64 nominal = cbr_nominal(bitrate, timeperframe);
+ s64 target, expected = 0;
+ int qp;
+
+ if (!bitrate)
+ return -EINVAL;
+
+ if (!cbr->bitrate) {
+ cbr->ratio = CBR_RATIO_INIT;
+ cbr->qp_q8 = -1;
+ }
+
+ if (cbr->bitrate != bitrate || cbr->nominal != nominal) {
+ cbr->bitrate = bitrate;
+ cbr->nominal = nominal;
+ cbr->buffer_bits = 0;
+ }
+
+ if (intra) {
+ /* An I picture closes the GOP: its length is the IDR period. */
+ if (cbr->seen_i)
+ cbr->gop = cbr->since_i + 1;
+ cbr->since_i = 0;
+ cbr->seen_i = true;
+ }
+
+ cbr->plan_bits = cbr_plan(cbr, fps, intra);
+
+ if (cbr->qp_q8 < 0) {
+ /* First picture: start QP from the bits per sample. */
+ qp = cbr_qp_initial(cbr_plan(cbr, fps, true), samples) +
+ cbr->qp_delta_intra;
+ cbr->qp_q8 = clamp_t(int, qp, qp_min, qp_max) * 256;
+ }
+
+ /*
+ * The buffer holds the coded minus the nominal sizes. Within a GOP the
+ * plan expects it to go up by the excess of the I picture and back
+ * down to zero by the savings of the P pictures until the next I
+ * picture. Spread the difference to that over the next pictures.
+ */
+ if (!intra && cbr->seen_i)
+ expected = (s64)cbr_plan(cbr, fps, true) - (s64)nominal +
+ (s64)cbr->since_i *
+ ((s64)cbr->plan_bits - (s64)nominal);
+
+ target = (s64)cbr->plan_bits -
+ div_s64(cbr->buffer_bits - max_t(s64, expected, 0),
+ CBR_WINDOW);
+ cbr->target_bits = clamp_t(s64, target,
+ max_t(u64, cbr->plan_bits / 4, 8),
+ cbr->plan_bits * 2);
+ cbr->intra = intra;
+
+ pr_debug("+ v4l2-enc-cbr: begin");
+ pr_debug(" nominal: %llu bits, plan: %llu bits, target: %llu bits",
+ cbr->nominal, cbr->plan_bits, cbr->target_bits);
+ pr_debug(" buffer: %lld bits, expected: %lld bits, gop: %u, ratio: %u",
+ cbr->buffer_bits, expected, cbr->gop, cbr->ratio);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_enc_cbr_begin);
+
+/**
+ * v4l2_enc_cbr_qp() - QP of the picture set up by v4l2_enc_cbr_begin()
+ * @cbr: the rate control state
+ *
+ * Return: the QP, not limited to the QP range of the codec.
+ */
+int v4l2_enc_cbr_qp(const struct v4l2_enc_cbr *cbr)
+{
+ int qp = DIV_ROUND_CLOSEST(cbr->qp_q8, 256);
+
+ if (cbr->intra)
+ qp -= cbr->qp_delta_intra;
+
+ return qp;
+}
+EXPORT_SYMBOL_GPL(v4l2_enc_cbr_qp);
+
+/**
+ * v4l2_enc_cbr_end() - account for a coded picture
+ * @cbr: the rate control state
+ * @bits: size of the coded picture in bits
+ * @timeperframe: frame interval
+ * @qp_min: lowest QP
+ * @qp_max: highest QP
+ */
+void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
+ const struct v4l2_fract *timeperframe, int qp_min,
+ int qp_max)
+{
+ s64 limit = cbr->nominal * CBR_BUFFER_SECONDS * cbr_fps(timeperframe);
+ unsigned int ratio;
+ int err, step;
+
+ if (!cbr->bitrate || !cbr->target_bits)
+ return;
+
+ cbr->buffer_bits = clamp_t(s64, cbr->buffer_bits + (s64)bits -
+ (s64)cbr->nominal, -limit, limit);
+
+ /* QP error of the picture, 1/256 units */
+ err = 6 * (cbr_log2_q8(bits) - cbr_log2_q8(cbr->target_bits));
+
+ step = err / 8;
+ if (err > CBR_ERR_SLOW)
+ step += (err - CBR_ERR_SLOW) / 2;
+ else if (err < -CBR_ERR_SLOW)
+ step += (err + CBR_ERR_SLOW) / 2;
+ step = clamp(step, -CBR_STEP_DOWN_MAX, CBR_STEP_UP_MAX);
+
+ if (!cbr->intra) {
+ cbr->qp_q8 += step;
+ cbr->p_bits = cbr->p_bits ? (7 * cbr->p_bits + bits) / 8 : bits;
+ cbr->since_i++;
+
+ /* I/P size ratio, once a few P pictures were coded */
+ if (cbr->since_i == CBR_RATIO_PICS && cbr->i_bits) {
+ ratio = div64_u64(cbr->i_bits * 16, cbr->p_bits);
+ ratio = clamp(ratio, CBR_RATIO_MIN, CBR_RATIO_MAX);
+ cbr->ratio = (cbr->ratio + ratio + 1) / 2;
+ }
+ } else {
+ /*
+ * The size of an I picture mostly reflects the I/P ratio
+ * estimate: only all-intra streams steer the QP fully with it.
+ */
+ cbr->i_bits = bits;
+ cbr->qp_q8 += cbr->gop == 1 ? step : step / 4;
+ }
+
+ cbr->qp_q8 = clamp_t(int, cbr->qp_q8, qp_min * 256, qp_max * 256);
+
+ pr_debug("+ v4l2-enc-cbr: end");
+ pr_debug(" size: %llu bits, target: %llu bits, qp error: %d/256, qp: %d/256",
+ bits, cbr->target_bits, err, cbr->qp_q8);
+}
+EXPORT_SYMBOL_GPL(v4l2_enc_cbr_end);
+
+MODULE_DESCRIPTION("V4L2 stateless encoder constant bitrate rate control");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/v4l2-core/v4l2-h264-enc-rc.c b/drivers/media/v4l2-core/v4l2-h264-enc-rc.c
index bbfe03febac8..c4f1718ead0e 100644
--- a/drivers/media/v4l2-core/v4l2-h264-enc-rc.c
+++ b/drivers/media/v4l2-core/v4l2-h264-enc-rc.c
@@ -5,7 +5,6 @@
* Copyright (C) 2025-2026 Paul Kocialkowski <paulk@sys-base.io>
*/
-#include <linux/int_log.h>
#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/module.h>
@@ -162,116 +161,15 @@ static const struct v4l2_h264_enc_rc_mode cq_mode = {
};
/*
- * Constant bitrate mode
- *
- * One QP state is kept for the stream: it is the QP of the P (and B)
- * pictures, and I pictures are coded a fixed number of steps below it, so
- * that the quality of the reference pictures stays even across GOPs. Each
- * picture gets a size target from a GOP plan, in which an I picture is
- * "ratio" times the size of a P picture, with the ratio and the IDR period
- * measured on the stream. The difference between the coded size so far and
- * the size the plan expects at this point of the GOP is spread over the next
- * pictures, so that the bitrate converges to the target.
- *
- * After each picture the QP state moves by a fraction of the QP error
- * 6 * log2(size / target) (a QP step of 6 halves the size): slowly for small
- * errors, so that the QP does not follow the size noise from picture to
- * picture, and quickly for errors of several QP steps (a scene change, a
- * poor start).
+ * Constant bitrate mode, with the codec independent rate control of
+ * v4l2-enc-cbr: I pictures 2 QP below the P pictures (the default of the
+ * Rockchip MPP library for H.264).
*/
-
-/* The accumulated size error is spread over this many pictures. */
-#define CBR_WINDOW 8
-/* Limit of the accumulated size error, in seconds of the bitrate */
-#define CBR_BUFFER_SECONDS 3
-/* I/P size ratio, 1/16 units: initial value (10x) and limits (1.5x .. 30x) */
-#define CBR_RATIO_INIT 160
-#define CBR_RATIO_MIN 24
-#define CBR_RATIO_MAX 480
-/* P pictures after an I picture when the I/P size ratio is measured */
-#define CBR_RATIO_PICS 4
-/* QP of the I pictures below the QP of the P pictures */
#define CBR_QP_DELTA_INTRA 2
-/* QP errors up to this are corrected by 1/8 per picture, 1/256 units */
-#define CBR_ERR_SLOW (3 * 256)
-/*
- * Largest QP increase and decrease per picture, 1/256 units. Decreases are
- * slower, so that pictures that are much smaller than their target, such as
- * repeated pictures, don't pull the QP down far.
- */
-#define CBR_STEP_UP_MAX (4 * 256)
-#define CBR_STEP_DOWN_MAX (2 * 256)
-
-/* log2(x) in 8.8 fixed point */
-static int cbr_log2_q8(u64 x)
-{
- unsigned int shift = 0;
-
- if (!x)
- return 0;
-
- while (x > U32_MAX) {
- x >>= 1;
- shift++;
- }
-
- return (int)(intlog2(x) >> 16) + shift * 256;
-}
-
-static unsigned int cbr_fps(const struct v4l2_fract *timeperframe)
-{
- if (!timeperframe->numerator || !timeperframe->denominator)
- return 30;
-
- return max(DIV_ROUND_CLOSEST(timeperframe->denominator,
- timeperframe->numerator), 1U);
-}
-
-static u64 cbr_nominal(u32 bitrate, const struct v4l2_fract *timeperframe)
-{
- if (!timeperframe->numerator || !timeperframe->denominator)
- return max(bitrate / 30, 1U);
-
- return max_t(u64, div_u64((u64)bitrate * timeperframe->numerator,
- timeperframe->denominator), 1);
-}
-
-/* Planned size of an I or P picture of the current GOP */
-static u64 cbr_plan(const struct v4l2_h264_enc_rc_cbr *cbr, unsigned int fps,
- bool intra)
-{
- unsigned int gop = cbr->gop;
- u64 p;
-
- /* Until the second I picture, assume at least two seconds. */
- if (!gop)
- gop = max(cbr->since_i + 1, 2 * fps);
- if (gop <= 1)
- return cbr->nominal;
-
- p = div_u64(cbr->nominal * gop * 16, cbr->ratio + 16 * (gop - 1));
- if (intra)
- p = div_u64(p * cbr->ratio, 16);
-
- return max_t(u64, p, 1);
-}
-
-/*
- * Start QP of an I picture from its size in bits per luma sample:
- * 20.3 - 6 * log2(bits per sample), clipped to 15..45. This is the relation
- * the Rockchip MPP library uses for its first picture at 1080p.
- */
-static int cbr_qp_initial(u64 bits, unsigned int samples)
-{
- int qp_q8 = 20 * 256 + 77 +
- 6 * (cbr_log2_q8(samples) - cbr_log2_q8(max_t(u64, bits, 1)));
-
- return clamp(DIV_ROUND_CLOSEST(qp_q8, 256), 15, 45);
-}
static int cbr_init(struct v4l2_h264_enc_rc *rc)
{
- memset(&rc->cbr, 0, sizeof(rc->cbr));
+ v4l2_enc_cbr_reset(&rc->cbr, CBR_QP_DELTA_INTRA);
return 0;
}
@@ -279,148 +177,34 @@ static int cbr_init(struct v4l2_h264_enc_rc *rc)
static int cbr_measure(struct v4l2_h264_enc_rc *rc, unsigned long *size)
{
struct v4l2_h264_enc_state *state = rc->state;
- struct v4l2_h264_enc_rc_cbr *cbr = &rc->cbr;
- struct v4l2_fract *timeperframe = &state->timeperframe;
bool intra = state->encode.slice_type == V4L2_H264_SLICE_TYPE_I;
- unsigned int fps = cbr_fps(timeperframe);
- u64 nominal = cbr_nominal(state->bitrate, timeperframe);
unsigned int samples = state->width_mbs * state->height_mbs * 256;
- s64 target, expected = 0;
- int qp;
-
- if (!state->bitrate)
- return -EINVAL;
-
- if (!cbr->bitrate) {
- cbr->ratio = CBR_RATIO_INIT;
- cbr->qp_q8 = -1;
- }
-
- /* A new bitrate keeps the QP state and the measured statistics. */
- if (cbr->bitrate != state->bitrate || cbr->nominal != nominal) {
- cbr->bitrate = state->bitrate;
- cbr->nominal = nominal;
- cbr->buffer_bits = 0;
- }
-
- if (intra) {
- /* An I picture closes the GOP: its length is the IDR period. */
- if (cbr->seen_i)
- cbr->gop = cbr->since_i + 1;
- cbr->since_i = 0;
- cbr->seen_i = true;
- }
-
- cbr->plan_bits = cbr_plan(cbr, fps, intra);
+ int ret;
- if (cbr->qp_q8 < 0) {
- /* First picture: start QP from the bits per sample. */
- qp = cbr_qp_initial(cbr_plan(cbr, fps, true), samples) +
- CBR_QP_DELTA_INTRA;
- cbr->qp_q8 = clamp_t(int, qp, state->qp_min, state->qp_max) *
- 256;
- }
+ ret = v4l2_enc_cbr_begin(&rc->cbr, state->bitrate,
+ &state->timeperframe, samples, intra,
+ state->qp_min, state->qp_max);
+ if (ret)
+ return ret;
- /*
- * The buffer holds the coded minus the nominal sizes. Within a GOP the
- * plan expects it to go up by the excess of the I picture and back
- * down to zero by the savings of the P pictures until the next I
- * picture. Spread the difference to that over the next pictures.
- */
- if (!intra && cbr->seen_i)
- expected = (s64)cbr_plan(cbr, fps, true) - (s64)nominal +
- (s64)cbr->since_i *
- ((s64)cbr->plan_bits - (s64)nominal);
-
- target = (s64)cbr->plan_bits -
- div_s64(cbr->buffer_bits - max_t(s64, expected, 0),
- CBR_WINDOW);
- cbr->target_bits = clamp_t(s64, target,
- max_t(u64, cbr->plan_bits / 4, 8),
- cbr->plan_bits * 2);
- cbr->intra = intra;
-
- pr_debug("+ v4l2-h264-enc-rc: cbr measure");
- pr_debug(" nominal: %llu bits, plan: %llu bits, target: %llu bits",
- cbr->nominal, cbr->plan_bits, cbr->target_bits);
- pr_debug(" buffer: %lld bits, expected: %lld bits, gop: %u, ratio: %u",
- cbr->buffer_bits, expected, cbr->gop, cbr->ratio);
-
- *size = max_t(unsigned long, cbr->target_bits / 8, 1);
+ *size = max_t(unsigned long, rc->cbr.target_bits / 8, 1);
return 0;
}
static int cbr_estimate(struct v4l2_h264_enc_rc *rc, unsigned int *qp)
{
- struct v4l2_h264_enc_rc_cbr *cbr = &rc->cbr;
- int value;
-
- value = DIV_ROUND_CLOSEST(cbr->qp_q8, 256);
- if (cbr->intra)
- value -= CBR_QP_DELTA_INTRA;
-
- *qp = clamp(value, 0, 51);
+ *qp = clamp(v4l2_enc_cbr_qp(&rc->cbr), 0, 51);
return 0;
}
-static void cbr_update(struct v4l2_h264_enc_rc *rc, u64 bits)
+static int cbr_complete(struct v4l2_h264_enc_rc *rc, unsigned long bytesused)
{
struct v4l2_h264_enc_state *state = rc->state;
- struct v4l2_h264_enc_rc_cbr *cbr = &rc->cbr;
- s64 limit = cbr->nominal * CBR_BUFFER_SECONDS *
- cbr_fps(&state->timeperframe);
- unsigned int ratio;
- int err, step;
-
- if (!cbr->bitrate || !cbr->target_bits)
- return;
-
- cbr->buffer_bits = clamp_t(s64, cbr->buffer_bits + (s64)bits -
- (s64)cbr->nominal, -limit, limit);
-
- /* QP error of the picture, 1/256 units */
- err = 6 * (cbr_log2_q8(bits) - cbr_log2_q8(cbr->target_bits));
-
- step = err / 8;
- if (err > CBR_ERR_SLOW)
- step += (err - CBR_ERR_SLOW) / 2;
- else if (err < -CBR_ERR_SLOW)
- step += (err + CBR_ERR_SLOW) / 2;
- step = clamp(step, -CBR_STEP_DOWN_MAX, CBR_STEP_UP_MAX);
-
- if (!cbr->intra) {
- cbr->qp_q8 += step;
- cbr->p_bits = cbr->p_bits ? (7 * cbr->p_bits + bits) / 8 : bits;
- cbr->since_i++;
-
- /* I/P size ratio, once a few P pictures were coded */
- if (cbr->since_i == CBR_RATIO_PICS && cbr->i_bits) {
- ratio = div64_u64(cbr->i_bits * 16, cbr->p_bits);
- ratio = clamp(ratio, CBR_RATIO_MIN, CBR_RATIO_MAX);
- cbr->ratio = (cbr->ratio + ratio + 1) / 2;
- }
- } else {
- /*
- * The size of an I picture mostly reflects the I/P ratio
- * estimate: only all-intra streams steer the QP fully with it.
- */
- cbr->i_bits = bits;
- cbr->qp_q8 += cbr->gop == 1 ? step : step / 4;
- }
- cbr->qp_q8 = clamp_t(int, cbr->qp_q8, state->qp_min * 256,
- state->qp_max * 256);
-
- pr_debug("+ v4l2-h264-enc-rc: cbr update");
- pr_debug(" size: %llu bits, target: %llu bits, qp error: %d/256, qp: %d/256",
- bits, cbr->target_bits, err, cbr->qp_q8);
-}
-
-static int cbr_complete(struct v4l2_h264_enc_rc *rc, unsigned long bytesused)
-{
- cbr_update(rc, (u64)bytesused * 8);
+ v4l2_enc_cbr_end(&rc->cbr, (u64)bytesused * 8, &state->timeperframe,
+ state->qp_min, state->qp_max);
return v4l2_h264_enc_rc_stats_collect(rc, bytesused);
}
diff --git a/include/media/v4l2-enc-cbr.h b/include/media/v4l2-enc-cbr.h
new file mode 100644
index 000000000000..6b66d121b0cc
--- /dev/null
+++ b/include/media/v4l2-enc-cbr.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * V4L2 stateless encoder constant bitrate rate control
+ *
+ * A codec independent frame-level rate control for the constant bitrate
+ * mode of the stateless encode cores.
+ */
+
+#ifndef _MEDIA_V4L2_ENC_CBR_H
+#define _MEDIA_V4L2_ENC_CBR_H
+
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+/**
+ * struct v4l2_enc_cbr - constant bitrate rate control state
+ * @qp_delta_intra: QP of the I pictures below the QP of the P pictures
+ * @bitrate: bitrate the state was set up for, 0 before the first picture
+ * @nominal: nominal picture size in bits (bitrate times frame interval)
+ * @qp_q8: QP of the P pictures in 1/256 units, -1 before the first picture
+ * @ratio: I/P picture size ratio in 1/16 units
+ * @gop: measured IDR period in pictures, 0 until two I pictures were seen
+ * @since_i: pictures coded since the last I picture
+ * @seen_i: an I picture was coded
+ * @intra: the current picture is an I picture
+ * @p_bits: running mean of the P picture sizes
+ * @i_bits: size of the last I picture
+ * @buffer_bits: accumulated difference between the coded and nominal sizes
+ * @plan_bits: planned size of the current picture
+ * @target_bits: size target of the current picture
+ *
+ * Only @qp_delta_intra is set by the user, after v4l2_enc_cbr_reset().
+ * @target_bits is valid from v4l2_enc_cbr_begin() of a picture until its
+ * v4l2_enc_cbr_end().
+ */
+struct v4l2_enc_cbr {
+ int qp_delta_intra;
+
+ u32 bitrate;
+ u64 nominal;
+ int qp_q8;
+ unsigned int ratio;
+ unsigned int gop;
+ unsigned int since_i;
+ bool seen_i;
+ bool intra;
+ u64 p_bits;
+ u64 i_bits;
+ s64 buffer_bits;
+ u64 plan_bits;
+ u64 target_bits;
+};
+
+void v4l2_enc_cbr_reset(struct v4l2_enc_cbr *cbr, int qp_delta_intra);
+int v4l2_enc_cbr_begin(struct v4l2_enc_cbr *cbr, u32 bitrate,
+ const struct v4l2_fract *timeperframe,
+ unsigned int samples, bool intra, int qp_min,
+ int qp_max);
+int v4l2_enc_cbr_qp(const struct v4l2_enc_cbr *cbr);
+void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
+ const struct v4l2_fract *timeperframe, int qp_min,
+ int qp_max);
+
+#endif
diff --git a/include/media/v4l2-h264-enc-rc.h b/include/media/v4l2-h264-enc-rc.h
index 72d78a594e40..76781fe2d844 100644
--- a/include/media/v4l2-h264-enc-rc.h
+++ b/include/media/v4l2-h264-enc-rc.h
@@ -10,6 +10,7 @@
#include <linux/types.h>
#include <linux/v4l2-controls.h>
+#include <media/v4l2-enc-cbr.h>
#include <media/v4l2-h264.h>
#define V4L2_H264_ENC_RC_STATS_TYPE_COUNT 10
@@ -83,38 +84,6 @@ struct v4l2_h264_enc_rc_stats {
unsigned int index;
};
-/**
- * struct v4l2_h264_enc_rc_cbr - constant bitrate mode state
- * @bitrate: bitrate the state was set up for, 0 before the first picture
- * @nominal: nominal picture size in bits (bitrate times frame interval)
- * @qp_q8: QP of the P pictures in 1/256 units, -1 before the first picture
- * @ratio: I/P picture size ratio in 1/16 units
- * @gop: measured IDR period in pictures, 0 until two I pictures were seen
- * @since_i: pictures coded since the last I picture
- * @seen_i: an I picture was coded
- * @intra: the current picture is an I picture
- * @p_bits: running mean of the P picture sizes
- * @i_bits: size of the last I picture
- * @buffer_bits: accumulated difference between the coded and nominal sizes
- * @plan_bits: planned size of the current picture
- * @target_bits: size target of the current picture
- */
-struct v4l2_h264_enc_rc_cbr {
- u32 bitrate;
- u64 nominal;
- int qp_q8;
- unsigned int ratio;
- unsigned int gop;
- unsigned int since_i;
- bool seen_i;
- bool intra;
- u64 p_bits;
- u64 i_bits;
- s64 buffer_bits;
- u64 plan_bits;
- u64 target_bits;
-};
-
struct v4l2_h264_enc_rc {
const struct v4l2_h264_enc_rc_ops *ops;
void *private_data;
@@ -124,7 +93,7 @@ struct v4l2_h264_enc_rc {
void *mode_data;
struct v4l2_h264_enc_rc_stats stats;
- struct v4l2_h264_enc_rc_cbr cbr;
+ struct v4l2_enc_cbr cbr;
unsigned long size;
unsigned int qp;
};
--
2.55.0
next prev parent reply other threads:[~2026-11-10 10:05 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 10:05 [PATCH RFC 00/11] media: h264: Stateless encode core follow-ups: parameter sets, POC type 2, CBR and VBR Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 01/11] media: h264: Only write the SPS and PPS of a stateless encode when they change Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 02/11] media: h264: Support picture order count type 2 in stateless encode Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 03/11] media: h264: Write the stateless encode SPS and PPS with nal_ref_idc 3 Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 04/11] media: v4l2-h264-enc-rc: Rework the constant bitrate mode Arun Iyengar via B4 Relay
2026-11-10 10:05 ` Arun Iyengar via B4 Relay [this message]
2026-11-10 10:05 ` [PATCH RFC 06/11] media: v4l2-core: Bound the I picture size in the encode CBR rate control Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 07/11] media: v4l2-core: Halve the I picture QP increase of the encode CBR Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 08/11] media: v4l2-core: Re-encode oversized pictures in " Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 09/11] media: v4l2-core: Fix all-intra streams in the encode CBR rate control Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 10/11] media: v4l2-core: Add a peak bitrate to the encode " Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 11/11] media: h264: Add a VBR mode to the stateless " Arun Iyengar via B4 Relay
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110-h264-encode-fixes-v1-5-7a64486df499@gmail.com \
--to=devnull+aiyengar1990.gmail.com@kernel.org \
--cc=aiyengar1990@gmail.com \
--cc=hverkuil@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=m.felsch@pengutronix.de \
--cc=m.tretter@pengutronix.de \
--cc=mchehab@kernel.org \
--cc=nicolas.dufresne@collabora.com \
--cc=paulk@sys-base.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®