Skip to content

Commit 181b012

Browse files
jcelerierclaude
andcommitted
gfx: add the GStreamer name edge for the shared-memory transports
Sh4lt and Shmdata hand over shared-memory *buffers*, not streams, and resolve their caps through Video::gstreamerToLibav() to an AVPixelFormat -- after which a format the decode path does not take natively is CPU-converted to RGBA. Removing that conversion needs the layout and a stride, which is what a VideoPixelFormat is, so they need an edge into the vocabulary. The rest of the 54-entry gst map stays AV-first, as agreed: most of what arrives over GStreamer is a stream format and the decode pipeline is AV-based. This table covers the buffer layouts only. It is a direct table rather than the existing map composed with the libav bridge, and that is the point. Routing through AVPixelFormat loses the V-before-U layouts, because FFmpeg has no pixel format for them: "YV12" would arrive as YUV420P with red and blue exchanged, and "YVU9" likewise. The suite asserts that YV12 and I420 do not collapse onto each other and that the swap is recorded. This is the mapping, not the rewiring. Pointing Sh4lt and Shmdata at host-import instead of the rescaler is the follow-up, and it wants those transports actually running to validate, which needs a producer this machine does not have. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
1 parent 94f58a1 commit 181b012

4 files changed

Lines changed: 166 additions & 0 deletions

File tree

