Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qpipewire_spa_video_format_support.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtMultimedia/private/qcameradevice_p.h>
7#include <QtMultimedia/private/qmultimedia_ranges_p.h>
8#include <QtMultimedia/private/qpipewire_spa_pod_parser_support_p.h>
9#include <QtMultimedia/private/qpipewire_videoformat_support_p.h>
10
11#include <spa/pod/pod.h>
12
13#include <iterator>
14
15QT_BEGIN_NAMESPACE
16
17namespace QtPipeWire {
18
19namespace {
20
21namespace ranges = QtMultimediaPrivate::ranges;
22
23qreal fpsFromFraction(spa_fraction fraction)
24{
25 if (fraction.denom == 0)
26 return 0.;
27 return qreal(fraction.num) / qreal(fraction.denom);
28}
29
30QSize toSize(spa_rectangle rectangle)
31{
32 return QSize(int(rectangle.width), int(rectangle.height));
33}
34
35void appendUnique(std::vector<QSize> &list, QSize value)
36{
37 if (!value.isEmpty() && !ranges::contains(list, value))
38 list.push_back(value);
39}
40
41void appendUnique(std::vector<QVideoFrameFormat::PixelFormat> &list,
42 QVideoFrameFormat::PixelFormat value)
43{
44 if (value != QVideoFrameFormat::Format_Invalid && !ranges::contains(list, value))
45 list.push_back(value);
46}
47
48std::vector<QVideoFrameFormat::PixelFormat> parsePixelFormats(const spa_pod &pod)
49{
50 std::vector<QVideoFrameFormat::PixelFormat> result;
51
52 if (auto format = spaParsePodPropertyScalar<spa_video_format>(pod, SPA_TYPE_OBJECT_Format,
53 SPA_FORMAT_VIDEO_format)) {
54 const QVideoFrameFormat::PixelFormat pixelFormat = toQtPixelFormat(*format);
55 if (pixelFormat != QVideoFrameFormat::Format_Invalid)
56 result.push_back(pixelFormat);
57 return result;
58 }
59
60 if (auto choice = spaParsePodPropertyChoice<spa_video_format, SPA_CHOICE_Enum>(
61 pod, SPA_TYPE_OBJECT_Format, SPA_FORMAT_VIDEO_format)) {
62 appendUnique(result, toQtPixelFormat(choice->defaultValue()));
63 if (choice->size() > 1) {
64 for (spa_video_format format : choice->alternativeValues())
65 appendUnique(result, toQtPixelFormat(format));
66 }
67 }
68
69 return result;
70}
71
72SpaObjectVideoFormat::ResolutionSet parseResolutions(SpaEnum<spa_rectangle> values)
73{
74 std::vector<QSize> result;
75 appendUnique(result, toSize(values.defaultValue()));
76 if (values.size() > 1) {
77 for (spa_rectangle size : values.alternativeValues())
78 appendUnique(result, toSize(size));
79 }
80 return result;
81}
82SpaObjectVideoFormat::ResolutionSet parseResolutions(SpaRange<spa_rectangle> range)
83{
84 return SizeRange{
85 .preferred = toSize(range.defaultValue),
86 .min = toSize(range.minValue),
87 .max = toSize(range.maxValue),
88 };
89}
90SpaObjectVideoFormat::ResolutionSet parseResolutions(const spa_pod &pod)
91{
92 if (auto size = spaParsePodPropertyScalar<spa_rectangle>(pod, SPA_TYPE_OBJECT_Format,
93 SPA_FORMAT_VIDEO_size)) {
94 const QSize resolution = toSize(*size);
95 if (resolution.isEmpty())
96 return std::vector<QSize>{};
97 return std::vector{ resolution };
98 }
99
100 auto choice = spaParsePodPropertyChoice<spa_rectangle, SPA_CHOICE_Enum, SPA_CHOICE_Range>(
101 pod, SPA_TYPE_OBJECT_Format, SPA_FORMAT_VIDEO_size);
102 if (!choice)
103 return std::vector<QSize>{};
104
105 return std::visit([](const auto &arg) {
106 return parseResolutions(arg);
107 }, *choice);
108}
109
110// A SizeRange covers every size in between, but a QCameraFormat list has to
111// be finite: report the bounds and the preferred size only, like
112// QV4L2CameraDevices does for V4L2_FRMSIZE_TYPE_STEPWISE.
113std::vector<QSize> expandResolutions(const SizeRange &range)
114{
115 std::vector<QSize> result;
116 appendUnique(result, range.preferred);
117 appendUnique(result, range.min);
118 appendUnique(result, range.max);
119 return result;
120}
121std::vector<QSize> expandResolutions(const std::vector<QSize> &list)
122{
123 return list;
124}
125std::vector<QSize> expandResolutions(const SpaObjectVideoFormat::ResolutionSet &resolutions)
126{
127 return std::visit([](const auto &arg) {
128 return expandResolutions(arg);
129 }, resolutions);
130}
131
132bool isEmpty(const std::vector<QSize> &list)
133{
134 return list.empty();
135}
136bool isEmpty(const SizeRange &)
137{
138 return false; // a SizeRange always carries a preferred size and both bounds
139}
140bool isEmpty(const SpaObjectVideoFormat::ResolutionSet &resolutions)
141{
142 using namespace QtMultimediaPrivate;
143 return std::visit([](const auto &arg) {
144 return isEmpty(arg);
145 }, resolutions);
146}
147
148// Returns the inclusive frame rate bounds, both 0 if the pod does not describe
149// a frame rate at all.
150std::pair<qreal, qreal> parseFrameRateRange(const SpaEnum<spa_fraction> &values)
151{
152 std::vector<qreal> fpsValues{ fpsFromFraction(values.defaultValue()) };
153 if (values.size() > 1)
154 ranges::transform(values.alternativeValues(), std::back_inserter(fpsValues), fpsFromFraction);
155
156 return { ranges::min(fpsValues), ranges::max(fpsValues) };
157}
158std::pair<qreal, qreal> parseFrameRateRange(const SpaRange<spa_fraction> &range)
159{
160 return { fpsFromFraction(range.minValue), fpsFromFraction(range.maxValue) };
161}
162
163std::pair<qreal, qreal> parseFrameRateRange(const spa_pod &pod, unsigned property)
164{
165 if (auto rate =
166 spaParsePodPropertyScalar<spa_fraction>(pod, SPA_TYPE_OBJECT_Format, property)) {
167 const qreal fps = fpsFromFraction(*rate);
168 return { fps, fps };
169 }
170
171 auto choice = spaParsePodPropertyChoice<spa_fraction, SPA_CHOICE_Enum, SPA_CHOICE_Range>(
172 pod, SPA_TYPE_OBJECT_Format, property);
173 if (!choice)
174 return { 0., 0. };
175
176 return std::visit([](const auto &arg) {
177 return parseFrameRateRange(arg);
178 }, *choice);
179}
180
181} // namespace
182
184{
185 if (!pod)
186 return std::nullopt;
187
191 return std::nullopt;
192
195 if (!mediaSubtype)
196 return std::nullopt;
197
200 .pixelFormats = {},
202 .minFrameRate = 0.,
203 .maxFrameRate = 0.,
204 };
205
206 switch (*mediaSubtype) {
209 break;
211 // Compressed subtypes carry no SPA_FORMAT_VIDEO_format property; the
212 // pixel format is implied by the subtype.
214 break;
215 default:
216 // h264, dsp, and friends are not something a QCamera can consume.
217 return std::nullopt;
218 }
219
221 return std::nullopt;
222
225 return std::nullopt;
226
229
230 // Variable frame rate nodes report framerate 0/1 and put the real upper
231 // bound in maxFramerate instead.
232 if (result.maxFrameRate == 0.) {
235 }
236
237 return result;
238}
239
240QList<QCameraFormat> toCameraFormats(QSpan<const SpaObjectVideoFormat> formats)
241{
242 QList<QCameraFormat> result;
243
244 for (const SpaObjectVideoFormat &format : formats) {
245 for (QVideoFrameFormat::PixelFormat pixelFormat : format.pixelFormats) {
246 for (QSize resolution : expandResolutions(format.resolutions)) {
247 auto entry = std::make_unique<QCameraFormatPrivate>();
248 entry->pixelFormat = pixelFormat;
249 entry->resolution = resolution;
250 entry->minFrameRate = format.minFrameRate;
251 entry->maxFrameRate = format.maxFrameRate;
252
253 QCameraFormat cameraFormat = QCameraFormatPrivate::create(std::move(entry));
254 if (!result.contains(cameraFormat))
255 result.push_back(cameraFormat);
256 }
257 }
258 }
259
260 return result;
261}
262
263} // namespace QtPipeWire
264
265QT_END_NAMESPACE
QList< QCameraFormat > toCameraFormats(QSpan< const SpaObjectVideoFormat > formats)