diff mbox series

[meta-arago,master] gstreamer1.0-plugins-good: Add Patch to Support Cropping for JPEG Encoder

Message ID 20250317225107.727422-1-b-brnich@ti.com
State Accepted
Delegated to: Ryan Eatmon
Headers show
Series [meta-arago,master] gstreamer1.0-plugins-good: Add Patch to Support Cropping for JPEG Encoder | expand

Commit Message

Brandon Brnich March 17, 2025, 10:51 p.m. UTC
The E5010 JPEG Encoder found on TI's am62a has the ability to only
encode a specific region using the V4L2 S_SELECTION IOCTL. This control
allows a cropping rectangle to be set where anything outside that region
will be ignored. Gstreamer did not have support for cropping.

This patch adds support at the gstreamer level by exposing a crop-bounds
control for the v4l2jpegenc element and updating the format accordingly
if a crop rectangle is being requested.

Signed-off-by: Brandon Brnich <b-brnich@ti.com>
---

Master is using gstreamer 1.24 so needed to send a separate patch to get
functionality in master.

 ...support-for-cropping-in-JPEG-Encoder.patch | 287 ++++++++++++++++++
 .../gstreamer1.0-plugins-good_1.24-arago.inc  |   1 +
 2 files changed, 288 insertions(+)
 create mode 100644 meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good/0001-v4l2jpegenc-Add-support-for-cropping-in-JPEG-Encoder.patch
diff mbox series

Patch

