127 const spa_pod *param)
129 auto *self =
static_cast<
Private *>(userData);
130 if (!param || id != SPA_PARAM_Format)
133 spa_video_info info = {};
134 if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0)
136 if (info.media_type != SPA_MEDIA_TYPE_video || info.media_subtype != SPA_MEDIA_SUBTYPE_raw)
138 if (spa_format_video_raw_parse(param, &info.info.raw) < 0)
141 const int stride = strideForWidth(
int(info.info.raw.size.width));
142 const int size = stride *
int(info.info.raw.size.height);
144 std::array<uint8_t, 1024> buffer;
145 spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
146 const spa_pod *params[1];
147 params[0] =
static_cast<
const spa_pod *>(spa_pod_builder_add_object(
148 &builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers,
149 SPA_POD_CHOICE_RANGE_Int(4, 2, 8), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
150 SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride,
151 SPA_POD_Int(stride), SPA_PARAM_BUFFERS_dataType,
152 SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr))));
154 pw_stream_update_params(self->stream.get(), params, 1);
159 auto *self =
static_cast<
Private *>(userData);
161 pw_buffer *pwBuffer = pw_stream_dequeue_buffer(self->stream.get());
165 spa_buffer *buffer = pwBuffer->buffer;
166 auto *destination =
static_cast<uint8_t *>(buffer->datas[0].data);
168 pw_stream_queue_buffer(self->stream.get(), pwBuffer);
175 QMutexLocker locker(&self->sceneMutex);
176 if (self->frame.isNull()) {
177 pw_stream_queue_buffer(self->stream.get(), pwBuffer);
180 height = self->frame.height();
181 stride = strideForWidth(self->frame.width());
183 const qsizetype copyBytes = std::min<qsizetype>(stride, self->frame.bytesPerLine());
184 for (
int y = 0; y < height; ++y)
185 memcpy(destination + qsizetype(y) * stride, self->frame.constScanLine(y), copyBytes);
188 buffer->datas[0].chunk->offset = 0;
189 buffer->datas[0].chunk->stride = stride;
190 buffer->datas[0].chunk->size = uint32_t(stride * height);
192 pw_stream_queue_buffer(self->stream.get(), pwBuffer);
197 if (outputPort.value == SPA_ID_INVALID)
200 const QByteArray out = QByteArray::number(outputPort.value);
201 const QByteArray in = QByteArray::number(inputPort.value);
203 QtPipeWire::PwPropertiesHandle props{
204 pw_properties_new(PW_KEY_LINK_OUTPUT_PORT, out.constData(), PW_KEY_LINK_INPUT_PORT,
205 in.constData(), PW_KEY_OBJECT_LINGER,
"false",
nullptr),
208 QtPipeWire::PwProxyHandle link{
209 static_cast<pw_proxy *>(pw_core_create_object(core.get(),
"link-factory",
210 PW_TYPE_INTERFACE_Link, PW_VERSION_LINK,
215 qWarning() <<
"QSyntheticPipeWireVideoSource: failed to link port" << outputPort.value
216 <<
"->" << inputPort.value;
219 links.push_back(std::move(link));
244 const char *type, uint32_t,
245 const spa_dict *props)
247 auto *self =
static_cast<
Private *>(userData);
251 if (std::string_view(type) == PW_TYPE_INTERFACE_Node) {
252 if (ObjectId{ id } == self->nodeId)
255 const char *role = spa_dict_lookup(props, PW_KEY_MEDIA_ROLE);
256 self->consumerRoles.insert(id, role ? QByteArray(role) : QByteArray());
259 for (ObjectId port : std::exchange(self->pendingInputPorts[id], {}))
260 self->considerPort(port, id);
261 self->pendingInputPorts.erase(id);
265 if (std::string_view(type) != PW_TYPE_INTERFACE_Port)
268 const char *direction = spa_dict_lookup(props, PW_KEY_PORT_DIRECTION);
269 const char *portNodeId = spa_dict_lookup(props, PW_KEY_NODE_ID);
270 if (!direction || !portNodeId)
273 const ObjectId portId{ id };
274 const ObjectId nodeOfPort{ uint32_t(QByteArray(portNodeId).toUInt()) };
276 if (std::string_view(direction) ==
"out" && nodeOfPort == self->nodeId) {
277 self->outputPort = portId;
279 for (
const auto &[node, ports] : std::exchange(self->pendingInputPorts, {})) {
280 for (ObjectId port : ports)
281 self->considerPort(port, node);
286 if (std::string_view(direction) ==
"in" && nodeOfPort != self->nodeId) {
288 if (self->outputPort.value == SPA_ID_INVALID
289 || !self->consumerRoles.contains(nodeOfPort.value)) {
290 self->pendingInputPorts[nodeOfPort.value].push_back(portId);
293 self->considerPort(portId, nodeOfPort.value);
308 const QSyntheticPipeWireVideoSourceConfig &config)
310 using namespace QtPipeWire;
312 return q23::unexpected(u"already started"_s);
317 pw_init(
nullptr,
nullptr);
319 d->threadLoop.emplace(config.loopName.constData());
321 return q23::unexpected(u"pw_thread_loop_new() failed"_s);
323 if (d->threadLoop->start() != 0)
324 return q23::unexpected(u"pw_thread_loop_start() failed"_s);
326 std::lock_guard threadLoopLock{ *d->threadLoop };
327 return [&]() -> q23::expected<
void, QString> {
328 d->context = PwContextHandle{
329 pw_context_new(d->threadLoop->loop(),
nullptr, 0),
332 return q23::unexpected(u"pw_context_new() failed"_s);
334 d->core = PwCoreConnectionHandle{
335 pw_context_connect(d->context.get(),
nullptr, 0),
338 return q23::unexpected(u"pw_context_connect() failed: "_s
339 + QString::fromLocal8Bit(strerror(errno)));
342 static constexpr pw_registry_events registryEvents = {
343 .version = PW_VERSION_REGISTRY_EVENTS,
344 .global = &Private::onRegistryGlobal,
345 .global_remove =
nullptr,
347 d->registry = PwRegistryHandle{
348 pw_core_get_registry(d->core.get(), PW_VERSION_REGISTRY, 0),
350 pw_registry_add_listener(d->registry.get(), &d->registryListener, ®istryEvents, d.get());
354 d->stream = PwStreamHandle{
355 pw_stream_new(d->core.get(), config.nodeName.constData(),
356 pw_properties_new(PW_KEY_MEDIA_CLASS, config.mediaClass.constData(),
357 PW_KEY_MEDIA_TYPE,
"Video", PW_KEY_MEDIA_CATEGORY,
358 config.mediaCategory.constData(), PW_KEY_MEDIA_ROLE,
359 config.mediaRole.constData(), PW_KEY_NODE_NAME,
360 config.nodeName.constData(), PW_KEY_NODE_DESCRIPTION,
361 config.nodeDescription.constData(),
nullptr)),
364 return q23::unexpected(u"pw_stream_new() failed"_s);
366 static constexpr pw_stream_events streamEvents = {
367 .version = PW_VERSION_STREAM_EVENTS,
369 .state_changed = &Private::onStateChanged,
370 .control_info =
nullptr,
371 .io_changed =
nullptr,
372 .param_changed = &Private::onParamChanged,
373 .add_buffer =
nullptr,
374 .remove_buffer =
nullptr,
375 .process = &Private::onProcess,
378 .trigger_done =
nullptr,
380 pw_stream_add_listener(d->stream.get(), &d->streamListener, &streamEvents, d.get());
382 std::array<uint8_t, 1024> buffer;
383 spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
384 spa_rectangle size = SPA_RECTANGLE(uint32_t(scene.frameSize.width()),
385 uint32_t(scene.frameSize.height()));
386 spa_fraction frameRate = FrameRate;
387 const spa_pod *params[1];
388 params[0] =
static_cast<
const spa_pod *>(spa_pod_builder_add_object(
389 &builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType,
390 SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype,
391 SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format,
392 SPA_POD_Id(SPA_VIDEO_FORMAT_BGRx), SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size),
393 SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&frameRate)));
397 const int error = pw_stream_connect(
398 d->stream.get(), PW_DIRECTION_OUTPUT, PW_ID_ANY,
399 pw_stream_flags(PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_DRIVER), params, 1);
401 return q23::unexpected(u"pw_stream_connect() failed: "_s
402 + QString::fromLocal8Bit(spa_strerror(error)));
406 while (d->nodeId.value == SPA_ID_INVALID) {
407 if (d->threadLoop->wait_for(NodeIdTimeout) != 0)
408 return q23::unexpected(u"timed out waiting for the pipewire node id"_s);