mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Arun Iyengar via B4 Relay <devnull+aiyengar1990.gmail.com@kernel.org>
To: Mauro Carvalho Chehab <mchehab@kernel.org>
Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Paul Kocialkowski <paulk@sys-base.io>,
	 Nicolas Dufresne <nicolas.dufresne@collabora.com>,
	 Hans Verkuil <hverkuil@kernel.org>,
	Arun Iyengar <aiyengar1990@gmail.com>
Subject: [PATCH RFC 06/10] media: hevc: Add KUnit tests for the stateless encode core
Date: Tue, 10 Nov 2026 10:07:36 +0000	[thread overview]
Message-ID: <20261110-hevc-encode-uapi-v1-6-38ae9fcfedd4@gmail.com> (raw)
In-Reply-To: <20261110-hevc-encode-uapi-v1-0-38ae9fcfedd4@gmail.com>

From: Arun Iyengar <aiyengar1990@gmail.com>

Run short sequences of pictures through the HEVC stateless encode core,
with a real control handler and a fake CAPTURE buffer, and check:

- an IDR picture followed by P pictures that use, keep without using,
  drop and turn long-term their references: the coded RPS, reference
  picture list 0, collocated picture, DPB contents, QP, buffer flags and
  feedback;
- that invalid references (dropped pictures, long-term pictures used as
  short-term, POC values a decoder could not derive, B slices on P-only
  hardware) fail without changing the DPB;
- CRA and IDR pictures keeping and flushing references;
- hardware that writes its own slice headers without TemporalId: nothing
  is written for pictures without parameter sets, and
  nuh_temporal_id_plus1 is patched after the encode run;
- intra-only hardware, for which the SPS is accommodated and no
  reference slots are allocated;
- the control validation of the encode parameters, including its default
  value.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/.kunitconfig          |   1 +
 drivers/media/v4l2-core/Kconfig               |  14 +
 drivers/media/v4l2-core/Makefile              |   1 +
 drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c | 554 ++++++++++++++++++++++++++
 4 files changed, 570 insertions(+)

diff --git a/drivers/media/v4l2-core/.kunitconfig b/drivers/media/v4l2-core/.kunitconfig
index 4ef422b5e0a8..65aa43070053 100644
--- a/drivers/media/v4l2-core/.kunitconfig
+++ b/drivers/media/v4l2-core/.kunitconfig
@@ -3,3 +3,4 @@ CONFIG_MEDIA_SUPPORT=y
 CONFIG_MEDIA_SUPPORT_FILTER=n
 CONFIG_VIDEO_DEV=y
 CONFIG_V4L2_HEVC_ENC_RBSP_KUNIT_TEST=y
+CONFIG_V4L2_HEVC_ENC_KUNIT_TEST=y
diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index 4c39bd943e81..eb245deb4622 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -75,6 +75,20 @@ config V4L2_HEVC_ENC_RBSP_KUNIT_TEST
 
 	  If unsure, say N.
 
+config V4L2_HEVC_ENC_KUNIT_TEST
+	tristate "KUnit tests for the V4L2 HEVC encode core" if !KUNIT_ALL_TESTS
+	depends on KUNIT && VIDEO_DEV
+	select V4L2_HEVC_ENC
+	select VIDEOBUF2_CORE
+	default KUNIT_ALL_TESTS
+	help
+	  Build the KUnit tests of the stateless HEVC encode core. They run
+	  short sequences of pictures through it and check the reference
+	  picture sets, reference picture lists, decoded picture buffer and
+	  feedback that result, and the validation of the encode parameters.
+
+	  If unsure, say N.
+
 # Used by drivers that need v4l2-vp9.ko
 config V4L2_VP9
 	tristate
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 153b8a6e74b8..d57740d71fe1 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_V4L2_H264_ENC) += v4l2-h264-enc.o v4l2-h264-enc-rbsp.o \
 obj-$(CONFIG_V4L2_HEVC_ENC) += v4l2-hevc-enc.o
 obj-$(CONFIG_V4L2_HEVC_ENC_RBSP) += v4l2-hevc-enc-rbsp.o
 obj-$(CONFIG_V4L2_HEVC_ENC_RBSP_KUNIT_TEST) += v4l2-hevc-enc-rbsp-kunit.o