src/plugins/score-plugin-gfx/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,11 +231,13 @@ set(HDRS
231231
Gfx/Graph/interop/DirectShowPixelFormat.hpp
232232
Gfx/Graph/interop/DrmPixelFormat.hpp
233233
Gfx/Graph/interop/VideoPixelFormatQRhi.hpp
234+
Gfx/Graph/interop/GStreamerPixelFormat.hpp
234235
Gfx/Graph/interop/VideoPixelFormat.cpp
235236
Gfx/Graph/interop/V4L2PixelFormat.cpp
236237
Gfx/Graph/interop/DirectShowPixelFormat.cpp
237238
Gfx/Graph/interop/DrmPixelFormat.cpp
238239
Gfx/Graph/interop/VideoPixelFormatQRhi.cpp
240+
Gfx/Graph/interop/GStreamerPixelFormat.cpp
239241

240242
Gfx/Graph/decoders/GPUVideoDecoder.hpp
241243
Gfx/Graph/decoders/GPUVideoDecoderFactory.hpp
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <Gfx/Graph/interop/GStreamerPixelFormat.hpp>
2+
3+
#include <algorithm>
4+
#include <array>
5+
#include <utility>
6+
7+
namespace score::gfx::interop
8+
{
9+
namespace
10+
{
11+
using Row = std::pair<std::string_view, VideoPixelFormat>;
12+
using V = VideoPixelFormat;
13+
14+
// GStreamer names the memory byte order directly, so these read literally: its
15+
// "RGBA" is R,G,B,A in memory. The V-before-U spellings are distinct layouts, not
16+
// their U-first twins with a flag.
17+
constexpr std::array kRows{
18+
// packed 8-bit RGB
19+
Row{"RGBA", V::RGBA8}, Row{"BGRA", V::BGRA8},
20+
Row{"ARGB", V::ARGB8}, Row{"ABGR", V::ABGR8},
21+
Row{"RGBx", V::RGBX8}, Row{"BGRx", V::BGRX8},
22+
Row{"xRGB", V::XRGB8}, Row{"xBGR", V::XBGR8},
23+
Row{"RGB", V::RGB24}, Row{"BGR", V::BGR24},
24+
Row{"RGB16", V::RGB565}, Row{"RGB15", V::RGB555},
25+
// packed 10-bit RGB
26+
Row{"r210", V::R210},
27+
// packed YUV
28+
Row{"YUY2", V::YUYV422}, Row{"YVYU", V::YVYU422},
29+
Row{"UYVY", V::UYVY422}, Row{"VYUY", V::VYUY422},
30+
Row{"AYUV", V::AYUV}, Row{"VUYA", V::VUYA},
31+
Row{"v210", V::V210}, Row{"v216", V::V216},
32+
Row{"Y210", V::Y210}, Row{"Y410", V::XV30},
33+
Row{"IYU1", V::UYYVYY411},
34+
// planar, U before V
35+
Row{"I420", V::YUV420P}, Row{"Y42B", V::YUV422P},
36+
Row{"Y444", V::YUV444P}, Row{"Y41B", V::YUV411P},
37+
Row{"YUV9", V::YUV410P},
38+
// planar, V before U
39+
Row{"YV12", V::YVU420P}, Row{"YVU9", V::YVU410P},
40+
// planar high bit depth
41+
Row{"I420_10LE", V::YUV420P10}, Row{"I422_10LE", V::YUV422P10},
42+
Row{"Y444_10LE", V::YUV444P10}, Row{"I422_12LE", V::YUV422P12},
43+
Row{"Y444_12LE", V::YUV444P12}, Row{"I422_16LE", V::YUV422P16},
44+
Row{"A444", V::YUVA444P},
45+
// semi-planar
46+
Row{"NV12", V::NV12}, Row{"NV21", V::NV21},
47+
Row{"NV16", V::NV16}, Row{"NV61", V::NV61},
48+
Row{"NV24", V::NV24},
49+
Row{"P010_10LE", V::P010}, Row{"P016_LE", V::P216},
50+
// greyscale
51+
Row{"GRAY8", V::Mono8},
52+
Row{"GRAY16_LE", V::Mono16}, Row{"GRAY16_BE", V::Mono16BE},
53+
};
54+
} // namespace
55+
56+
VideoPixelFormat fromGStreamerFormat(std::string_view name) noexcept
57+
{
58+
const auto it = std::find_if(
59+
kRows.begin(), kRows.end(), [name](const Row& r) { return r.first == name; });
60+
return it != kRows.end() ? it->second : V::Unknown;
61+
}
62+
63+
std::string_view toGStreamerFormat(VideoPixelFormat f) noexcept
64+
{
65+
const auto it = std::find_if(
66+
kRows.begin(), kRows.end(), [f](const Row& r) { return r.second == f; });
67+
return it != kRows.end() ? it->first : std::string_view{};
68+
}
69+
70+
} // namespace score::gfx::interop
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#pragma once
2+
3+
/**
4+
* @file GStreamerPixelFormat.hpp
5+
* @brief GStreamer video-format name <-> VideoPixelFormat.
6+
*
7+
* GStreamer names formats with strings in caps ("NV12", "I420", "v210"), and
8+
* `Video::gstreamerToLibav()` already maps those to AVPixelFormat for the decode
9+
* path. That mapping stays: most of what arrives over GStreamer is a *stream*
10+
* format, and the decode pipeline is AV-based.
11+
*
12+
* This table exists for the transports that hand over a *buffer* -- Sh4lt and
13+
* Shmdata pass shared memory -- where the layout is what matters, because a
14+
* layout plus a stride is what lets a frame be imported straight onto the GPU
15+
* instead of being converted on the CPU first.
16+
*
17+
* It is a direct table rather than a composition of the existing map with the
18+
* libav bridge, and that is the point: going through AVPixelFormat loses the
19+
* V-before-U layouts, since FFmpeg has no pixel format for them. "YV12" would
20+
* arrive as YUV420P with red and blue exchanged.
21+
*/
22+
23+
#include <Gfx/Graph/interop/VideoPixelFormat.hpp>
24+
25+
#include <score_plugin_gfx_export.h>
26+
27+
#include <string_view>
28+
29+
namespace score::gfx::interop
30+
{
31+
32+
/// The layout behind a GStreamer format name, or Unknown when score has no row
33+
/// for it -- which includes the formats that are streams rather than buffers.
34+
SCORE_PLUGIN_GFX_EXPORT
35+
VideoPixelFormat fromGStreamerFormat(std::string_view name) noexcept;
36+
37+
/// The GStreamer name for a layout, or an empty view when it has none.
38+
SCORE_PLUGIN_GFX_EXPORT
39+
std::string_view toGStreamerFormat(VideoPixelFormat f) noexcept;
40+
41+
} // namespace score::gfx::interop

tests/unit/VideoPixelFormatTest.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <Gfx/Graph/interop/VideoPixelFormat.hpp>
2626
#include <Gfx/Graph/interop/DirectShowPixelFormat.hpp>
2727
#include <Gfx/Graph/interop/DrmPixelFormat.hpp>
28+
#include <Gfx/Graph/interop/GStreamerPixelFormat.hpp>
2829
#include <Gfx/Graph/interop/VideoPixelFormatQRhi.hpp>
2930
#include <Gfx/Graph/interop/VideoPixelFormatAV.hpp>
3031
#if defined(__linux__)
@@ -1214,3 +1215,55 @@ TEST_CASE("texture formats map back only where unambiguous", "[gfx][pixfmt][qrhi
12141215
CHECK(vpf::fromTextureFormat(vpf::planeTextureFormat(f, 0)) == f);
12151216
}
12161217
}
1218+
1219+
// GStreamer names, for the transports that hand over a buffer rather than a
1220+
// stream. Composing the existing gst->AV map with the libav bridge would lose the
1221+
// V-before-U layouts, so this asserts the direct table gets them right -- that is
1222+
// the whole reason it exists.
1223+
TEST_CASE("GStreamer names resolve to the right layout", "[gfx][pixfmt][gst]")
1224+
{
1225+
struct Pin { const char* name; V expect; };
1226+
static const Pin kPins[] = {
1227+
{"RGBA", V::RGBA8}, {"BGRA", V::BGRA8}, {"ARGB", V::ARGB8}, {"ABGR", V::ABGR8},
1228+
{"RGBx", V::RGBX8}, {"BGRx", V::BGRX8}, {"xRGB", V::XRGB8}, {"xBGR", V::XBGR8},
1229+
{"RGB", V::RGB24}, {"BGR", V::BGR24},
1230+
{"YUY2", V::YUYV422}, {"UYVY", V::UYVY422}, {"YVYU", V::YVYU422},
1231+
{"v210", V::V210}, {"v216", V::V216}, {"r210", V::R210},
1232+
{"I420", V::YUV420P}, {"Y42B", V::YUV422P}, {"Y444", V::YUV444P},
1233+
{"Y41B", V::YUV411P}, {"YUV9", V::YUV410P},
1234+
{"NV12", V::NV12}, {"NV21", V::NV21}, {"NV16", V::NV16},
1235+
{"NV61", V::NV61}, {"NV24", V::NV24},
1236+
{"I420_10LE", V::YUV420P10}, {"I422_10LE", V::YUV422P10},
1237+
{"P010_10LE", V::P010}, {"A444", V::YUVA444P},
1238+
{"GRAY8", V::Mono8}, {"GRAY16_LE", V::Mono16}, {"GRAY16_BE", V::Mono16BE},
1239+
};
1240+
for(const auto& p : kPins)
1241+
{
1242+
INFO("gst format " << p.name);
1243+
CHECK(vpf::fromGStreamerFormat(p.name) == p.expect);
1244+
CHECK(vpf::toGStreamerFormat(p.expect) == std::string_view{p.name});
1245+
}
1246+
// The point of a direct table: these must NOT collapse onto their U-first
1247+
// twins, which is what routing through AVPixelFormat would have done.
1248+
CHECK(vpf::fromGStreamerFormat("YV12") == V::YVU420P);
1249+
CHECK(vpf::fromGStreamerFormat("YVU9") == V::YVU410P);
1250+
CHECK(vpf::fromGStreamerFormat("YV12") != vpf::fromGStreamerFormat("I420"));
1251+
CHECK(vpf::chromaSwappedTwin(vpf::fromGStreamerFormat("YV12"))
1252+
== vpf::fromGStreamerFormat("I420"));
1253+
CHECK(vpf::chromaSwappedTwin(vpf::fromGStreamerFormat("NV21"))
1254+
== vpf::fromGStreamerFormat("NV12"));
1255+
// Case matters in caps, and unknown names must not guess.
1256+
CHECK(vpf::fromGStreamerFormat("nv12") == V::Unknown);
1257+
CHECK(vpf::fromGStreamerFormat("") == V::Unknown);
1258+
CHECK(vpf::fromGStreamerFormat("ENCODED") == V::Unknown);
1259+
CHECK(vpf::toGStreamerFormat(V::Unknown).empty());
1260+
// A name maps to one layout and back, for every row.
1261+
for(const auto* i : described())
1262+
{
1263+
const auto name = vpf::toGStreamerFormat(i->format);
1264+
if(name.empty())
1265+
continue;
1266+
INFO(i->name << " <-> " << name);
1267+
CHECK(vpf::fromGStreamerFormat(name) == i->format);
1268+
}
1269+
}

0 commit comments

Comments
 (0)