diff --git a/meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good/0001-v4l2jpegenc-Add-support-for-cropping-in-JPEG-Encoder.patch b/meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good/0001-v4l2jpegenc-Add-support-for-cropping-in-JPEG-Encoder.patch
new file mode 100644
index 00000000..c761314e
--- /dev/null
+++ b/meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good/0001-v4l2jpegenc-Add-support-for-cropping-in-JPEG-Encoder.patch
@@ -0,0 +1,287 @@ 
+From c997f3ee027da932cd8626dd50e86b9085c56b23 Mon Sep 17 00:00:00 2001
+From: Brandon Brnich <b-brnich@ti.com>
+Date: Mon, 10 Mar 2025 15:25:51 +0000
+Subject: [PATCH] v4l2jpegenc: Add support for cropping in JPEG Encoder
+
+V4L2 exposes control that allows users to set a cropping rectangle. This
+rectangle can be used to encode a specific region of interest.
+
+Add property to v4l2jpegenc to customize the region and update
+set_format function accordingly.
+
+Upstream-Status: Pending
+
+Signed-off-by: Brandon Brnich <b-brnich@ti.com>
+---
+ sys/v4l2/gstv4l2jpegenc.c                 | 204 +++++++++++++++++-
+ sys/v4l2/gstv4l2jpegenc.h                 |   4 +
+ 2 files changed, 206 insertions(+), 2 deletions(-)
+
+diff --git a/sys/v4l2/gstv4l2jpegenc.c b/sys/v4l2/gstv4l2jpegenc.c
+index eee7a67da5..43f76ec9f3 100644
+--- a/sys/v4l2/gstv4l2jpegenc.c
++++ b/sys/v4l2/gstv4l2jpegenc.c
+@@ -46,24 +46,210 @@ enum
+ {
+   PROP_0,
+   V4L2_STD_OBJECT_PROPS,
++  PROP_CROP_BOUNDS,
++  PROP_LAST
+   /* TODO */
+ };
+
+ #define gst_v4l2_jpeg_enc_parent_class parent_class
+ G_DEFINE_TYPE (GstV4l2JPEGEnc, gst_v4l2_jpeg_enc, GST_TYPE_V4L2_VIDEO_ENC);
+
++static void
++gst_v4l2_jpeg_enc_set_rect_value (GValue * value, struct v4l2_rect *rect)
++{
++  GValue val = { 0 };
++
++  g_value_init (&val, G_TYPE_INT);
++  g_value_reset (value);
++
++  g_value_set_int (&val, rect->left);
++  gst_value_array_append_value (value, &val);
++
++  g_value_set_int (&val, rect->top);
++  gst_value_array_append_value (value, &val);
++
++  g_value_set_int (&val, rect->width);
++  gst_value_array_append_value (value, &val);
++
++  g_value_set_int (&val, rect->height);
++  gst_value_array_append_value (value, &val);
++
++  g_value_unset (&val);
++}
++
+ static void
+ gst_v4l2_jpeg_enc_set_property (GObject * object,
+     guint prop_id, const GValue * value, GParamSpec * pspec)
+ {
+-  /* TODO */
++  GstV4l2JPEGEnc *v4l2jpegenc = GST_V4L2_JPEG_ENC (object);
++
++  if (!gst_v4l2_object_set_property_helper (v4l2jpegenc->parent.v4l2output,
++          prop_id, value, pspec)) {
++    switch (prop_id) {
++      case PROP_CROP_BOUNDS:
++        v4l2jpegenc->crop_bounds.left = g_value_get_int (gst_value_array_get_value (value, 0));
++        v4l2jpegenc->crop_bounds.top = g_value_get_int (gst_value_array_get_value (value, 1));
++        v4l2jpegenc->crop_bounds.width = g_value_get_int (gst_value_array_get_value (value, 2));
++        v4l2jpegenc->crop_bounds.height = g_value_get_int (gst_value_array_get_value (value, 3));
++        v4l2jpegenc->apply_crop_settings = TRUE;
++        break;
++      default:
++        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++        break;
++    }
++  }
+ }
+
+ static void
+ gst_v4l2_jpeg_enc_get_property (GObject * object,
+     guint prop_id, GValue * value, GParamSpec * pspec)
+ {
+-  /* TODO */
++  GstV4l2JPEGEnc *v4l2jpegenc = GST_V4L2_JPEG_ENC (object);
++  guint i = 0;
++
++  if (!gst_v4l2_object_get_property_helper (v4l2jpegenc->parent.v4l2output,
++          prop_id, value, pspec)) {
++    switch (prop_id) {
++      case PROP_CROP_BOUNDS:
++        gst_v4l2_jpeg_enc_set_rect_value (value, &v4l2jpegenc->crop_bounds);
++        for (i = 0; i < gst_value_array_get_size (value); i++) {
++          GST_DEBUG_OBJECT(object, "entry %i is %i", i, gst_value_array_get_value (value, i));
++        }
++        break;
++      default:
++        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
++        break;
++    }
++  }
++
++}
++
++static gboolean
++gst_v4l2_encoder_cmd (GstV4l2Object * v4l2object, guint cmd, guint flags)
++{
++  struct v4l2_encoder_cmd ecmd = { 0, };
++
++  GST_DEBUG_OBJECT (v4l2object->element,
++      "sending v4l2 encoder command %u with flags %u", cmd, flags);
++
++  if (!GST_V4L2_IS_OPEN (v4l2object))
++    return FALSE;
++
++  ecmd.cmd = cmd;
++  ecmd.flags = flags;
++  if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_ENCODER_CMD, &ecmd) < 0)
++    goto ecmd_failed;
++
++  return TRUE;
++
++ecmd_failed:
++  if (errno == ENOTTY) {
++    GST_INFO_OBJECT (v4l2object->element,
++        "Failed to send encoder command %u with flags %u for '%s'. (%s)",
++        cmd, flags, v4l2object->videodev, g_strerror (errno));
++  } else {
++    GST_ERROR_OBJECT (v4l2object->element,
++        "Failed to send encoder command %u with flags %u for '%s'. (%s)",
++        cmd, flags, v4l2object->videodev, g_strerror (errno));
++  }
++  return FALSE;
++}
++
++static GstFlowReturn
++gst_v4l2_video_enc_finish (GstVideoEncoder * encoder)
++{
++  GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder);
++  GstFlowReturn ret = GST_FLOW_OK;
++
++  if (gst_pad_get_task_state (encoder->srcpad) != GST_TASK_STARTED)
++    goto done;
++
++  GST_DEBUG_OBJECT (self, "Finishing encoding");
++
++  /* drop the stream lock while draining, so remaining buffers can be
++   * pushed from the src pad task thread */
++  GST_VIDEO_ENCODER_STREAM_UNLOCK (encoder);
++
++  if (gst_v4l2_encoder_cmd (self->v4l2capture, V4L2_ENC_CMD_STOP, 0)) {
++    GstTask *task = encoder->srcpad->task;
++
++    /* Wait for the task to be drained */
++    GST_DEBUG_OBJECT (self, "Waiting for encoder stop");
++    GST_OBJECT_LOCK (task);
++    while (GST_TASK_STATE (task) == GST_TASK_STARTED)
++      GST_TASK_WAIT (task);
++    GST_OBJECT_UNLOCK (task);
++    ret = GST_FLOW_FLUSHING;
++  }
++
++  /* and ensure the processing thread has stopped in case another error
++   * occurred. */
++  gst_v4l2_object_unlock (self->v4l2capture);
++  gst_pad_stop_task (encoder->srcpad);
++  GST_VIDEO_ENCODER_STREAM_LOCK (encoder);
++
++  if (ret == GST_FLOW_FLUSHING)
++    ret = self->output_flow;
++
++  GST_DEBUG_OBJECT (encoder, "Done draining buffers");
++
++done:
++  return ret;
++}
++
++static gboolean
++gst_v4l2_video_jpeg_set_format (GstVideoEncoder * encoder,
++    GstVideoCodecState * state)
++{
++  gboolean ret = TRUE;
++  GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder);
++  GstV4l2JPEGEnc *v4l2jpegenc = GST_V4L2_JPEG_ENC (encoder);
++  GstV4l2Error error = GST_V4L2_ERROR_INIT;
++  GstCaps *outcaps;
++  GstVideoCodecState *output;
++
++  GST_DEBUG_OBJECT (self, "Setting format: %" GST_PTR_FORMAT, state->caps);
++
++  if (self->input_state) {
++    if (gst_v4l2_object_caps_equal (self->v4l2output, state->caps)) {
++      GST_DEBUG_OBJECT (self, "Compatible caps");
++      return TRUE;
++    }
++
++    if (gst_v4l2_video_enc_finish (encoder) != GST_FLOW_OK)
++      return FALSE;
++
++    gst_v4l2_object_stop (self->v4l2output);
++    gst_v4l2_object_stop (self->v4l2capture);
++
++    gst_video_codec_state_unref (self->input_state);
++    self->input_state = NULL;
++  }
++
++  outcaps = gst_pad_get_pad_template_caps (encoder->srcpad);
++  outcaps = gst_caps_make_writable (outcaps);
++  output = gst_video_encoder_set_output_state (encoder, outcaps, state);
++  gst_video_codec_state_unref (output);
++
++  if (!gst_video_encoder_negotiate (encoder))
++    return FALSE;
++
++  if (!gst_v4l2_object_set_format (self->v4l2output, state->caps, &error)) {
++    gst_v4l2_error (self, &error);
++    return FALSE;
++  }
++
++  /* best effort */
++  if (v4l2jpegenc->apply_crop_settings)
++    gst_v4l2_object_set_crop (self->v4l2output, &v4l2jpegenc->crop_bounds);
++  else
++    gst_v4l2_object_setup_padding (self->v4l2output);
++
++  self->input_state = gst_video_codec_state_ref (state);
++
++  GST_DEBUG_OBJECT (self, "output caps: %" GST_PTR_FORMAT, state->caps);
++
++  return ret;
+ }
+
+ static void
+@@ -77,12 +263,14 @@ gst_v4l2_jpeg_enc_class_init (GstV4l2JPEGEncClass * klass)
+   GstElementClass *element_class;
+   GObjectClass *gobject_class;
+   GstV4l2VideoEncClass *baseclass;
++  GstVideoEncoderClass *video_encoder_class;
+
+   parent_class = g_type_class_peek_parent (klass);
+
+   element_class = (GstElementClass *) klass;
+   gobject_class = (GObjectClass *) klass;
+   baseclass = (GstV4l2VideoEncClass *) (klass);
++  video_encoder_class = (GstVideoEncoderClass *) klass;
+
+   GST_DEBUG_CATEGORY_INIT (gst_v4l2_jpeg_enc_debug, "v4l2jpegenc", 0,
+       "V4L2 JPEG Encoder");
+@@ -99,6 +287,18 @@ gst_v4l2_jpeg_enc_class_init (GstV4l2JPEGEncClass * klass)
+       GST_DEBUG_FUNCPTR (gst_v4l2_jpeg_enc_get_property);
+
+   baseclass->codec_name = "JPEG";
++  video_encoder_class->set_format =
++      GST_DEBUG_FUNCPTR (gst_v4l2_video_jpeg_set_format);
++
++  g_object_class_install_property (gobject_class, PROP_CROP_BOUNDS,
++      gst_param_spec_array ("crop-bounds", "Crop bounds",
++          "The bounding region for crop rectangles ('<x, y, width, height>').",
++          g_param_spec_int ("rect-value", "Rectangle Value",
++              "One of x, y, width or height value.", G_MININT, G_MAXINT, -1,
++              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS),
++          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
++
++
+ }
+
+ /* Probing functions */
+diff --git a/sys/v4l2/gstv4l2jpegenc.h b/sys/v4l2/gstv4l2jpegenc.h
+index 8b6d0b7a64..59cad21e08 100644
+--- a/sys/v4l2/gstv4l2jpegenc.h
++++ b/sys/v4l2/gstv4l2jpegenc.h
+@@ -42,6 +42,10 @@ typedef struct _GstV4l2JPEGEncClass GstV4l2JPEGEncClass;
+ struct _GstV4l2JPEGEnc
+ {
+   GstV4l2VideoEnc parent;
++
++  struct v4l2_rect crop_bounds;
++  gboolean apply_crop_settings;
++
+ };
+
+ struct _GstV4l2JPEGEncClass
+--
+2.34.1
diff --git a/meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good_1.24-arago.inc b/meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good_1.24-arago.inc
index 26549776..a9353189 100644
--- a/meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good_1.24-arago.inc
+++ b/meta-arago-extras/recipes-multimedia/gstreamer/gstreamer1.0-plugins-good_1.24-arago.inc
@@ -5,6 +5,7 @@  SRC_URI:append = " \
     file://0003-v4l2-Changes-for-DMA-Buf-import-j721s2.patch \
     file://0004-v4l2-Give-preference-to-contiguous-format-if-support.patch \
     file://0005-HACK-gstv4l2object-Increase-min-buffers-for-CSI-capt.patch \
+    file://0001-v4l2jpegenc-Add-support-for-cropping-in-JPEG-Encoder.patch \
 "
 
 PR:append = ".arago0"