+obj-$(CONFIG_V4L2_HEVC_ENC_KUNIT_TEST) += v4l2-hevc-enc-kunit.o
 obj-$(CONFIG_V4L2_ISP) += v4l2-isp.o
 obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o
 obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c b/drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c
new file mode 100644
index 000000000000..15c65663d46c
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit tests for the V4L2 HEVC encode core
+ *
+ * Runs short sequences of pictures through v4l2_hevc_enc_step() and
+ * v4l2_hevc_enc_complete() with a real control handler and a fake CAPTURE
+ * buffer, and checks the reference picture set, reference lists, decoded
+ * picture buffer, headers and feedback that result.
+ */
+
+#include <kunit/test.h>
+#include <linux/module.h>
+#include <linux/v4l2-controls.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-hevc.h>
+#include <media/v4l2-hevc-enc.h>
+#include <media/videobuf2-v4l2.h>
+
+#define BUF_SIZE	4096
+
+struct hevc_enc_test {
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_hevc_enc enc;
+	struct v4l2_pix_format fmt;
+	struct v4l2_fract tpf;
+	struct vb2_queue queue;
+	struct vb2_v4l2_buffer buf;
+	u8 *data;
+	unsigned int rec_allocated;
+};
+
+static void *fake_vaddr(struct vb2_buffer *vb, void *buf_priv)
+{
+	return buf_priv;
+}
+
+static const struct vb2_mem_ops fake_mem_ops = {
+	.vaddr = fake_vaddr,
+};
+
+static int fake_rec_alloc(struct v4l2_hevc_enc *enc,
+			  struct v4l2_hevc_enc_rec_buffer *buffer)
+{
+	struct hevc_enc_test *t = enc->private_data;
+
+	t->rec_allocated++;
+	buffer->private_data = (void *)(uintptr_t)t->rec_allocated;
+
+	return 0;
+}
+
+static void fake_rec_free(struct v4l2_hevc_enc *enc,
+			  struct v4l2_hevc_enc_rec_buffer *buffer)
+{
+	struct hevc_enc_test *t = enc->private_data;
+
+	t->rec_allocated--;
+}
+
+static const struct v4l2_hevc_enc_ops fake_ops = {
+	.rec_buffer_alloc = fake_rec_alloc,
+	.rec_buffer_free = fake_rec_free,
+};
+
+static const u32 compound_ctrls[] = {
+	V4L2_CID_STATELESS_HEVC_SPS,
+	V4L2_CID_STATELESS_HEVC_PPS,
+	V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS,
+	V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK,
+};
+
+static const struct v4l2_ctrl_hevc_sps test_sps = {
+	.pic_width_in_luma_samples = 1280,
+	.pic_height_in_luma_samples = 720,
+	.log2_max_pic_order_cnt_lsb_minus4 = 4,
+	.sps_max_dec_pic_buffering_minus1 = 2,
+	.log2_diff_max_min_luma_coding_block_size = 3,
+	.log2_diff_max_min_luma_transform_block_size = 3,
+	.chroma_format_idc = 1,
+	.sps_max_sub_layers_minus1 = 1,
+	.flags = V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT,
+};
+
+static struct hevc_enc_test *hevc_enc_test_init(struct kunit *test,
+						unsigned int flags)
+{
+	struct hevc_enc_test *t;
+	unsigned int i;
+	int ret;
+
+	t = kunit_kzalloc(test, sizeof(*t), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t);
+	t->data = kunit_kzalloc(test, BUF_SIZE, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t->data);
+
+	v4l2_ctrl_handler_init(&t->hdl, 8);
+	for (i = 0; i < ARRAY_SIZE(compound_ctrls); i++) {
+		struct v4l2_ctrl_config cfg = { .id = compound_ctrls[i] };
+
+		v4l2_ctrl_new_custom(&t->hdl, &cfg, NULL);
+	}
+	v4l2_ctrl_new_std(&t->hdl, NULL, V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std(&t->hdl, NULL, V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP,
+			  0, 51, 1, 33);
+	KUNIT_ASSERT_EQ(test, t->hdl.error, 0);
+
+	ret = v4l2_ctrl_s_ctrl_compound(v4l2_ctrl_find(&t->hdl,
+						       V4L2_CID_STATELESS_HEVC_SPS),
+					V4L2_CTRL_TYPE_HEVC_SPS, &test_sps);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	t->fmt.width = 1280;
+	t->fmt.height = 720;
+	t->tpf = (struct v4l2_fract){ 1, 30 };
+
+	t->queue.mem_ops = &fake_mem_ops;
+	t->buf.vb2_buf.vb2_queue = &t->queue;
+	t->buf.vb2_buf.num_planes = 1;
+	t->buf.vb2_buf.planes[0].mem_priv = t->data;
+	t->buf.vb2_buf.planes[0].length = BUF_SIZE;
+
+	t->enc.ops = &fake_ops;
+	t->enc.private_data = t;
+	t->enc.format = &t->fmt;
+	t->enc.timeperframe = &t->tpf;
+	t->enc.ctrl_handler = &t->hdl;
+	t->enc.flags = flags;
+
+	ret = v4l2_hevc_enc_init(&t->enc);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	return t;
+}
+
+static void hevc_enc_test_exit(struct hevc_enc_test *t)
+{
+	v4l2_hevc_enc_exit(&t->enc);
+	v4l2_ctrl_handler_free(&t->hdl);
+}
+
+static int hevc_enc_test_set(struct hevc_enc_test *t,
+			     const struct v4l2_ctrl_hevc_encode_params *p)
+{
+	return v4l2_ctrl_s_ctrl_compound(v4l2_ctrl_find(&t->hdl,
+							V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS),
+					 V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS, p);
+}
+
+/* Encode one picture, with the hardware "writing" @hw_size bytes. */
+static int hevc_enc_test_frame(struct kunit *test, struct hevc_enc_test *t,
+			       const struct v4l2_ctrl_hevc_encode_params *p,
+			       u64 timestamp, unsigned int hw_size)
+{
+	int ret;
+
+	ret = hevc_enc_test_set(t, p);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	memset(t->data, 0, BUF_SIZE);
+	t->buf.vb2_buf.timestamp = timestamp;
+
+	ret = v4l2_hevc_enc_step(&t->enc, &t->buf);
+	if (ret)
+		return ret;
+
+	vb2_set_plane_payload(&t->buf.vb2_buf, 0,
+			      t->enc.header_size + hw_size);
+
+	return v4l2_hevc_enc_complete(&t->enc, &t->buf, NULL);
+}
+
+static const struct v4l2_ctrl_hevc_encode_feedback *
+hevc_enc_test_feedback(struct hevc_enc_test *t)
+{
+	return v4l2_ctrl_find(&t->hdl, V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK)
+		->p_cur.p_hevc_encode_feedback;
+}
+
+static unsigned int hevc_enc_test_active(struct hevc_enc_test *t)
+{
+	unsigned int i, count = 0;
+
+	for (i = 0; i < t->enc.ref.slots_count; i++)
+		count += t->enc.ref.dpb[i].active;
+
+	return count;
+}
+
+static int hevc_enc_test_slot(struct hevc_enc_test *t, u64 timestamp)
+{
+	unsigned int i;
+
+	for (i = 0; i < t->enc.ref.slots_count; i++)
+		if (t->enc.ref.dpb[i].active &&
+		    t->enc.ref.dpb[i].timestamp == timestamp)
+			return i;
+
+	return -1;
+}
+
+/* Like VEPU580 slice header registers, without long-term pictures. */
+static const struct v4l2_hevc_enc_rps_limits no_lt_limits = {
+	.num_negative_pics = 4,
+};
+
+#define REF(ts, f)	{ .timestamp = (ts), .flags = (f) }
+#define USED		V4L2_HEVC_ENCODE_REF_FLAG_USED_BY_CURR_PIC
+#define LT		V4L2_HEVC_ENCODE_REF_FLAG_LONG_TERM
+
+/* IDR, then P pictures that keep, drop and turn references long-term. */
+static void hevc_enc_gop(struct kunit *test)
+{
+	struct hevc_enc_test *t =
+		hevc_enc_test_init(test, V4L2_HEVC_ENC_FLAG_INTER_PRED);
+	const struct v4l2_ctrl_hevc_encode_feedback *fb;
+	struct v4l2_hevc_slice_header *sh = &t->enc.slice;
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		.flags = V4L2_HEVC_ENCODE_FLAG_REFERENCE,
+	};
+	static const u8 vps_start[] = { 0, 0, 0, 1, 0x40, 0x01 };
+	int ret;
+
+	/* POC 0: IDR, with the parameter sets. */
+	ret = hevc_enc_test_frame(test, t, &p, 1000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_GT(test, t->enc.header_size, 40);
+	KUNIT_EXPECT_MEMEQ(test, t->data, vps_start, sizeof(vps_start));
+	KUNIT_EXPECT_EQ(test, t->enc.qp, 30);
+	KUNIT_EXPECT_TRUE(test, t->buf.flags & V4L2_BUF_FLAG_KEYFRAME);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 1);
+	fb = hevc_enc_test_feedback(t);
+	KUNIT_EXPECT_EQ(test, fb->timestamp, 1000);
+	KUNIT_EXPECT_EQ(test, fb->slice_qp, 30);
+	KUNIT_EXPECT_EQ(test, fb->stats.header_size, t->enc.header_size);
+	KUNIT_EXPECT_TRUE(test,
+			  fb->flags & V4L2_HEVC_ENCODE_FEEDBACK_FLAG_REFERENCE);
+
+	/* The current picture and the 3 DPB slots of the SPS. */
+	KUNIT_EXPECT_EQ(test, t->rec_allocated, 4);
+
+	/* POC 1: P referencing POC 0. Only a slice header is written. */
+	p.nal_unit_type = V4L2_HEVC_NAL_TRAIL_R;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_P;
+	p.pic_order_cnt_val = 1;
+	p.num_refs = 1;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(1000, USED);
+	p.flags |= V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED;
+	ret = hevc_enc_test_frame(test, t, &p, 2000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->enc.qp, 33);
+	KUNIT_EXPECT_EQ(test, t->data[4] >> 1, V4L2_HEVC_NAL_TRAIL_R);
+	KUNIT_EXPECT_LT(test, t->enc.header_size, 16);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[0], 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.used_by_curr_pic_s0, 0x1);
+	KUNIT_EXPECT_EQ(test, sh->slice_pic_order_cnt_lsb, 1);
+	KUNIT_EXPECT_EQ(test, sh->slice_qp_delta, 33 - 26);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.l0_count, 1);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.collocated, t->enc.ref.l0[0]);
+	KUNIT_EXPECT_TRUE(test, t->buf.flags & V4L2_BUF_FLAG_PFRAME);
+	fb = hevc_enc_test_feedback(t);
+	KUNIT_EXPECT_EQ(test, fb->num_ref_idx_l0_active, 1);
+	KUNIT_EXPECT_EQ(test, fb->ref_idx_l0[0], 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+
+	/*
+	 * POC 2: keep POC 0 without using it, use POC 1. The closest
+	 * picture comes first in the RPS whatever the order of refs[].
+	 */
+	p.pic_order_cnt_val = 2;
+	p.num_refs = 2;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(1000, 0);
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(2000, USED);
+	ret = hevc_enc_test_frame(test, t, &p, 3000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 2);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[0], 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[1], 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.used_by_curr_pic_s0, 0x1);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.l0[0], hevc_enc_test_slot(t, 2000));
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_feedback(t)->ref_idx_l0[0], 1);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 3);
+
+	/* POC 3: non-reference, uses POC 2, drops POC 1, POC 0 long-term. */
+	p.nal_unit_type = V4L2_HEVC_NAL_TRAIL_N;
+	p.pic_order_cnt_val = 3;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(3000, USED);
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(1000, LT);
+	p.flags &= ~V4L2_HEVC_ENCODE_FLAG_REFERENCE;
+	ret = hevc_enc_test_frame(test, t, &p, 4000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->num_long_term_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->lt[0].poc_lsb_lt, 0);
+	KUNIT_EXPECT_FALSE(test, sh->lt[0].used_by_curr_pic_lt);
+	KUNIT_EXPECT_EQ(test, sh->lt[0].delta_poc_msb_cycle_lt, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_slot(t, 2000), -1);
+	KUNIT_EXPECT_TRUE(test,
+			  t->enc.ref.dpb[hevc_enc_test_slot(t, 1000)].long_term);
+	KUNIT_EXPECT_FALSE(test, hevc_enc_test_feedback(t)->flags &
+			   V4L2_HEVC_ENCODE_FEEDBACK_FLAG_REFERENCE);
+
+	/* A long-term picture cannot be used as short-term again. */
+	p.pic_order_cnt_val = 4;
+	p.num_refs = 2;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(3000, USED);
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(1000, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* A picture that is not in the DPB (dropped, or never encoded). */
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(2000, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* A POC too far from the previous TemporalId 0 picture. */
+	p.num_refs = 1;
+	p.pic_order_cnt_val = 2 + 128;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* More references than the hardware can signal. */
+	p.pic_order_cnt_val = 4;
+	p.num_refs = 2;
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(1000, LT);
+	t->enc.rps_limits = &no_lt_limits;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+	t->enc.rps_limits = NULL;
+	p.num_refs = 1;
+
+	/* Failed runs left the DPB alone. */
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+
+	/* No B slices without INTER_BIPRED. */
+	p.pic_order_cnt_val = 4;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_B;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* A CRA keeps references for leading pictures but uses none. */
+	p.nal_unit_type = V4L2_HEVC_NAL_CRA_NUT;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_I;
+	p.pic_order_cnt_val = 8;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(3000, 0);
+	p.flags |= V4L2_HEVC_ENCODE_FLAG_REFERENCE;
+	ret = hevc_enc_test_frame(test, t, &p, 9000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[0], 5);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.used_by_curr_pic_s0, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+
+	/* An IDR empties the DPB. */
+	p.nal_unit_type = V4L2_HEVC_NAL_IDR_N_LP;
+	ret = hevc_enc_test_frame(test, t, &p, 10000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 1);
+	KUNIT_EXPECT_GE(test, hevc_enc_test_slot(t, 10000), 0);
+
+	hevc_enc_test_exit(t);
+	KUNIT_EXPECT_EQ(test, t->rec_allocated, 0);
+}
+
+/*
+ * Hardware that writes its own slice headers without TemporalId: the core
+ * patches nuh_temporal_id_plus1 and writes nothing for pictures without
+ * parameter sets.
+ */
+static void hevc_enc_hw_slice_header(struct kunit *test)
+{
+	struct hevc_enc_test *t =
+		hevc_enc_test_init(test, V4L2_HEVC_ENC_FLAG_INTER_PRED |
+				   V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER);
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		.flags = V4L2_HEVC_ENCODE_FLAG_REFERENCE,
+	};
+	static const u8 hw_slice[] = { 0, 0, 0, 1, 0x04, 0x01, 0xd0, 0x00,
+				       0x00, 0x01, 0x04, 0x01, 0x80 };
+	static const u8 patched[] = { 0, 0, 0, 1, 0x04, 0x02, 0xd0, 0x00,
+				      0x00, 0x01, 0x04, 0x02, 0x80 };
+	unsigned int header_size;
+	int ret;
+
+	ret = hevc_enc_test_frame(test, t, &p, 1000, 0);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	header_size = t->enc.header_size;
+	KUNIT_EXPECT_GT(test, header_size, 0);
+
+	/* Parameter sets only: the buffer ends with the PPS trailing bits. */
+	KUNIT_EXPECT_NE(test, t->data[header_size - 1], 0);
+
+	p.nal_unit_type = V4L2_HEVC_NAL_TSA_N;
+	p.nuh_temporal_id_plus1 = 2;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_P;
+	p.pic_order_cnt_val = 1;
+	p.num_refs = 1;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(1000, USED);
+	p.flags = 0;
+	ret = hevc_enc_test_set(t, &p);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	t->buf.vb2_buf.timestamp = 2000;
+	ret = v4l2_hevc_enc_step(&t->enc, &t->buf);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->enc.header_size, 0);
+
+	memcpy(t->data, hw_slice, sizeof(hw_slice));
+	vb2_set_plane_payload(&t->buf.vb2_buf, 0, sizeof(hw_slice));
+	ret = v4l2_hevc_enc_complete(&t->enc, &t->buf, NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_MEMEQ(test, t->data, patched, sizeof(patched));
+
+	hevc_enc_test_exit(t);
+}
+
+/* Intra-only hardware turns P pictures into I pictures and keeps no DPB. */
+static void hevc_enc_intra_only(struct kunit *test)
+{
+	struct hevc_enc_test *t = hevc_enc_test_init(test, 0);
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		.flags = V4L2_HEVC_ENCODE_FLAG_REFERENCE,
+	};
+	int ret;
+
+	ret = hevc_enc_test_frame(test, t, &p, 1000, 10);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->rec_allocated, 1);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.slots_count, 0);
+
+	/* The SPS was accommodated: no DPB, no TMVP. */
+	KUNIT_EXPECT_EQ(test,
+			v4l2_ctrl_find(&t->hdl, V4L2_CID_STATELESS_HEVC_SPS)
+			->p_cur.p_hevc_sps->sps_max_dec_pic_buffering_minus1,
+			0);
+
+	p.nal_unit_type = V4L2_HEVC_NAL_TRAIL_R;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_P;
+	p.pic_order_cnt_val = 1;
+	ret = hevc_enc_test_set(t, &p);
+	/* A P picture needs a used reference... */
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_I;
+	ret = hevc_enc_test_frame(test, t, &p, 2000, 10);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->enc.slice.slice_type, V4L2_HEVC_SLICE_TYPE_I);
+	KUNIT_EXPECT_EQ(test, t->enc.slice.st_rps.num_negative_pics, 0);
+
+	hevc_enc_test_exit(t);
+}
+
+/* Control validation of the encode parameters. */
+static void hevc_enc_validate(struct kunit *test)
+{
+	struct hevc_enc_test *t =
+		hevc_enc_test_init(test, V4L2_HEVC_ENC_FLAG_INTER_PRED);
+	const struct v4l2_ctrl_hevc_encode_params *cur =
+		v4l2_ctrl_find(&t->hdl, V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS)
+		->p_cur.p_hevc_encode_params;
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_TRAIL_R,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_P,
+		.num_refs = 1,
+		.refs = { REF(1, USED) },
+	};
+	struct v4l2_ctrl_hevc_encode_params bad;
+
+	/* The default value is a valid IDR picture. */
+	KUNIT_EXPECT_EQ(test, cur->nal_unit_type, V4L2_HEVC_NAL_IDR_W_RADL);
+	KUNIT_EXPECT_EQ(test, cur->nuh_temporal_id_plus1, 1);
+
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &p), 0);
+	/* collocated_from_l0_flag is inferred for P slices. */
+	KUNIT_EXPECT_TRUE(test, cur->flags &
+			  V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0);
+
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_VPS;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_CRA_NUT;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_TSA_R;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.nuh_temporal_id_plus1 = 0;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.refs[0].flags = 0;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.num_refs = 2;
+	bad.refs[1] = bad.refs[0];
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.flags = BIT_ULL(40);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	/* An IDR has no RPS: the references and POC are dropped. */
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_IDR_N_LP;
+	bad.slice_type = V4L2_HEVC_SLICE_TYPE_I;
+	bad.refs[0].flags = 0;
+	bad.pic_order_cnt_val = 5;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), 0);
+	KUNIT_EXPECT_EQ(test, cur->num_refs, 0);
+	KUNIT_EXPECT_EQ(test, cur->pic_order_cnt_val, 0);
+	KUNIT_EXPECT_EQ(test, cur->refs[0].timestamp, 0);
+
+	/* The first picture must be an IRAP picture. */
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 1, 10),
+			-EINVAL);
+
+	hevc_enc_test_exit(t);
+}
+
+static struct kunit_case hevc_enc_test_cases[] = {
+	KUNIT_CASE(hevc_enc_gop),
+	KUNIT_CASE(hevc_enc_hw_slice_header),
+	KUNIT_CASE(hevc_enc_intra_only),
+	KUNIT_CASE(hevc_enc_validate),
+	{}
+};
+
+static struct kunit_suite hevc_enc_test_suite = {
+	.name = "v4l2-hevc-enc",
+	.test_cases = hevc_enc_test_cases,
+};
+
+kunit_test_suite(hevc_enc_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the V4L2 HEVC encode core");
+MODULE_LICENSE("GPL");

-- 
2.55.0



  parent reply	other threads:[~2026-11-10 10:07 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 01/10] media: hevc: Add H.265 syntax definitions Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 02/10] media: uapi: Add HEVC stateless encode support Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 03/10] media: hevc: Add stateless encode RBSP writer Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 04/10] media: hevc: Add KUnit tests for the " Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 05/10] media: hevc: Add stateless encode core Arun Iyengar via B4 Relay
2026-11-10 10:07 ` Arun Iyengar via B4 Relay [this message]
2026-11-10 10:07 ` [PATCH RFC 07/10] media: v4l2-hevc-enc: Add a constant bitrate mode Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 08/10] media: v4l2-hevc-enc: Add QP limits for the I pictures Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 09/10] media: v4l2-hevc-enc: Re-encode oversized pictures Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 10/10] media: docs: Document the HEVC stateless encode uAPI 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-hevc-encode-uapi-v1-6-38ae9fcfedd4@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=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®