6#include <QtMultimedia/private/qmemoryvideobuffer_p.h>
7#include <QtMultimedia/private/qpipewire_propertydict_p.h>
8#include <QtMultimedia/private/qpipewire_videoformat_support_p.h>
9#include <QtMultimedia/private/qvideoframe_p.h>
10#include <QtCore/qloggingcategory.h>
12#include <spa/param/video/format-utils.h>
13#include <spa/pod/builder.h>
16# include <spa/param/buffers.h>
18# include <QtMultimedia/private/qpipewire_spa_compat_p.h>
22# include <QtMultimedia/private/qmultimedia_dmabuf_support_p.h>
41constexpr std::array cameraPixelFormats{
42 SPA_VIDEO_FORMAT_NV12, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_UYVY,
43 SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_BGR,
44 SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_GRAY8,
47enum class CameraMemory : uint8_t { Auto, MemPtr, MemFd, DmaBuf };
49CameraMemory cameraMemoryFromEnvironment()
51 const QByteArray value = qgetenv(
"QT_PIPEWIRE_CAMERA_MEMORY");
52 if (value.isEmpty() || value ==
"auto")
53 return CameraMemory::Auto;
54 if (value ==
"memptr")
55 return CameraMemory::MemPtr;
57 return CameraMemory::MemFd;
58 if (value ==
"dmabuf") {
60 return CameraMemory::DmaBuf;
62 qCWarning(lcPipewireCameraStream)
63 <<
"QT_PIPEWIRE_CAMERA_MEMORY=dmabuf needs PipeWire 0.3.76 or newer, using auto";
64 return CameraMemory::Auto;
68 qCWarning(lcPipewireCameraStream)
69 <<
"unknown QT_PIPEWIRE_CAMERA_MEMORY value" << value <<
", using auto";
70 return CameraMemory::Auto;
74[[maybe_unused]] std::optional<spa_data_type> forcedDataType(CameraMemory memory)
77 case CameraMemory::Auto:
79 case CameraMemory::MemPtr:
80 return SPA_DATA_MemPtr;
81 case CameraMemory::MemFd:
82 return SPA_DATA_MemFd;
83 case CameraMemory::DmaBuf:
84 return SPA_DATA_DmaBuf;
86 Q_UNREACHABLE_RETURN(
std::nullopt);
91std::optional<QByteArray> copyFrameData(
const spa_buffer &buffer,
92 [[maybe_unused]]
bool dmaBufReadable)
94 const spa_data &data = buffer.datas[0];
97 if (data.type == SPA_DATA_DmaBuf) {
100 if (buffer.n_datas != 1 || !dmaBufReadable) {
101 qCDebug(lcPipewireCameraStream)
102 <<
"dropping DMABUF frame: needs one plane in a linear modifier";
106 if (!data.chunk || data.maxsize == 0)
108 const size_t chunkOffset = data.chunk->offset % data.maxsize;
109 if (chunkOffset + data.chunk->size > data.maxsize) {
110 qCDebug(lcPipewireCameraStream) <<
"dropping DMABUF frame: chunk exceeds the buffer";
113 return QtMultimediaPrivate::copyDmaBufData(
114 data.fd, off_t(data.mapoffset) + off_t(chunkOffset), data.chunk->size,
115 size_t(data.mapoffset) + data.maxsize);
119 if (!data.data || !data.chunk)
121 return QByteArray(
static_cast<
const char *>(data.data) + data.chunk->offset,
122 qsizetype(data.chunk->size));
180const spa_pod *buildFormatParam(
std::array<uint8_t, 1024> &storage,
181 QVideoFrameFormat::PixelFormat requestedPixelFormat,
182 QSize requestedSize, qreal requestedFrameRate,
183 [[maybe_unused]]
bool linearModifier)
185 spa_pod_builder builder = SPA_POD_BUILDER_INIT(storage.data(), uint32_t(storage.size()));
187 spa_pod_frame frame{};
188 spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
189 spa_pod_builder_add(&builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
191 switch (requestedPixelFormat) {
192 case QVideoFrameFormat::Format_Jpeg: {
193 spa_pod_builder_add(&builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg),
198 spa_pod_builder_add(&builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
201 const spa_video_format requestedPixelFormatSpa = toSpaVideoFormat(requestedPixelFormat);
202 switch (requestedPixelFormatSpa) {
203 case SPA_VIDEO_FORMAT_UNKNOWN: {
205 spa_pod_builder_prop(&builder, SPA_FORMAT_VIDEO_format, 0);
206 spa_pod_frame choice{};
207 spa_pod_builder_push_choice(&builder, &choice, SPA_CHOICE_Enum, 0);
209 spa_pod_builder_id(&builder, cameraPixelFormats.front());
210 for (spa_video_format format : cameraPixelFormats)
211 spa_pod_builder_id(&builder, format);
212 spa_pod_builder_pop(&builder, &choice);
217 spa_pod_builder_add(&builder, SPA_FORMAT_VIDEO_format,
218 SPA_POD_Id(requestedPixelFormatSpa), 0);
226 if (!requestedSize.isEmpty()) {
227 const spa_rectangle exactSize = toSpaRectangle(requestedSize);
228 spa_pod_builder_add(&builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&exactSize), 0);
230 constexpr spa_rectangle minSize{ .width = 1, .height = 1 };
231 constexpr spa_rectangle maxSize{ .width = 8192, .height = 8192 };
233 spa_pod_builder_add(&builder, SPA_FORMAT_VIDEO_size,
234 SPA_POD_CHOICE_RANGE_Rectangle(&maxSize, &minSize, &maxSize), 0);
239 constexpr spa_fraction minRate{ .num = 0, .denom = 1 };
240 const FrameRate rate = rateFromFps(requestedFrameRate > 0. ? requestedFrameRate : 30.);
241 spa_pod_builder_add(&builder, SPA_FORMAT_VIDEO_framerate,
242 SPA_POD_CHOICE_RANGE_Fraction(&rate.frac, &minRate, &rate.frac), 0);
245 if (linearModifier) {
246 spa_pod_frame modifierChoice{};
247 spa_pod_builder_prop(&builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
248 spa_pod_builder_push_choice(&builder, &modifierChoice, SPA_CHOICE_Enum, 0);
249 spa_pod_builder_long(&builder, int64_t(QtMultimediaPrivate::DrmFormatModifierLinear));
250 spa_pod_builder_pop(&builder, &modifierChoice);
254 return static_cast<
const spa_pod *>(spa_pod_builder_pop(&builder, &frame));
258const spa_pod *buildBuffersParam(
std::array<uint8_t, 1024> &storage, BufferDataTypes dataTypes)
260 spa_pod_builder builder = SPA_POD_BUILDER_INIT(storage.data(), uint32_t(storage.size()));
261 return static_cast<
const spa_pod *>(spa_pod_builder_add_object(
262 &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_dataType,
263 SPA_POD_CHOICE_FLAGS_Int(uint32_t(dataTypes.to_ulong()))));
266bool offersDmaBuf(CameraMemory memory)
269 && (memory == CameraMemory::Auto || memory == CameraMemory::DmaBuf);
272BufferDataTypes bufferDataTypes(CameraMemory memory)
274 BufferDataTypes dataTypes;
275 if (memory == CameraMemory::Auto || memory == CameraMemory::MemPtr)
276 dataTypes.set(SPA_DATA_MemPtr);
277 if (memory == CameraMemory::Auto || memory == CameraMemory::MemFd)
278 dataTypes.set(SPA_DATA_MemFd);
279 if (offersDmaBuf(memory))
280 dataTypes.set(SPA_DATA_DmaBuf);
376 : u"The PipeWire camera stream failed"_s);
417 <<
"modifier fixation requested; confirming linear support";
460 <<
"the server picked a pixel format we cannot represent, media subtype"
Q_STATIC_LOGGING_CATEGORY(lcPipewireAudioSink, "qt.multimedia.pipewire.audiosink")
#define QT_PIPEWIRE_HAS_DMABUF_CAMERA