Loading...
Searching...
No Matches
PipewireFormats.hpp
1#pragma once
2
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// Shared format-conversion utilities for the PipeWire video I/O
6// devices. Maps between:
7//
8// - SPA_VIDEO_FORMAT_* (pipewire wire format)
9// - AVPixelFormat (FFmpeg, used on the input AVFrame side)
10// - QRhiTexture::Format (used on the output rendering side)
11//
12// Header-only — the body is small enough that having two TUs include
13// it is cheaper than a separate .cpp. Both InputDevice and OutputDevice
14// pull it in.
15
16#include <Gfx/Graph/interop/DrmFourcc.hpp>
17
18#include <pipewire/pipewire.h>
19#include <spa/param/video/format-utils.h>
20
21extern "C" {
22#include <libavutil/pixfmt.h>
23}
24
25#include <private/qrhi_p.h>
26
27#include <QString>
28#include <QStringList>
29
30#include <cstdint>
31
32namespace Gfx::PipeWire::formats
33{
34
42enum class Tag : uint8_t
43{
44 RGBA8 = 0,
45 BGRA8,
46 RGB10A2,
47 BGR10A2,
48 RGBA16F,
49 RGBA32F,
50 P010,
51 P210,
52 YUV420P,
53 YV12,
54 NV12,
55 YUYV422,
56 UYVY422,
57 RGB24,
58 Unknown
59};
60
64inline QString tagToString(Tag t) noexcept
65{
66 switch(t)
67 {
68 case Tag::RGBA8: return QStringLiteral("rgba8");
69 case Tag::BGRA8: return QStringLiteral("bgra8");
70 case Tag::RGB10A2: return QStringLiteral("rgb10a2");
71 case Tag::BGR10A2: return QStringLiteral("bgr10a2");
72 case Tag::RGBA16F: return QStringLiteral("rgba16f");
73 case Tag::RGBA32F: return QStringLiteral("rgba32f");
74 case Tag::P010: return QStringLiteral("p010");
75 case Tag::P210: return QStringLiteral("p210");
76 case Tag::YUV420P: return QStringLiteral("yuv420p");
77 case Tag::YV12: return QStringLiteral("yv12");
78 case Tag::NV12: return QStringLiteral("nv12");
79 case Tag::YUYV422: return QStringLiteral("yuyv422");
80 case Tag::UYVY422: return QStringLiteral("uyvy422");
81 case Tag::RGB24: return QStringLiteral("rgb24");
82 case Tag::Unknown: break;
83 }
84 return QStringLiteral("rgba8");
85}
86
88inline QStringList allTagNames()
89{
90 QStringList r;
91 for(int i = 0; i < int(Tag::Unknown); i++)
92 r += tagToString(Tag(i));
93 return r;
94}
95
99inline QStringList renderableTagNames()
100{
101 QStringList r;
102 for(auto t : {Tag::RGBA8, Tag::BGRA8, Tag::RGB10A2, Tag::BGR10A2, Tag::RGBA16F,
103 Tag::RGBA32F})
104 r += tagToString(t);
105 return r;
106}
107
110inline Tag tagFromString(const QString& s)
111{
112 const QString k = s.toLower();
113 if(k == "rgba8" || k == "rgba")
114 return Tag::RGBA8;
115 if(k == "bgra8" || k == "bgra")
116 return Tag::BGRA8;
117 if(k == "rgb10a2")
118 return Tag::RGB10A2;
119 if(k == "bgr10a2")
120 return Tag::BGR10A2;
121 if(k == "rgba16f" || k == "rgba_f16")
122 return Tag::RGBA16F;
123 if(k == "rgba32f" || k == "rgba_f32")
124 return Tag::RGBA32F;
125 if(k == "p010")
126 return Tag::P010;
127 if(k == "p210")
128 return Tag::P210;
129 if(k == "yuv420p" || k == "i420")
130 return Tag::YUV420P;
131 if(k == "yv12")
132 return Tag::YV12;
133 if(k == "nv12")
134 return Tag::NV12;
135 if(k == "yuyv422" || k == "yuy2")
136 return Tag::YUYV422;
137 if(k == "uyvy422" || k == "uyvy")
138 return Tag::UYVY422;
139 if(k == "rgb24" || k == "rgb")
140 return Tag::RGB24;
141 return Tag::Unknown;
142}
143
145inline spa_video_format toSpa(Tag t)
146{
147 switch(t)
148 {
149 case Tag::RGBA8: return SPA_VIDEO_FORMAT_RGBA;
150 case Tag::BGRA8: return SPA_VIDEO_FORMAT_BGRA;
151 case Tag::RGB10A2: return SPA_VIDEO_FORMAT_xRGB_210LE;
152 case Tag::BGR10A2: return SPA_VIDEO_FORMAT_xBGR_210LE;
153 case Tag::RGBA16F: return SPA_VIDEO_FORMAT_RGBA_F16;
154 case Tag::RGBA32F: return SPA_VIDEO_FORMAT_RGBA_F32;
155 case Tag::P010: return SPA_VIDEO_FORMAT_P010_10LE;
156 case Tag::P210: return SPA_VIDEO_FORMAT_P010_10LE; // pipewire doesn't have a P210 enum
157 case Tag::YUV420P: return SPA_VIDEO_FORMAT_I420;
158 case Tag::YV12: return SPA_VIDEO_FORMAT_YV12;
159 case Tag::NV12: return SPA_VIDEO_FORMAT_NV12;
160 case Tag::YUYV422: return SPA_VIDEO_FORMAT_YUY2;
161 case Tag::UYVY422: return SPA_VIDEO_FORMAT_UYVY;
162 case Tag::RGB24: return SPA_VIDEO_FORMAT_RGB;
163 case Tag::Unknown: break;
164 }
165 return SPA_VIDEO_FORMAT_RGBA;
166}
167
170inline Tag tagFromSpa(uint32_t fmt)
171{
172 switch(fmt)
173 {
174 case SPA_VIDEO_FORMAT_RGBA: return Tag::RGBA8;
175 case SPA_VIDEO_FORMAT_BGRA: return Tag::BGRA8;
176 case SPA_VIDEO_FORMAT_xRGB_210LE: return Tag::RGB10A2;
177 case SPA_VIDEO_FORMAT_xBGR_210LE: return Tag::BGR10A2;
178 case SPA_VIDEO_FORMAT_RGBA_F16: return Tag::RGBA16F;
179 case SPA_VIDEO_FORMAT_RGBA_F32: return Tag::RGBA32F;
180 case SPA_VIDEO_FORMAT_P010_10LE: return Tag::P010;
181 case SPA_VIDEO_FORMAT_YV12: return Tag::YV12;
182 case SPA_VIDEO_FORMAT_I420: return Tag::YUV420P;
183 case SPA_VIDEO_FORMAT_NV12: return Tag::NV12;
184 case SPA_VIDEO_FORMAT_YUY2: return Tag::YUYV422;
185 case SPA_VIDEO_FORMAT_UYVY: return Tag::UYVY422;
186 case SPA_VIDEO_FORMAT_RGB: return Tag::RGB24;
187 default: return Tag::Unknown;
188 }
189}
190
194inline AVPixelFormat toAvPixFmt(Tag t)
195{
196 switch(t)
197 {
198 case Tag::RGBA8: return AV_PIX_FMT_RGBA;
199 case Tag::BGRA8: return AV_PIX_FMT_BGRA;
200 case Tag::RGB10A2: return AV_PIX_FMT_X2RGB10LE;
201 case Tag::BGR10A2: return AV_PIX_FMT_X2BGR10LE;
202 case Tag::RGBA16F:
203 // FFmpeg 6.0+ has AV_PIX_FMT_RGBAF16; older libs fall back to
204 // AV_PIX_FMT_RGBA64LE which is 16-bit integer per channel.
205 // RGBA64LE is the more universally supported choice.
206 return AV_PIX_FMT_RGBA64LE;
207 case Tag::RGBA32F: return AV_PIX_FMT_NONE; // not a standard AVFrame format
208 case Tag::P010: return AV_PIX_FMT_P010LE;
209 case Tag::P210: return AV_PIX_FMT_P210LE;
210 case Tag::YUV420P: return AV_PIX_FMT_YUV420P;
211 // YV12 is published as YUV420P: the copy path swaps the U/V plane
212 // order so the AVFrame is always I420-ordered.
213 case Tag::YV12: return AV_PIX_FMT_YUV420P;
214 case Tag::NV12: return AV_PIX_FMT_NV12;
215 case Tag::YUYV422: return AV_PIX_FMT_YUYV422;
216 case Tag::UYVY422: return AV_PIX_FMT_UYVY422;
217 case Tag::RGB24: return AV_PIX_FMT_RGB24;
218 case Tag::Unknown: break;
219 }
220 return AV_PIX_FMT_NONE;
221}
222
223inline Tag tagFromAvPixFmt(AVPixelFormat fmt)
224{
225 switch(fmt)
226 {
227 case AV_PIX_FMT_RGBA: return Tag::RGBA8;
228 case AV_PIX_FMT_BGRA: return Tag::BGRA8;
229 case AV_PIX_FMT_X2RGB10LE: return Tag::RGB10A2;
230 case AV_PIX_FMT_X2BGR10LE: return Tag::BGR10A2;
231 case AV_PIX_FMT_RGBA64LE: return Tag::RGBA16F;
232 case AV_PIX_FMT_P010LE: return Tag::P010;
233 case AV_PIX_FMT_P210LE: return Tag::P210;
234 case AV_PIX_FMT_YUV420P: return Tag::YUV420P;
235 case AV_PIX_FMT_NV12: return Tag::NV12;
236 case AV_PIX_FMT_YUYV422: return Tag::YUYV422;
237 case AV_PIX_FMT_UYVY422: return Tag::UYVY422;
238 case AV_PIX_FMT_RGB24: return Tag::RGB24;
239 default: return Tag::Unknown;
240 }
241}
242
245inline QRhiTexture::Format toQRhi(Tag t)
246{
247 switch(t)
248 {
249 case Tag::RGBA8: return QRhiTexture::RGBA8;
250 case Tag::BGRA8: return QRhiTexture::BGRA8;
251 case Tag::RGB10A2:
252 case Tag::BGR10A2: return QRhiTexture::RGB10A2;
253 case Tag::RGBA16F: return QRhiTexture::RGBA16F;
254 case Tag::RGBA32F: return QRhiTexture::RGBA32F;
255 // Planar / packed YUV formats: QRhi has no native planar storage.
256 // Output side cannot produce these directly; the renderer would
257 // need a CPU pack pass between readback and chunk publish.
258 default: return QRhiTexture::RGBA8;
259 }
260}
261
265inline uint32_t bytesPerPixel(Tag t)
266{
267 switch(t)
268 {
269 case Tag::RGBA8:
270 case Tag::BGRA8:
271 case Tag::RGB10A2:
272 case Tag::BGR10A2: return 4;
273 case Tag::YUYV422: // 4:2:2 packed: 2 bytes per pixel (Y always,
274 case Tag::UYVY422: return 2; // U/V shared across the pair)
275 case Tag::RGBA16F: return 8;
276 case Tag::RGBA32F: return 16;
277 case Tag::P010:
278 case Tag::P210: return 2; // 16-bit lane per Y sample
279 case Tag::YUV420P:
280 case Tag::YV12:
281 case Tag::NV12: return 1; // Y plane byte
282 case Tag::RGB24: return 3;
283 case Tag::Unknown: break;
284 }
285 return 4;
286}
287
292inline uint64_t frameBytes(Tag t, int w, int h) noexcept
293{
294 const uint64_t wh = uint64_t(w) * uint64_t(h);
295 // A subsampled plane rounds up: 641 luma columns carry 321 chroma columns.
296 const uint64_t cw = uint64_t(w + 1) / 2;
297 const uint64_t ch = uint64_t(h + 1) / 2;
298 switch(t)
299 {
300 case Tag::NV12:
301 case Tag::YV12:
302 case Tag::YUV420P: return wh + 2 * cw * ch; // 8-bit 4:2:0
303 case Tag::P010: return 2 * (wh + 2 * cw * ch); // 16-bit, 4:2:0
304 case Tag::P210: return 2 * (wh + 2 * cw * uint64_t(h)); // 16-bit, 4:2:2
305 case Tag::YUYV422:
306 case Tag::UYVY422: return 4 * cw * uint64_t(h); // packed 4:2:2
307 default: return wh * bytesPerPixel(t);
308 }
309}
310
315inline bool isPlanar(Tag t) noexcept
316{
317 switch(t)
318 {
319 case Tag::YUV420P:
320 case Tag::YV12:
321 case Tag::NV12:
322 case Tag::P010:
323 case Tag::P210:
324 return true;
325 default:
326 return false;
327 }
328}
329
336inline uint32_t toDrmFourcc(Tag t) noexcept
337{
338 // P210 has no SPA enum (toSpa aliases it to P010), so map it directly.
339 if(t == Tag::P210)
340 return score::gfx::interop::DRM_P210;
341 return score::gfx::interop::spaToDrmFourcc(toSpa(t));
342}
343
344} // namespace Gfx::PipeWire::formats