Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libpisp for openSUSE:Factory checked in at 2026-09-10 16:40:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libpisp (Old) and /work/SRC/openSUSE:Factory/.libpisp.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libpisp" Thu Sep 10 16:40:05 2026 rev:3 rq:1376896 version:1.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/libpisp/libpisp.changes 2026-06-30 17:41:17.245544232 +0200 +++ /work/SRC/openSUSE:Factory/.libpisp.new.1265/libpisp.changes 2026-09-10 16:40:09.351794485 +0200 @@ -1,0 +2,10 @@ +Thu Sep 10 08:15:08 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 1.7.0 + * gst: Add BGR output format support. + * gst: Honour GstVideoMeta strides on input buffers. + * gst: Fix R/B channel ordering for RGB output formats. + * tests: Add strided input and BGR test cases. + * tests: Drop BGR compensation for GStreamer RGB output. + +------------------------------------------------------------------- Old: ---- libpisp-1.6.0.tar.gz New: ---- libpisp-1.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libpisp.spec ++++++ --- /var/tmp/diff_new_pack.OcCGOV/_old 2026-09-10 16:40:10.350836396 +0200 +++ /var/tmp/diff_new_pack.OcCGOV/_new 2026-09-10 16:40:10.353836522 +0200 @@ -18,7 +18,7 @@ %define soversion 1 Name: libpisp -Version: 1.6.0 +Version: 1.7.0 Release: 0 Summary: RPi ISP helper library Group: System/Libraries ++++++ libpisp-1.6.0.tar.gz -> libpisp-1.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/.github/workflows/camera-test-libpisp.yml new/libpisp-1.7.0/.github/workflows/camera-test-libpisp.yml --- old/libpisp-1.6.0/.github/workflows/camera-test-libpisp.yml 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/.github/workflows/camera-test-libpisp.yml 2026-08-07 11:06:54.000000000 +0200 @@ -10,7 +10,7 @@ jobs: trigger-camera-tests: runs-on: [self-hosted, camera-test-bridge] - environment: ${{ github.event.pull_request.head.repo.full_name == github.repository && '' || 'camera-test' }} + environment: ${{ github.event.pull_request.head.repo.full_name != github.repository && 'camera-test' || '' }} timeout-minutes: 120 steps: - name: Checkout camera_tester diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/LICENSES/CC0-1.0.txt new/libpisp-1.7.0/LICENSES/CC0-1.0.txt --- old/libpisp-1.6.0/LICENSES/CC0-1.0.txt 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/LICENSES/CC0-1.0.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,119 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES -NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE -AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION -ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE -OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS -LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION -OR WORKS PROVIDED HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer exclusive -Copyright and Related Rights (defined below) upon the creator and subsequent -owner(s) (each and all, an "owner") of an original work of authorship and/or -a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for the -purpose of contributing to a commons of creative, cultural and scientific -works ("Commons") that the public can reliably and without fear of later claims -of infringement build upon, modify, incorporate in other works, reuse and -redistribute as freely as possible in any form whatsoever and for any purposes, -including without limitation commercial purposes. These owners may contribute -to the Commons to promote the ideal of a free culture and the further production -of creative, cultural and scientific works, or to gain reputation or greater -distribution for their Work in part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any expectation -of additional consideration or compensation, the person associating CC0 with -a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright -and Related Rights in the Work, voluntarily elects to apply CC0 to the Work -and publicly distribute the Work under its terms, with knowledge of his or -her Copyright and Related Rights in the Work and the meaning and intended -legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be protected -by copyright and related or neighboring rights ("Copyright and Related Rights"). -Copyright and Related Rights include, but are not limited to, the following: - -i. the right to reproduce, adapt, distribute, perform, display, communicate, -and translate a Work; - - ii. moral rights retained by the original author(s) and/or performer(s); - -iii. publicity and privacy rights pertaining to a person's image or likeness -depicted in a Work; - -iv. rights protecting against unfair competition in regards to a Work, subject -to the limitations in paragraph 4(a), below; - -v. rights protecting the extraction, dissemination, use and reuse of data -in a Work; - -vi. database rights (such as those arising under Directive 96/9/EC of the -European Parliament and of the Council of 11 March 1996 on the legal protection -of databases, and under any national implementation thereof, including any -amended or successor version of such directive); and - -vii. other similar, equivalent or corresponding rights throughout the world -based on applicable law or treaty, and any national implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention of, -applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and -unconditionally waives, abandons, and surrenders all of Affirmer's Copyright -and Related Rights and associated claims and causes of action, whether now -known or unknown (including existing as well as future claims and causes of -action), in the Work (i) in all territories worldwide, (ii) for the maximum -duration provided by applicable law or treaty (including future time extensions), -(iii) in any current or future medium and for any number of copies, and (iv) -for any purpose whatsoever, including without limitation commercial, advertising -or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the -benefit of each member of the public at large and to the detriment of Affirmer's -heirs and successors, fully intending that such Waiver shall not be subject -to revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason be -judged legally invalid or ineffective under applicable law, then the Waiver -shall be preserved to the maximum extent permitted taking into account Affirmer's -express Statement of Purpose. In addition, to the extent the Waiver is so -judged Affirmer hereby grants to each affected person a royalty-free, non -transferable, non sublicensable, non exclusive, irrevocable and unconditional -license to exercise Affirmer's Copyright and Related Rights in the Work (i) -in all territories worldwide, (ii) for the maximum duration provided by applicable -law or treaty (including future time extensions), (iii) in any current or -future medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional purposes -(the "License"). The License shall be deemed effective as of the date CC0 -was applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder of -the License, and in such case Affirmer hereby affirms that he or she will -not (i) exercise any of his or her remaining Copyright and Related Rights -in the Work or (ii) assert any associated claims and causes of action with -respect to the Work, in either case contrary to Affirmer's express Statement -of Purpose. - - 4. Limitations and Disclaimers. - -a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, -licensed or otherwise affected by this document. - -b. Affirmer offers the Work as-is and makes no representations or warranties -of any kind concerning the Work, express, implied, statutory or otherwise, -including without limitation warranties of title, merchantability, fitness -for a particular purpose, non infringement, or the absence of latent or other -defects, accuracy, or the present or absence of errors, whether or not discoverable, -all to the greatest extent permissible under applicable law. - -c. Affirmer disclaims responsibility for clearing rights of other persons -that may apply to the Work or any use thereof, including without limitation -any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims -responsibility for obtaining any necessary consents, permissions or other -rights required for any use of the Work. - -d. Affirmer understands and acknowledges that Creative Commons is not a party -to this document and has no duty or obligation with respect to this CC0 or -use of the Work. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/meson.build new/libpisp-1.7.0/meson.build --- old/libpisp-1.6.0/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,9 +1,9 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd project('libpisp', 'c', 'cpp', meson_version : '>= 0.58.0', - version : '1.6.0', + version : '1.7.0', default_options : [ 'werror=true', 'warning_level=2', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/meson_options.txt new/libpisp-1.7.0/meson_options.txt --- old/libpisp-1.6.0/meson_options.txt 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/meson_options.txt 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd option('logging', type : 'feature', value : 'auto') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/examples/meson.build new/libpisp-1.7.0/src/examples/meson.build --- old/libpisp-1.6.0/src/examples/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/examples/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2025, Raspberry Pi Ltd opts_dep = dependency('cxxopts', fallback : ['cxxopts', 'cxxopts_dep']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/gst/gstpispconvert.cpp new/libpisp-1.7.0/src/gst/gstpispconvert.cpp --- old/libpisp-1.6.0/src/gst/gstpispconvert.cpp 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/gst/gstpispconvert.cpp 2026-08-07 11:06:54.000000000 +0200 @@ -32,7 +32,7 @@ #define GST_CAT_DEFAULT gst_pisp_convert_debug /* Supported GStreamer formats */ -#define PISP_FORMATS "{ RGB, RGBx, BGRx, I420, YV12, Y42B, Y444, YUY2, UYVY, NV12, NV12_128C8, NV12_10LE32_128C8 }" +#define PISP_FORMATS "{ RGB, BGR, RGBx, BGRx, I420, YV12, Y42B, Y444, YUY2, UYVY, NV12, NV12_128C8, NV12_10LE32_128C8 }" /* Supported DRM fourccs */ #define PISP_DRM_FORMATS \ "{ RG24, XB24, XR24, YU12, YV12, YU16, YU24, YUYV, UYVY, NV12, NV12:0x0700000000000004, P030:0x0700000000000004 }" @@ -66,8 +66,9 @@ /* Bidirectional mapping between GstVideoFormat and PiSP format strings */ static const std::map<GstVideoFormat, std::string> gst_pisp_format_map = { { GST_VIDEO_FORMAT_RGB, "RGB888" }, + { GST_VIDEO_FORMAT_BGR, "RGB888" }, { GST_VIDEO_FORMAT_RGBx, "RGBX8888" }, - { GST_VIDEO_FORMAT_BGRx, "XRGB8888" }, + { GST_VIDEO_FORMAT_BGRx, "RGBX8888" }, { GST_VIDEO_FORMAT_I420, "YUV420P" }, { GST_VIDEO_FORMAT_YV12, "YVU420P" }, { GST_VIDEO_FORMAT_Y42B, "YUV422P" }, @@ -84,7 +85,7 @@ { "RG24", "RGB888" }, { "BG24", "RGB888" }, { "XB24", "RGBX8888" }, - { "XR24", "XRGB8888" }, + { "XR24", "RGBX8888" }, { "YU12", "YUV420P" }, { "YV12", "YVU420P" }, { "YU16", "YUV422P" }, @@ -96,20 +97,33 @@ { "P030:0x0700000000000004", "YUV420SP10_COL128" }, }; +/* Formats storing channels in B,G,R memory order need an R/B swap relative to + * the hardware's native R,G,B channel output. Keyed on the GStreamer/DRM + * format as the PiSP format string does not encode the channel order. */ +static bool require_rb_swap(GstVideoFormat format) +{ + return format == GST_VIDEO_FORMAT_BGR || format == GST_VIDEO_FORMAT_BGRx; +} + +static bool require_rb_swap(const gchar *drm_format) +{ + return drm_format && (g_str_equal(drm_format, "XR24") || g_str_equal(drm_format, "RG24")); +} + static const char *gst_format_to_pisp(GstVideoFormat format) { auto it = gst_pisp_format_map.find(format); return it != gst_pisp_format_map.end() ? it->second.c_str() : nullptr; } -static GstVideoFormat pisp_to_gst_video_format(const char *pisp_format) +static GstVideoFormat pisp_to_gst_video_format(const char *pisp_format, bool rb_swap) { if (!pisp_format) return GST_VIDEO_FORMAT_UNKNOWN; for (const auto &[gst_fmt, pisp_fmt] : gst_pisp_format_map) { - if (g_str_equal(pisp_format, pisp_fmt.c_str())) + if (g_str_equal(pisp_format, pisp_fmt.c_str()) && rb_swap == require_rb_swap(gst_fmt)) return gst_fmt; } return GST_VIDEO_FORMAT_UNKNOWN; @@ -153,7 +167,7 @@ /* Configure colour space conversion blocks for the backend */ static uint32_t configure_colour_conversion(libpisp::BackEnd *backend, const char *in_format, const char *in_colorspace, - const char *out_format, const char *out_colorspace, + const char *out_format, bool out_rb_swap, const char *out_colorspace, unsigned int output_index) { uint32_t rgb_enables = 0; @@ -175,8 +189,7 @@ backend->SetCsc(output_index, csc); rgb_enables |= PISP_BE_RGB_ENABLE_CSC(output_index); } - else if (g_str_equal(out_format, "RGB888") || g_str_equal(out_format, "RGBX8888") || - g_str_equal(out_format, "XRGB8888")) + else if (out_rb_swap) { /* R/B channel swap to match GStreamer/DRM byte ordering */ pisp_be_ccm_config csc = {}; @@ -363,6 +376,11 @@ self->priv->configured = FALSE; self->priv->dmabuf_allocator = gst_dmabuf_allocator_new(); self->priv->use_dmabuf_input = FALSE; + self->priv->in_has_meta = FALSE; + self->priv->in_n_mem = 0; + self->priv->in_meta_stride = { 0, 0, 0 }; + self->priv->in_meta_offset = { 0, 0, 0 }; + self->priv->force_memcpy_input = FALSE; for (unsigned int i = 0; i < PISP_NUM_OUTPUTS; i++) { @@ -371,6 +389,7 @@ self->priv->out_stride[i] = 0; self->priv->out_hw_stride[i] = 0; self->priv->out_format[i] = nullptr; + self->priv->out_rb_swap[i] = false; self->priv->output_enabled[i] = FALSE; self->priv->use_dmabuf_output[i] = FALSE; self->priv->output_pool[i] = nullptr; @@ -447,6 +466,7 @@ gst_structure_get_int(out_structure, "width", (gint *)&self->priv->out_width[index]); gst_structure_get_int(out_structure, "height", (gint *)&self->priv->out_height[index]); self->priv->out_format[index] = drm_format_to_pisp(drm_format); + self->priv->out_rb_swap[index] = require_rb_swap(drm_format); self->priv->out_stride[index] = 0; GstVideoColorimetry colorimetry = {}; @@ -470,6 +490,7 @@ self->priv->out_height[index] = GST_VIDEO_INFO_HEIGHT(&out_info); self->priv->out_stride[index] = GST_VIDEO_INFO_PLANE_STRIDE(&out_info, 0); self->priv->out_format[index] = gst_format_to_pisp(GST_VIDEO_INFO_FORMAT(&out_info)); + self->priv->out_rb_swap[index] = require_rb_swap(GST_VIDEO_INFO_FORMAT(&out_info)); self->priv->out_colorspace[index] = colorimetry_to_pisp(&GST_VIDEO_INFO_COLORIMETRY(&out_info)); GST_INFO_OBJECT(self, "Output%u format: pisp=%s, colorspace=%s (matrix=%d, range=%d)", index, self->priv->out_format[index], self->priv->out_colorspace[index], @@ -637,9 +658,10 @@ } /* Attach GstVideoMeta with the correct hardware stride to a dmabuf output buffer */ -static void add_video_meta(GstBuffer *buffer, const char *pisp_format, guint width, guint height, guint hw_stride) +static void add_video_meta(GstBuffer *buffer, const char *pisp_format, bool rb_swap, guint width, guint height, + guint hw_stride) { - GstVideoFormat gst_fmt = pisp_to_gst_video_format(pisp_format); + GstVideoFormat gst_fmt = pisp_to_gst_video_format(pisp_format, rb_swap); if (gst_fmt == GST_VIDEO_FORMAT_UNKNOWN) return; @@ -664,16 +686,16 @@ strides); } -static void copy_planes(std::array<uint8_t *, 3> src, guint src_stride, std::array<uint8_t *, 3> dst, guint dst_stride, - guint width, guint height, const char *format) +static void copy_planes(std::array<uint8_t *, 3> src, std::array<guint, 3> src_stride, std::array<uint8_t *, 3> dst, + std::array<guint, 3> dst_stride, guint width, guint height, const char *format) { - GST_DEBUG("copy_planes: %ux%u, src_stride=%u, dst_stride=%u, format=%s", width, height, src_stride, dst_stride, - format); + GST_DEBUG("copy_planes: %ux%u, src_stride=%u, dst_stride=%u, format=%s", width, height, src_stride[0], + dst_stride[0], format); /* YUV420SP_COL128 (NV12 column 128) - special tiled format */ if (strncmp(format, "YUV420SP_COL128", 15) == 0 || strncmp(format, "YUV420SP10_COL128", 17) == 0) { - guint y_size = GST_VIDEO_TILE_X_TILES(src_stride) * 128 * GST_VIDEO_TILE_Y_TILES(src_stride) * 8; + guint y_size = GST_VIDEO_TILE_X_TILES(src_stride[0]) * 128 * GST_VIDEO_TILE_Y_TILES(src_stride[0]) * 8; memcpy(dst[0], src[0], y_size); uint8_t *src_uv = src[1] ? src[1] : src[0] + y_size; @@ -682,12 +704,31 @@ return; } + /* Semi-planar YUV formats: interleaved UV plane at half height (YUV420SP) */ + if (is_yuv_format(format) && strstr(format, "SP") != nullptr) + { + /* Copy Y plane line by line */ + for (guint y = 0; y < height; ++y) + memcpy(dst[0] + y * dst_stride[0], src[0] + y * src_stride[0], width); + + guint src_uv_stride = src_stride[1] ? src_stride[1] : src_stride[0]; + guint dst_uv_stride = dst_stride[1] ? dst_stride[1] : dst_stride[0]; + + /* Calculate plane pointers if not explicitly provided (single contiguous buffer) */ + uint8_t *src_uv = src[1] ? src[1] : src[0] + src_stride[0] * height; + uint8_t *dst_uv = dst[1] ? dst[1] : dst[0] + dst_stride[0] * height; + + for (guint y = 0; y < height / 2; ++y) + memcpy(dst_uv + y * dst_uv_stride, src_uv + y * src_uv_stride, width); + return; + } + /* Planar YUV formats: YUV420P, YVU420P, YUV422P, YUV444P */ if (is_yuv_format(format) && strstr(format, "P") != nullptr) { /* Copy Y plane line by line */ for (guint y = 0; y < height; ++y) - memcpy(dst[0] + y * dst_stride, src[0] + y * src_stride, width); + memcpy(dst[0] + y * dst_stride[0], src[0] + y * src_stride[0], width); /* Determine UV subsampling */ guint uv_width, uv_height; @@ -707,13 +748,13 @@ uv_height = height; } - guint src_uv_stride = (uv_width == width) ? src_stride : src_stride / 2; - guint dst_uv_stride = (uv_width == width) ? dst_stride : dst_stride / 2; + guint src_uv_stride = src_stride[1] ? src_stride[1] : (uv_width == width) ? src_stride[0] : src_stride[0] / 2; + guint dst_uv_stride = dst_stride[1] ? dst_stride[1] : (uv_width == width) ? dst_stride[0] : dst_stride[0] / 2; /* Calculate plane pointers if not explicitly provided (single contiguous buffer) */ - uint8_t *src_u = src[1] ? src[1] : src[0] + src_stride * height; + uint8_t *src_u = src[1] ? src[1] : src[0] + src_stride[0] * height; uint8_t *src_v = src[2] ? src[2] : src_u + src_uv_stride * uv_height; - uint8_t *dst_u = dst[1] ? dst[1] : dst[0] + dst_stride * height; + uint8_t *dst_u = dst[1] ? dst[1] : dst[0] + dst_stride[0] * height; uint8_t *dst_v = dst[2] ? dst[2] : dst_u + dst_uv_stride * uv_height; /* Copy U and V planes */ @@ -730,19 +771,22 @@ guint line_stride = width * bytes_per_pixel; for (guint y = 0; y < height; ++y) - memcpy(dst[0] + y * dst_stride, src[0] + y * src_stride, line_stride); + memcpy(dst[0] + y * dst_stride[0], src[0] + y * src_stride[0], line_stride); } static void copy_buffer_to_pisp(GstBuffer *gstbuf, std::array<uint8_t *, 3> &mem, guint width, guint height, - guint gst_stride, guint hw_stride, const char *format) + const std::array<guint, 3> &gst_stride, const std::array<gsize, 3> &gst_offset, + guint hw_stride, const char *format) { GstMapInfo map; gst_buffer_map(gstbuf, &map, GST_MAP_READ); - /* GstBuffer is always contiguous - planes calculated from offsets */ - std::array<uint8_t *, 3> src = { map.data, nullptr, nullptr }; + /* Plane pointers from the GstVideoMeta offsets; zero offsets leave the + * pointer unset so copy_planes derives a contiguous layout. */ + std::array<uint8_t *, 3> src = { map.data + gst_offset[0], gst_offset[1] ? map.data + gst_offset[1] : nullptr, + gst_offset[2] ? map.data + gst_offset[2] : nullptr }; - copy_planes(src, gst_stride, mem, hw_stride, width, height, format); + copy_planes(src, gst_stride, mem, { hw_stride, 0, 0 }, width, height, format); gst_buffer_unmap(gstbuf, &map); } @@ -756,12 +800,47 @@ /* GstBuffer is always contiguous - planes calculated from offsets */ std::array<uint8_t *, 3> dst = { map.data, nullptr, nullptr }; - copy_planes(const_cast<std::array<uint8_t *, 3> &>(mem), hw_stride, dst, gst_stride, width, height, format); + copy_planes(const_cast<std::array<uint8_t *, 3> &>(mem), { hw_stride, 0, 0 }, dst, { gst_stride, 0, 0 }, width, + height, format); gst_buffer_unmap(gstbuf, &map); } /* + * Check whether the input buffer layout described by the GstVideoMeta can be + * consumed directly by the hardware. Zero-copy needs hardware-aligned strides + * and, for a single memory block, planes contiguous at those strides (the + * V4L2 device derives plane offsets from the configured stride and height). + */ +static gboolean zero_copy_layout_ok(GstPispConvert *self) +{ + const std::array<guint, 3> &stride = self->priv->in_meta_stride; + const std::array<gsize, 3> &offset = self->priv->in_meta_offset; + const char *format = self->priv->in_format; + guint height = self->priv->in_height; + + if (offset[0] != 0 || stride[0] % PISP_BACK_END_OUTPUT_MIN_ALIGN) + return FALSE; + + /* Per-plane memory blocks: offsets fall on memory boundaries */ + if (self->priv->in_n_mem > 1) + return TRUE; + + if (is_yuv_format(format) && strstr(format, "SP") != nullptr) + return offset[1] == (gsize)stride[0] * height; + + if (is_yuv_format(format) && strstr(format, "P") != nullptr) + { + guint uv_height = strstr(format, "420") != nullptr ? height / 2 : height; + return stride[1] == stride[2] && offset[1] == (gsize)stride[0] * height && + offset[2] == offset[1] + (gsize)stride[1] * uv_height; + } + + /* Packed single-plane formats */ + return TRUE; +} + +/* * Configure the PiSP backend for the current input/output settings. */ static gboolean gst_pisp_convert_configure(GstPispConvert *self) @@ -847,7 +926,30 @@ GST_ERROR_OBJECT(self, "Failed to get input format"); return FALSE; } - libpisp::compute_stride(input_cfg); + + /* For zero-copy input the hardware must read with the producer's + * actual strides (from GstVideoMeta) rather than our computed ones. + * COL128 formats keep their tile-encoded stride semantics. */ + self->priv->force_memcpy_input = FALSE; + gboolean col128 = strstr(self->priv->in_format, "COL128") != nullptr; + + if (self->priv->use_dmabuf_input && self->priv->in_has_meta && !col128) + { + if (zero_copy_layout_ok(self)) + { + input_cfg.stride = self->priv->in_meta_stride[0]; + input_cfg.stride2 = self->priv->in_meta_stride[1]; + } + else + { + GST_INFO_OBJECT(self, "Input buffer layout unsuitable for zero-copy, using memcpy path"); + self->priv->force_memcpy_input = TRUE; + libpisp::compute_stride(input_cfg); + } + } + else + libpisp::compute_stride(input_cfg); + self->priv->in_hw_stride = input_cfg.stride; self->priv->backend->SetInputFormat(input_cfg); @@ -889,7 +991,8 @@ global.rgb_enables |= configure_colour_conversion(self->priv->backend.get(), self->priv->in_format, self->priv->in_colorspace, self->priv->out_format[i], - self->priv->out_colorspace[i], i); + self->priv->out_rb_swap[i], self->priv->out_colorspace[i], + i); GST_INFO_OBJECT(self, "Output%d: %ux%u %s (stride: gst=%u hw=%u) colorspace %s", i, self->priv->out_width[i], self->priv->out_height[i], self->priv->out_format[i], @@ -1031,6 +1134,38 @@ GstFlowReturn ret = GST_FLOW_OK; GstBuffer *outbuf[PISP_NUM_OUTPUTS] = { nullptr, nullptr }; + /* Pick up the buffer's actual plane layout from GstVideoMeta, falling + * back to the caps-derived stride with contiguous planes. */ + GstVideoMeta *meta = gst_buffer_get_video_meta(inbuf); + std::array<guint, 3> meta_stride = { self->priv->in_stride, 0, 0 }; + std::array<gsize, 3> meta_offset = { 0, 0, 0 }; + if (meta) + { + for (guint p = 0; p < MIN(meta->n_planes, 3u); p++) + { + meta_stride[p] = meta->stride[p]; + meta_offset[p] = meta->offset[p]; + } + GST_LOG_OBJECT(self, + "Input meta: planes=%u strides=%u/%u/%u offsets=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT + "/%" G_GSIZE_FORMAT " buffer size=%" G_GSIZE_FORMAT " n_mem=%u", + meta->n_planes, meta_stride[0], meta_stride[1], meta_stride[2], meta_offset[0], meta_offset[1], + meta_offset[2], gst_buffer_get_size(inbuf), gst_buffer_n_memory(inbuf)); + } + + /* The zero-copy input configuration depends on the buffer layout */ + if (self->priv->configured && self->priv->use_dmabuf_input && + (meta_stride != self->priv->in_meta_stride || meta_offset != self->priv->in_meta_offset)) + { + GST_INFO_OBJECT(self, "Input buffer layout changed, reconfiguring"); + self->priv->configured = FALSE; + } + + self->priv->in_has_meta = meta != nullptr; + self->priv->in_meta_stride = meta_stride; + self->priv->in_meta_offset = meta_offset; + self->priv->in_n_mem = gst_buffer_n_memory(inbuf); + /* Configure on first buffer if not already configured */ if (!self->priv->configured) { @@ -1088,7 +1223,7 @@ slice.emplace(node_name, buffers[index]); /* Prepare input: copy to slice buffer (memcpy path) or get dmabuf (zero-copy path) */ - if (input_is_dmabuf && self->priv->use_dmabuf_input) + if (input_is_dmabuf && self->priv->use_dmabuf_input && !self->priv->force_memcpy_input) { std::optional<Buffer> dmabuf_input = gst_to_libpisp_buffer(inbuf); if (!dmabuf_input) @@ -1105,8 +1240,8 @@ Buffer::Sync s(slice.at("pispbe-input"), Buffer::Sync::Access::ReadWrite); const auto &mem = s.Get(); copy_buffer_to_pisp(inbuf, const_cast<std::array<uint8_t *, 3> &>(mem), self->priv->in_width, - self->priv->in_height, self->priv->in_stride, self->priv->in_hw_stride, - self->priv->in_format); + self->priv->in_height, self->priv->in_meta_stride, self->priv->in_meta_offset, + self->priv->in_hw_stride, self->priv->in_format); GST_DEBUG_OBJECT(self, "Using memcpy input path"); } @@ -1135,8 +1270,8 @@ goto cleanup; } - add_video_meta(outbuf[i], self->priv->out_format[i], self->priv->out_width[i], self->priv->out_height[i], - self->priv->out_hw_stride[i]); + add_video_meta(outbuf[i], self->priv->out_format[i], self->priv->out_rb_swap[i], self->priv->out_width[i], + self->priv->out_height[i], self->priv->out_hw_stride[i]); GST_DEBUG_OBJECT(self, "Using zero-copy output%d path", i); } @@ -1279,6 +1414,8 @@ self->priv->media_dev_path = nullptr; self->priv->configured = FALSE; self->priv->use_dmabuf_input = FALSE; + self->priv->in_has_meta = FALSE; + self->priv->force_memcpy_input = FALSE; return TRUE; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/gst/gstpispconvert.h new/libpisp-1.7.0/src/gst/gstpispconvert.h --- old/libpisp-1.6.0/src/gst/gstpispconvert.h 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/gst/gstpispconvert.h 2026-08-07 11:06:54.000000000 +0200 @@ -7,6 +7,7 @@ #pragma once +#include <array> #include <gst/gst.h> #include <map> #include <memory> @@ -74,6 +75,13 @@ const char *in_format; const char *in_colorspace; + /* Input buffer layout from GstVideoMeta (falls back to caps-derived stride) */ + gboolean in_has_meta; + guint in_n_mem; + std::array<guint, 3> in_meta_stride; + std::array<gsize, 3> in_meta_offset; + gboolean force_memcpy_input; // Buffer layout unsuitable for zero-copy + /* Output format info - arrays for dual outputs */ guint out_width[PISP_NUM_OUTPUTS]; guint out_height[PISP_NUM_OUTPUTS]; @@ -81,6 +89,7 @@ guint out_hw_stride[PISP_NUM_OUTPUTS]; // Hardware buffer stride const char *out_format[PISP_NUM_OUTPUTS]; const char *out_colorspace[PISP_NUM_OUTPUTS]; + bool out_rb_swap[PISP_NUM_OUTPUTS]; // Memory stores B,G,R channel order gboolean output_enabled[PISP_NUM_OUTPUTS]; // Track which outputs are active /* dmabuf support */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/gst/meson.build new/libpisp-1.7.0/src/gst/meson.build --- old/libpisp-1.6.0/src/gst/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/gst/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2026, Raspberry Pi Ltd gst_dep = dependency('gstreamer-1.0', version : '>= 1.14', required : get_option('gstreamer')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/helpers/meson.build new/libpisp-1.7.0/src/helpers/meson.build --- old/libpisp-1.6.0/src/helpers/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/helpers/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2025, Raspberry Pi Ltd pisp_sources += files([ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/libpisp/backend/meson.build new/libpisp-1.7.0/src/libpisp/backend/meson.build --- old/libpisp-1.6.0/src/libpisp/backend/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/libpisp/backend/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd config_install_dir = get_option('prefix') / get_option('datadir') / 'libpisp' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/libpisp/backend/tiling/meson.build new/libpisp-1.7.0/src/libpisp/backend/tiling/meson.build --- old/libpisp-1.6.0/src/libpisp/backend/tiling/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/libpisp/backend/tiling/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd backend_sources += files([ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/libpisp/common/meson.build new/libpisp-1.7.0/src/libpisp/common/meson.build --- old/libpisp-1.6.0/src/libpisp/common/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/libpisp/common/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd pisp_sources += files([ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/libpisp/frontend/meson.build new/libpisp-1.7.0/src/libpisp/frontend/meson.build --- old/libpisp-1.6.0/src/libpisp/frontend/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/libpisp/frontend/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd frontend_sources = files([ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/libpisp/meson.build new/libpisp-1.7.0/src/libpisp/meson.build --- old/libpisp-1.6.0/src/libpisp/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/libpisp/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd subdir('common') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/libpisp/variants/meson.build new/libpisp-1.7.0/src/libpisp/variants/meson.build --- old/libpisp-1.6.0/src/libpisp/variants/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/libpisp/variants/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd pisp_sources += files([ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/src/meson.build new/libpisp-1.7.0/src/meson.build --- old/libpisp-1.6.0/src/meson.build 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/src/meson.build 2026-08-07 11:06:54.000000000 +0200 @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: CC0-1.0 +# SPDX-License-Identifier: BSD-2-Clause # Copyright (C) 2023, Raspberry Pi Ltd pisp_sources = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/libpisp-1.6.0/utils/test_convert.py new/libpisp-1.7.0/utils/test_convert.py --- old/libpisp-1.6.0/utils/test_convert.py 2026-06-29 18:07:15.000000000 +0200 +++ new/libpisp-1.7.0/utils/test_convert.py 2026-08-07 11:06:54.000000000 +0200 @@ -39,10 +39,10 @@ self.test_cases = [ { "input_file": "conv_yuv420_4056x3040_4056s.yuv", - "output_file": "out_4056x3050_12168s_rgb888.rgb", + "output_file": "out_4056x3040_12168s_rgb888.rgb", "input_format": "4056:3040:4056:YUV420P", "output_format": "4056:3040:12168:RGB888", - "reference_file": "ref_4056x3050_12168s_rgb888.rgb", + "reference_file": "ref_4056x3040_12168s_rgb888.rgb", "skip_gst": False, }, { @@ -61,6 +61,61 @@ "reference_file": "ref_4000x3000_4032s.yuv", "skip_gst": True, }, + # Strided inputs: the harness rewrites the input with padded rows, + # passing the strides to rawvideoparse (exercising pispconvert's + # GstVideoMeta stride handling) or to the convert utility via the + # format string. Convert mode is skipped automatically if the + # strides do not follow its luma/chroma derivation rules. + { + "input_file": "conv_yuv420_4056x3040_4056s.yuv", + "output_file": "out_4056x3040_strided_rgb888.rgb", + "input_format": "4056:3040:4056:YUV420P", + "output_format": "4056:3040:12168:RGB888", + "input_strides": [4160, 2080, 2080], + "reference_file": "ref_4056x3040_12168s_rgb888.rgb", + }, + { + "input_file": "conv_800x600_1200s_422_yuyv.yuv", + "output_file": "out_1600x1200_strided_422p.yuv", + "input_format": "800:600:1600:YUYV", + "output_format": "1600:1200:1600:YUV422P", + "input_strides": [1728], + "reference_file": "ref_1600x1200_1600_422p.yuv", + }, + # RGB input with its native padded stride (2432 vs 800*3=2400). + # The packed-stride output reference allows this to run in both + # modes, unlike the 4032-stride variant above. + { + "input_file": "conv_rgb888_800x600_2432s.rgb", + "output_file": "out_4000x3000_4000s.yuv", + "input_format": "800:600:2432:RGB888", + "output_format": "4000:3000:4000:YUV444P", + "input_strides": [2432], + "reference_file": "ref_4000x3000_4000s.yuv", + }, + # BGR output variants exercise the pispconvert R/B swap CSC. + # videoconvert repacks the file back to RGB so the RGB888 + # references can be reused; a wrong channel order in the element + # shows up as a swapped file. GStreamer only. + { + "input_file": "conv_yuv420_4056x3040_4056s.yuv", + "output_file": "out_4056x3040_bgr_rgb888.rgb", + "input_format": "4056:3040:4056:YUV420P", + "output_format": "4056:3040:12168:BGR", + "file_format": "RGB", + "reference_file": "ref_4056x3040_12168s_rgb888.rgb", + "skip_convert": True, + }, + { + "input_file": "conv_yuv420_4056x3040_4056s.yuv", + "output_file": "out_4056x3040_strided_bgr_rgb888.rgb", + "input_format": "4056:3040:4056:YUV420P", + "output_format": "4056:3040:12168:BGR", + "input_strides": [4160, 2080, 2080], + "file_format": "RGB", + "reference_file": "ref_4056x3040_12168s_rgb888.rgb", + "skip_convert": True, + }, # Add more test cases here as needed ] @@ -89,7 +144,64 @@ } return format_map.get(pisp_format, pisp_format) - def run_gstreamer(self, input_file, output_file, input_format, output_format): + def _plane_geometry(self, fmt): + """Per-plane (rows, row_bytes, stride) for a parsed format dict.""" + w, h, s = fmt["width"], fmt["height"], fmt["stride"] + name = fmt["format"] + if name in ("YUV420P", "YVU420P"): + return [(h, w, s), (h // 2, w // 2, s // 2), (h // 2, w // 2, s // 2)] + if name == "YUV422P": + return [(h, w, s), (h, w // 2, s // 2), (h, w // 2, s // 2)] + if name == "YUV444P": + return [(h, w, s), (h, w, s), (h, w, s)] + if name in ("YUYV", "UYVY"): + return [(h, w * 2, s)] + if name == "RGB888": + return [(h, w * 3, s)] + raise ValueError(f"Unsupported format for strided input: {name}") + + def _strided_input_path(self, input_file): + """Output-dir path for the strided rewrite of an input file.""" + base = os.path.basename(input_file).removeprefix("conv_") + return os.path.join(self.output_dir or ".", "conv_strided_" + base) + + def _make_strided_input(self, src_path, in_fmt, strides, dst_path): + """Rewrite src_path with the given per-plane strides, zero-padding each + row (including a final row the source file may have left unpadded). + Returns the plane offsets of the new file.""" + planes = self._plane_geometry(in_fmt) + if len(strides) != len(planes): + raise ValueError("gst_input_strides needs one stride per plane") + + with open(src_path, "rb") as f: + data = f.read() + + out = bytearray() + offsets = [] + pos = 0 + for (rows, row_bytes, src_stride), dst_stride in zip(planes, strides): + if dst_stride < row_bytes: + raise ValueError( + f"Stride {dst_stride} smaller than row size {row_bytes}" + ) + offsets.append(len(out)) + for _ in range(rows): + row = data[pos : pos + row_bytes] + out += row + b"\x00" * (dst_stride - len(row)) + pos += src_stride + with open(dst_path, "wb") as f: + f.write(out) + return offsets + + def run_gstreamer( + self, + input_file, + output_file, + input_format, + output_format, + input_strides=None, + file_format=None, + ): """Run GStreamer pipeline with pispconvert.""" # Use input directory if specified if self.input_dir: @@ -106,8 +218,20 @@ # Convert to GStreamer format names gst_in_format = self._pisp_to_gst_format(in_fmt["format"]) gst_out_format = self._pisp_to_gst_format(out_fmt["format"]) - # pispconvert swaps R/B for RGB, use BGR file output to match convert reference - gst_file_format = "BGR" if gst_out_format == "RGB" else gst_out_format + + # Rewrite the input with explicit strides and tell rawvideoparse about + # them, so pispconvert receives buffers with non-default GstVideoMeta + parse_props = [] + if input_strides: + strided_file = self._strided_input_path(input_file) + offsets = self._make_strided_input( + input_file, in_fmt, input_strides, strided_file + ) + input_file = strided_file + parse_props = [ + f"plane-strides=<{','.join(str(s) for s in input_strides)}>", + f"plane-offsets=<{','.join(str(o) for o in offsets)}>", + ] # Build GStreamer pipeline pipeline = [ @@ -120,16 +244,31 @@ f"height={in_fmt['height']}", f"format={gst_in_format.lower()}", "framerate=30/1", - "!", - "video/x-raw,colorimetry=1:4:0:0", + *parse_props, + ] + + # BT.601 colorimetry is only valid for YUV inputs; RGB keeps the + # rawvideoparse default (identity matrix) + if not in_fmt["format"].startswith("RGB"): + pipeline += ["!", "video/x-raw,colorimetry=1:4:0:0"] + + pipeline += [ "!", "pispconvert", "!", f"video/x-raw,format={gst_out_format},width={out_fmt['width']},height={out_fmt['height']},colorimetry=1:4:0:0", - "!", - "videoconvert", - "!", - f"video/x-raw,format={gst_file_format},width={out_fmt['width']},height={out_fmt['height']}", + ] + + # Repack to the requested file format so a common reference can be used + if file_format and file_format != gst_out_format: + pipeline += [ + "!", + "videoconvert", + "!", + f"video/x-raw,format={file_format},width={out_fmt['width']},height={out_fmt['height']}", + ] + + pipeline += [ "!", "filesink", f"location={output_file}", @@ -156,7 +295,9 @@ print(f"stderr: {e.stderr}") return False - def run_convert(self, input_file, output_file, input_format, output_format): + def run_convert( + self, input_file, output_file, input_format, output_format, input_strides=None + ): """Run the convert utility with the specified parameters.""" # Use input directory if specified if self.input_dir: @@ -166,6 +307,18 @@ if self.output_dir: output_file = os.path.join(self.output_dir, output_file) + # Rewrite the input with explicit strides and adjust the format + # string accordingly (the caller has checked expressibility) + if input_strides: + in_fmt = self._parse_format(input_format) + strided_file = self._strided_input_path(input_file) + self._make_strided_input(input_file, in_fmt, input_strides, strided_file) + input_file = strided_file + input_format = ( + f"{in_fmt['width']}:{in_fmt['height']}:" + f"{input_strides[0]}:{in_fmt['format']}" + ) + cmd = [ self.convert_binary, input_file, @@ -247,6 +400,23 @@ print("SKIPPED: Test case marked as skip_gst=True") return None # Return None to indicate skipped + # Skip convert test if marked to skip + if not self.use_gstreamer and test_case.get("skip_convert", False): + print("SKIPPED: Test case marked as skip_convert=True") + return None # Return None to indicate skipped + + input_strides = test_case.get("input_strides") + + # The convert utility derives chroma strides from the luma stride, so + # skip strides its format string cannot express + if not self.use_gstreamer and input_strides: + in_fmt = self._parse_format(test_case["input_format"]) + in_fmt["stride"] = input_strides[0] + expected = [plane[2] for plane in self._plane_geometry(in_fmt)] + if input_strides != expected: + print("SKIPPED: strides not expressible by the convert utility") + return None + # Run the convert utility or GStreamer pipeline if self.use_gstreamer: success = self.run_gstreamer( @@ -254,6 +424,8 @@ test_case["output_file"], test_case["input_format"], test_case["output_format"], + input_strides, + test_case.get("file_format"), ) else: success = self.run_convert( @@ -261,6 +433,7 @@ test_case["output_file"], test_case["input_format"], test_case["output_format"], + input_strides, ) if not success:
