101 loop(pw_thread_loop_new(
"Pipewire thread",
nullptr)) {
102 BOOST_LOG(debug) <<
"[pipewire] Start PW thread loop"sv;
103 pw_thread_loop_start(loop);
107 BOOST_LOG(debug) <<
"[pipewire] Destroying pipewire_t"sv;
109 BOOST_LOG(debug) <<
"[pipewire] Stop PW thread loop"sv;
110 pw_thread_loop_stop(loop);
114 }
catch (
const std::exception &e) {
115 BOOST_LOG(error) <<
"[pipewire] Standard exception caught in ~pipewire_t: "sv << e.what();
117 BOOST_LOG(error) <<
"[pipewire] Unknown exception caught in ~pipewire_t"sv;
120 pw_thread_loop_lock(loop);
123 BOOST_LOG(debug) <<
"[pipewire] Disconnect PW core"sv;
124 pw_core_disconnect(core);
128 BOOST_LOG(debug) <<
"[pipewire] Destroy PW context"sv;
129 pw_context_destroy(context);
133 pw_thread_loop_unlock(loop);
136 BOOST_LOG(debug) <<
"[pipewire] Close pipewire_fd"sv;
139 BOOST_LOG(debug) <<
"[pipewire] Stop PW thread loop"sv;
140 pw_thread_loop_stop(loop);
141 BOOST_LOG(debug) <<
"[pipewire] Destroy PW thread loop"sv;
142 pw_thread_loop_destroy(loop);
145 std::mutex &frame_mutex() {
146 return stream_data.frame_mutex;
149 std::condition_variable &frame_cv() {
150 return stream_data.frame_cv;
153 bool is_frame_ready()
const {
154 return stream_data.frame_ready;
157 void set_frame_ready(
bool ready) {
158 stream_data.frame_ready = ready;
161 int init(
const int stream_fd,
const uint32_t stream_node,
const uint64_t stream_object_serial, std::shared_ptr<shared_state_t> shared_state) {
164 object_serial = stream_object_serial;
165 stream_data.shared = std::move(shared_state);
167 pw_thread_loop_lock(loop);
168 BOOST_LOG(debug) <<
"[pipewire] Setup PW context"sv;
169 context = pw_context_new(pw_thread_loop_get_loop(loop),
nullptr, 0);
171 BOOST_LOG(debug) <<
"[pipewire] Connect PW context to fd"sv;
173 core = pw_context_connect_fd(context, fd,
nullptr, 0);
175 core = pw_context_connect(context,
nullptr, 0);
178 pw_core_add_listener(core, &core_listener, &core_events,
nullptr);
180 BOOST_LOG(debug) <<
"[pipewire] Failed to connect to PW core. Error: "sv << errno <<
"(" << strerror(errno) <<
")"sv;
184 BOOST_LOG(debug) <<
"[pipewire] Failed to setup PW context. Error: "sv << errno <<
"(" << strerror(errno) <<
")"sv;
188 pw_thread_loop_unlock(loop);
192 void cleanup_stream() {
193 BOOST_LOG(debug) <<
"[pipewire] Cleaning up stream"sv;
194 if (loop && stream_data.stream) {
195 pw_thread_loop_lock(loop);
198 BOOST_LOG(debug) <<
"[pipewire] Stop fill_img"sv;
200 std::scoped_lock lock(stream_data.frame_mutex);
201 stream_data.frame_ready =
false;
202 stream_data.current_buffer =
nullptr;
205 if (stream_data.stream) {
206 BOOST_LOG(debug) <<
"[pipewire] Disconnect stream"sv;
207 pw_stream_disconnect(stream_data.stream);
208 BOOST_LOG(debug) <<
"[pipewire] Destroy stream"sv;
209 pw_stream_destroy(stream_data.stream);
210 stream_data.stream =
nullptr;
213 pw_thread_loop_unlock(loop);
217 int ensure_stream(
const platf::mem_type_e mem_type,
const uint32_t width,
const uint32_t height,
const uint32_t refresh_rate,
const struct dmabuf_format_info_t *dmabuf_infos,
const int n_dmabuf_infos,
const bool display_is_nvidia) {
218 pw_thread_loop_lock(loop);
219 if (!stream_data.stream) {
221 BOOST_LOG(debug) <<
"[pipewire] PW core not available. Cannot ensure stream."sv;
222 pw_thread_loop_unlock(loop);
226 struct pw_properties *props = pw_properties_new(PW_KEY_MEDIA_TYPE,
"Video", PW_KEY_MEDIA_CATEGORY,
"Capture", PW_KEY_MEDIA_ROLE,
"Screen",
nullptr);
227#ifdef PW_KEY_TARGET_OBJECT
229 if ((object_serial & SPA_ID_INVALID) != SPA_ID_INVALID) {
230 BOOST_LOG(debug) <<
"[pipewire] Set PW stream target object to serial: "sv << object_serial;
231 pw_properties_setf(props, PW_KEY_TARGET_OBJECT,
"%" PRIu64, object_serial);
236 BOOST_LOG(debug) <<
"[pipewire] Create PW stream"sv;
237 stream_data.stream = pw_stream_new(core,
"Sunshine Video Capture", props);
238 pw_stream_add_listener(stream_data.stream, &stream_data.stream_listener, &stream_events, &stream_data);
240 std::array<uint8_t, SPA_POD_BUFFER_SIZE> buffer;
241 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
244 std::array<const struct spa_pod *, MAX_PARAMS> params;
250 bool use_dmabuf = n_dmabuf_infos > 0 && (mem_type == platf::mem_type_e::vaapi ||
251 mem_type == platf::mem_type_e::vulkan ||
252 (mem_type == platf::mem_type_e::cuda && display_is_nvidia));
254 for (
int i = 0; i < n_dmabuf_infos; i++) {
255 auto format_param = build_format_parameter(&pod_builder, width, height, refresh_rate, dmabuf_infos[i].format, dmabuf_infos[i].modifiers, dmabuf_infos[i].n_modifiers);
256 params[n_params] = format_param;
262 for (
const auto &fmt : format_map) {
263 auto format_param = build_format_parameter(&pod_builder, width, height, refresh_rate, fmt.pw_format,
nullptr, 0);
264 params[n_params] = format_param;
267 BOOST_LOG(debug) <<
"[pipewire] Connect PW stream - fd: "sv << fd <<
" node: "sv << node <<
" object serial: "sv << object_serial;
268 pw_stream_connect(stream_data.stream, PW_DIRECTION_INPUT, node, (
enum pw_stream_flags)(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params.data(), n_params);
270 pw_thread_loop_unlock(loop);
275 for (
int &fd : img_descriptor->sd.fds) {
284 img_descriptor->frame_timestamp = std::chrono::steady_clock::now();
286 struct spa_meta_header *h =
static_cast<struct spa_meta_header *
>(
287 spa_buffer_find_meta_data(buf, SPA_META_Header,
sizeof(*h))
290 img_descriptor->seq = h->seq;
291 img_descriptor->pts = h->pts;
294 if (buf->n_datas > 0) {
295 img_descriptor->pw_flags = buf->datas[0].chunk->flags;
298 struct spa_meta_region *damage =
static_cast<struct spa_meta_region *
>(
299 spa_buffer_find_meta_data(buf, SPA_META_VideoDamage,
sizeof(*damage))
301 img_descriptor->pw_damage = (damage && damage->region.size.width > 0 && damage->region.size.height > 0) ? std::optional<bool>(
true) : std::nullopt;
305 img_descriptor->sd.width = d.format.info.raw.size.width;
306 img_descriptor->sd.height = d.format.info.raw.size.height;
307 img_descriptor->sd.modifier = d.format.info.raw.modifier;
308 img_descriptor->sd.fourcc = d.drm_format;
309 for (
int i = 0; i < MIN(buf->n_datas, 4); i++) {
310 img_descriptor->sd.fds[i] = dup(buf->datas[i].fd);
311 img_descriptor->sd.pitches[i] = buf->datas[i].chunk->stride;
312 img_descriptor->sd.offsets[i] = buf->datas[i].chunk->offset;
317 pw_thread_loop_lock(loop);
318 std::scoped_lock lock(stream_data.frame_mutex);
320 if (stream_data.shared && stream_data.shared->stream_dead.load()) {
323 pw_thread_loop_unlock(loop);
327 if (!stream_data.current_buffer) {
329 pw_thread_loop_unlock(loop);
333 struct spa_buffer *buf = stream_data.current_buffer->buffer;
334 if (buf->datas[0].chunk->size != 0) {
336 fill_img_metadata(img_descriptor, buf);
337 if (buf->datas[0].type == SPA_DATA_DmaBuf) {
338 fill_img_dmabuf(img_descriptor, buf, stream_data);
340 img->data = stream_data.front_buffer->data();
341 img->row_pitch = stream_data.local_stride;
345 pw_thread_loop_unlock(loop);
348 void set_negotiate_maxframerate(
bool negotiate_maxframerate) {
349 negotiate_maxframerate_ = negotiate_maxframerate;
353 struct pw_thread_loop *loop;
354 struct pw_context *context;
355 struct pw_core *core;
356 struct spa_hook core_listener;
360 uint64_t object_serial;
361 bool negotiate_maxframerate_ =
true;
363 struct spa_pod *build_format_parameter(
struct spa_pod_builder *b, uint32_t width, uint32_t height, uint32_t refresh_rate, int32_t format, uint64_t *modifiers,
int n_modifiers) {
364 struct spa_pod_frame object_frame;
365 struct spa_pod_frame modifier_frame;
366 std::array<struct spa_rectangle, 3> sizes;
367 std::array<struct spa_fraction, 3> framerates;
369 sizes[0] = SPA_RECTANGLE(width, height);
370 sizes[1] = SPA_RECTANGLE(1, 1);
371 sizes[2] = SPA_RECTANGLE(8192, 4096);
373 framerates[0] = SPA_FRACTION(0, 1);
374 framerates[1] = SPA_FRACTION(0, 1);
375 framerates[2] = SPA_FRACTION(0, 1);
377 spa_pod_builder_push_object(b, &object_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
378 spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
379 spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
380 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
381 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&sizes[0], &sizes[1], &sizes[2]), 0);
382 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerates[0]), 0);
383 if (negotiate_maxframerate_) {
384 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&framerates[0], &framerates[1], &framerates[2]), 0);
387 if (format == SPA_VIDEO_FORMAT_xBGR_210LE) {
388 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_Id(SPA_VIDEO_COLOR_PRIMARIES_BT2020), 0);
389 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_transferFunction, SPA_POD_Id(SPA_VIDEO_TRANSFER_SMPTE2084), 0);
393 spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
394 spa_pod_builder_push_choice(b, &modifier_frame, SPA_CHOICE_Enum, 0);
397 spa_pod_builder_long(b, modifiers[0]);
398 for (uint32_t i = 0; i < n_modifiers; i++) {
399 spa_pod_builder_long(b, modifiers[i]);
402 spa_pod_builder_pop(b, &modifier_frame);
405 return static_cast<struct spa_pod *
>(spa_pod_builder_pop(b, &object_frame));
408 static void on_core_info_cb([[maybe_unused]]
void *user_data,
const struct pw_core_info *pw_info) {
409 BOOST_LOG(info) <<
"[pipewire] Connected to pipewire version "sv << pw_info->version;
412 static void on_core_error_cb([[maybe_unused]]
void *user_data,
const uint32_t
id,
const int seq, [[maybe_unused]]
int res,
const char *message) {
413 BOOST_LOG(info) <<
"[pipewire] Pipewire Error, id:"sv <<
id <<
" seq:"sv << seq <<
" message: "sv << message;
416 constexpr static const struct pw_core_events core_events = {
417 .version = PW_VERSION_CORE_EVENTS,
418 .info = on_core_info_cb,
419 .error = on_core_error_cb,
422 static void on_stream_state_changed(
void *user_data,
enum pw_stream_state old,
enum pw_stream_state state,
const char *err_msg) {
423 BOOST_LOG(debug) <<
"[pipewire] PipeWire stream state: " << pw_stream_state_as_string(old)
424 <<
" -> " << pw_stream_state_as_string(state);
429 case PW_STREAM_STATE_PAUSED:
430 if (d->shared && old == PW_STREAM_STATE_STREAMING) {
432 std::scoped_lock lock(d->frame_mutex);
433 d->frame_ready =
false;
434 d->current_buffer =
nullptr;
435 d->shared->stream_dead.store(
true);
436 d->shared->current_state = state;
437 d->shared->previous_state = old;
438 d->shared->err_msg =
"";
440 d->frame_cv.notify_all();
443 case PW_STREAM_STATE_ERROR:
445 std::scoped_lock lock(d->frame_mutex);
446 d->shared->current_state = state;
447 d->shared->previous_state = old;
448 d->shared->err_msg = std::string(err_msg);
451 case PW_STREAM_STATE_UNCONNECTED:
453 d->shared->stream_dead.store(
true);
454 d->frame_cv.notify_all();
462 static void on_process(
void *user_data) {
463 const auto d =
static_cast<struct
stream_data_t *
>(user_data);
464 struct pw_buffer *b =
nullptr;
467 while (
struct pw_buffer *aux = pw_stream_dequeue_buffer(d->stream)) {
469 pw_stream_queue_buffer(d->stream, b);
479 if (b->buffer->datas[0].type == SPA_DATA_DmaBuf) {
480 std::scoped_lock lock(d->frame_mutex);
481 if (d->current_buffer) {
482 pw_stream_queue_buffer(d->stream, d->current_buffer);
484 d->current_buffer = b;
485 d->frame_ready =
true;
488 else if (b->buffer->datas[0].data !=
nullptr) {
489 size_t size = b->buffer->datas[0].chunk->size;
492 if (d->back_buffer->size() < size) {
493 d->back_buffer->resize(size);
495 std::memcpy(d->back_buffer->data(), b->buffer->datas[0].data, size);
499 std::scoped_lock lock(d->frame_mutex);
500 std::swap(d->front_buffer, d->back_buffer);
502 d->local_stride = b->buffer->datas[0].chunk->stride;
503 d->frame_ready =
true;
504 d->current_buffer = b;
508 pw_stream_queue_buffer(d->stream, b);
511 d->frame_cv.notify_one();
514 static void on_param_changed(
void *user_data, uint32_t
id,
const struct spa_pod *param) {
515 const auto d =
static_cast<struct
stream_data_t *
>(user_data);
517 d->current_buffer =
nullptr;
519 if (param ==
nullptr ||
id != SPA_PARAM_Format) {
522 if (spa_format_parse(param, &d->format.media_type, &d->format.media_subtype) < 0) {
525 if (d->format.media_type != SPA_MEDIA_TYPE_video || d->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) {
528 if (spa_format_video_raw_parse(param, &d->format.info.raw) < 0) {
532 BOOST_LOG(info) <<
"[pipewire] Video format: "sv << d->format.info.raw.format;
533 BOOST_LOG(info) <<
"[pipewire] Size: "sv << d->format.info.raw.size.width <<
"x"sv << d->format.info.raw.size.height;
534 BOOST_LOG(info) <<
"[pipewire] Color primaries: "sv << d->format.info.raw.color_primaries;
535 BOOST_LOG(info) <<
"[pipewire] Transfer function: "sv << d->format.info.raw.transfer_function;
536 if (d->format.info.raw.max_framerate.num == 0 && d->format.info.raw.max_framerate.denom == 1) {
537 BOOST_LOG(info) <<
"[pipewire] Framerate (from compositor): 0/1 (variable rate capture)";
539 BOOST_LOG(info) <<
"[pipewire] Framerate (from compositor): "sv << d->format.info.raw.framerate.num <<
"/"sv << d->format.info.raw.framerate.denom;
540 BOOST_LOG(info) <<
"[pipewire] Framerate (from compositor, max): "sv << d->format.info.raw.max_framerate.num <<
"/"sv << d->format.info.raw.max_framerate.denom;
543 int physical_w = d->format.info.raw.size.width;
544 int physical_h = d->format.info.raw.size.height;
547 int old_w = d->shared->negotiated_width.load();
548 int old_h = d->shared->negotiated_height.load();
549 int old_color_primaries = d->shared->color_primaries.load();
550 int old_transfer_function = d->shared->transfer_function.load();
552 if (physical_w != old_w || physical_h != old_h) {
553 d->shared->negotiated_width.store(physical_w);
554 d->shared->negotiated_height.store(physical_h);
557 if (d->format.info.raw.color_primaries != old_color_primaries || d->format.info.raw.transfer_function != old_transfer_function) {
558 d->shared->color_primaries.store(d->format.info.raw.color_primaries);
559 d->shared->transfer_function.store(d->format.info.raw.transfer_function);
563 uint64_t drm_format = 0;
564 for (
const auto &fmt : format_map) {
565 if (fmt.pw_format == d->format.info.raw.format) {
566 drm_format = fmt.fourcc;
569 d->drm_format = drm_format;
571 uint32_t buffer_types = 0;
572 if (spa_pod_find_prop(param,
nullptr, SPA_FORMAT_VIDEO_modifier) !=
nullptr && d->drm_format) {
573 BOOST_LOG(info) <<
"[pipewire] using DMA-BUF buffers"sv;
574 buffer_types |= 1 << SPA_DATA_DmaBuf;
576 BOOST_LOG(info) <<
"[pipewire] using memory buffers"sv;
577 buffer_types |= 1 << SPA_DATA_MemPtr;
581 std::array<uint8_t, SPA_POD_BUFFER_SIZE> buffer;
582 std::array<const struct spa_pod *, 3> params;
584 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
585 auto buffer_param =
static_cast<const struct spa_pod *
>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types)));
586 params[n_params] = buffer_param;
588 auto meta_param =
static_cast<const struct spa_pod *
>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(
sizeof(
struct spa_meta_header))));
589 params[n_params] = meta_param;
591 int videoDamageRegionCount = 16;
592 auto damage_param =
static_cast<const struct spa_pod *
>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
sizeof(
struct spa_meta_region) * videoDamageRegionCount,
sizeof(
struct spa_meta_region) * 1,
sizeof(
struct spa_meta_region) * videoDamageRegionCount)));
593 params[n_params] = damage_param;
596 pw_stream_update_params(d->stream, params.data(), n_params);
599 constexpr static const struct pw_stream_events stream_events = {
600 .version = PW_VERSION_STREAM_EVENTS,
601 .state_changed = on_stream_state_changed,
602 .param_changed = on_param_changed,
603 .process = on_process,
609 static bool init_pipewire_and_check_hwdevice_type(
platf::mem_type_e hwdevice_type) {
611 pw_init(
nullptr,
nullptr);
614 switch (hwdevice_type) {
634 virtual int configure_stream(
const std::string &display_name,
int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_objectserial) = 0;
641 if (logical_height <= 0 || logical_width <= 0 || env_logical_height <= 0 || env_logical_width <= 0 || env_height <= 0 || env_width <= 0) {
642 int desktop_width = 0;
643 int desktop_height = 0;
644 int desktop_logical_width = 0;
645 int desktop_logical_height = 0;
646 for (
const auto &monitor : wl::monitors()) {
647 BOOST_LOG(debug) <<
"[pipewire] Found output: '"sv << monitor->name <<
"' offset: "sv << monitor->viewport.offset_x <<
'x' << monitor->viewport.offset_y <<
" resolution: "sv << monitor->viewport.width <<
'x' << monitor->viewport.height <<
" logical resolution: "sv << monitor->viewport.logical_width <<
'x' << monitor->viewport.logical_height;
650 if ((logical_width <= 0 || logical_height <= 0) && monitor->viewport.offset_x == offset_x && monitor->viewport.offset_y == offset_y && ((monitor->viewport.width == width && monitor->viewport.height == height) || (monitor->viewport.logical_width == width && monitor->viewport.logical_height == height))) {
651 this->logical_width = monitor->viewport.logical_width;
652 this->logical_height = monitor->viewport.logical_height;
653 BOOST_LOG(debug) <<
"[pipewire] Set logical resolution: "sv << logical_width <<
'x' << logical_height;
656 desktop_width = std::max(desktop_width, monitor->viewport.offset_x + monitor->viewport.width);
657 desktop_height = std::max(desktop_height, monitor->viewport.offset_y + monitor->viewport.height);
659 desktop_logical_width = std::max(desktop_logical_width, monitor->viewport.offset_x + monitor->viewport.logical_width);
660 desktop_logical_height = std::max(desktop_logical_height, monitor->viewport.offset_y + monitor->viewport.logical_height);
662 if (env_height <= 0 || env_width <= 0) {
663 this->env_width = desktop_width;
664 this->env_height = desktop_height;
665 BOOST_LOG(debug) <<
"[pipewire] Set desktop resolution: "sv << env_width <<
'x' << env_height;
667 if (env_logical_height <= 0 || env_logical_width <= 0) {
668 this->env_logical_width = desktop_logical_width;
669 this->env_logical_height = desktop_logical_height;
670 BOOST_LOG(debug) <<
"[pipewire] Set desktop logical resolution: "sv << env_logical_width <<
'x' << env_logical_height;
675 int init(
platf::mem_type_e hwdevice_type,
const std::string &display_name, const ::video::config_t &config) {
677 framerate = config.framerate;
678 if (config.framerateX100 > 0) {
679 AVRational fps_strict = ::video::framerateX100_to_rational(config.framerateX100);
680 delay = std::chrono::nanoseconds(
681 (
static_cast<int64_t
>(fps_strict.den) * 1'000'000'000LL) / fps_strict.num
683 BOOST_LOG(info) <<
"[pipewire] Requested frame rate [" << fps_strict.num <<
"/" << fps_strict.den <<
", approx. " << av_q2d(fps_strict) <<
" fps]";
685 delay = std::chrono::nanoseconds {1s} / framerate;
686 BOOST_LOG(info) <<
"[pipewire] Requested frame rate [" << framerate <<
"fps]";
688 mem_type = hwdevice_type;
690 if (get_dmabuf_modifiers() < 0) {
694 int pipewire_fd = -1;
695 auto pipewire_node = PW_ID_ANY;
696 uint64_t pipewire_object_serial = SPA_ID_INVALID;
698 if (
configure_stream(display_name, pipewire_fd, pipewire_node, pipewire_object_serial) < 0 || (pipewire_node == PW_ID_ANY && (pipewire_object_serial & SPA_ID_INVALID) == SPA_ID_INVALID)) {
699 BOOST_LOG(error) <<
"[pipewire] Could not find display with name: '"sv << display_name <<
"'";
702 BOOST_LOG(info) <<
"[pipewire] Streaming display '"sv << display_name <<
"' offset: "sv << offset_x <<
"x"sv << offset_y <<
" resolution: "sv << width <<
"x"sv << height;
707 framerate = config.framerate;
710 shared_state = std::make_shared<shared_state_t>();
712 shared_state->stream_dead.store(
false);
713 shared_state->negotiated_width.store(0);
714 shared_state->negotiated_height.store(0);
715 shared_state->color_primaries.store(0);
716 shared_state->transfer_function.store(0);
719 if (pipewire.init(pipewire_fd, pipewire_node, pipewire_object_serial, shared_state) < 0) {
720 BOOST_LOG(error) <<
"[pipewire] Failed to init pipewire. pipewire_t::init() failed.";
725 if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
726 BOOST_LOG(error) <<
"[pipewire] Failed to ensure pipewire stream. pipewire_t::init() failed.";
731 int timeout_ms = 1500;
732 int negotiated_w = 0;
733 int negotiated_h = 0;
734 while (timeout_ms > 0) {
735 negotiated_w = shared_state->negotiated_width.load();
736 negotiated_h = shared_state->negotiated_height.load();
737 if (negotiated_w > 0 && negotiated_h > 0) {
740 std::this_thread::sleep_for(std::chrono::milliseconds(10));
744 if (negotiated_w > 0 && negotiated_h > 0 && (negotiated_w != width || negotiated_h != height)) {
745 width = negotiated_w;
746 height = negotiated_h;
747 BOOST_LOG(info) <<
"[pipewire] Using negotiated Resolution: "sv << width <<
"x" << height;
754 env_logical_height = 0;
755 env_logical_width = 0;
764 auto deadline = std::chrono::steady_clock::now() +
timeout;
767 while (std::chrono::steady_clock::now() < deadline) {
768 if (!wait_for_frame(deadline)) {
769 return platf::capture_e::timeout;
772 if (!pull_free_image_cb(img_out)) {
773 return platf::capture_e::interrupted;
778 pipewire.fill_img(img_egl);
781 if ((img_egl->sd.fds[0] >= 0 || img_egl->data !=
nullptr) && !is_buffer_redundant(img_egl)) {
783 update_metadata(img_egl, retries);
784 return platf::capture_e::ok;
790 return platf::capture_e::timeout;
793 std::shared_ptr<platf::img_t> alloc_img()
override {
795 auto img = std::make_shared<egl::img_descriptor_t>();
798 img->height = height;
799 img->pixel_pitch = 4;
800 img->row_pitch = img->pixel_pitch * width;
802 img->serial = std::numeric_limits<
decltype(img->serial)>::max();
804 std::fill_n(img->sd.fds, 4, -1);
814 auto next_frame = std::chrono::steady_clock::now();
816 if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
817 BOOST_LOG(error) <<
"[pipewire] Failed to ensure pipewire stream. capture() failed with error.";
818 return platf::capture_e::error;
820 sleep_overshoot_logger.reset();
824 if (shared_state->stream_dead.exchange(
false)) {
830 BOOST_LOG(warning) <<
"[pipewire] PipeWire stream disconnected. Forcing session reset."sv;
831 return platf::capture_e::reinit;
835 auto now = std::chrono::steady_clock::now();
836 while (next_frame < now) {
840 if (next_frame > now) {
841 std::this_thread::sleep_until(next_frame);
842 sleep_overshoot_logger.first_point(next_frame);
843 sleep_overshoot_logger.second_point_now_and_log();
846 std::shared_ptr<platf::img_t> img_out;
847 switch (
const auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor)) {
848 case platf::capture_e::reinit:
849 case platf::capture_e::error:
850 case platf::capture_e::interrupted:
851 pipewire.frame_cv().notify_all();
853 case platf::capture_e::timeout:
854 if (!pull_free_image_cb(img_out)) {
856 BOOST_LOG(debug) <<
"[pipewire] PipeWire: timeout -> interrupt nudge";
857 pipewire.frame_cv().notify_all();
858 return platf::capture_e::interrupted;
860 if (!push_captured_image_cb(std::move(img_out),
false)) {
861 BOOST_LOG(debug) <<
"[pipewire] PipeWire: !push_captured_image_cb -> ok";
862 return platf::capture_e::ok;
865 case platf::capture_e::ok:
866 if (!push_captured_image_cb(std::move(img_out),
true)) {
867 BOOST_LOG(debug) <<
"[pipewire] PipeWire: !push_captured_image_cb -> ok";
868 return platf::capture_e::ok;
872 BOOST_LOG(error) <<
"[pipewire] Unrecognized capture status ["sv << std::to_underlying(status) <<
']';
877 return platf::capture_e::ok;
880 std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(
platf::pix_fmt_e pix_fmt)
override {
881#ifdef SUNSHINE_BUILD_VAAPI
882 if (mem_type == platf::mem_type_e::vaapi) {
883 return va::make_avcodec_encode_device(width, height, n_dmabuf_infos > 0);
887#ifdef SUNSHINE_BUILD_VULKAN
888 if (mem_type == platf::mem_type_e::vulkan && n_dmabuf_infos > 0) {
889 return vk::make_avcodec_encode_device_vram(width, height, 0, 0);
893#ifdef SUNSHINE_BUILD_CUDA
894 if (mem_type == platf::mem_type_e::cuda) {
895 if (display_is_nvidia && n_dmabuf_infos > 0) {
901 return cuda::make_avcodec_encode_device(width, height,
false);
906 return std::make_unique<platf::avcodec_encode_device_t>();
914 img->data =
new std::uint8_t[img->height * img->row_pitch];
915 std::fill_n(img->data, img->height * img->row_pitch, 0);
919 bool is_hdr()
override {
920 int color_primaries = shared_state->color_primaries.load();
921 int transfer_function = shared_state->transfer_function.load();
923 if (color_primaries == SPA_VIDEO_COLOR_PRIMARIES_BT2020 && transfer_function == SPA_VIDEO_TRANSFER_SMPTE2084) {
930 bool get_hdr_metadata(SS_HDR_METADATA &metadata)
override {
931 int color_primaries = shared_state->color_primaries.load();
932 int transfer_function = shared_state->transfer_function.load();
934 if (color_primaries == SPA_VIDEO_COLOR_PRIMARIES_BT2020 && transfer_function == SPA_VIDEO_TRANSFER_SMPTE2084) {
936 metadata.displayPrimaries[0].x = 0.708f * 50000;
937 metadata.displayPrimaries[0].y = 0.292f * 50000;
938 metadata.displayPrimaries[1].x = 0.170f * 50000;
939 metadata.displayPrimaries[1].y = 0.797f * 50000;
940 metadata.displayPrimaries[2].x = 0.131f * 50000;
941 metadata.displayPrimaries[2].y = 0.046f * 50000;
942 metadata.whitePoint.x = 0.3127f * 50000;
943 metadata.whitePoint.y = 0.3290f * 50000;
946 metadata.maxDisplayLuminance = 4000;
947 metadata.minDisplayLuminance = 1;
950 metadata.maxContentLightLevel = 0;
951 metadata.maxFrameAverageLightLevel = 0;
952 metadata.maxFullFrameLuminance = 0;
963 if (img->pw_flags.has_value() && (img->pw_flags.value() & SPA_CHUNK_FLAG_CORRUPTED)) {
968 if (img->pts.has_value() && last_pts.has_value() && img->pts.value() == last_pts.value()) {
969 return img->pw_damage.has_value() && !img->pw_damage.value();
978 img->sequence = ++sequence;
981 BOOST_LOG(debug) <<
"[pipewire] Processed frame after " << retries <<
" redundant events."sv;
985 bool wait_for_frame(std::chrono::steady_clock::time_point deadline) {
986 std::unique_lock<std::mutex> lock(pipewire.frame_mutex());
988 bool success = pipewire.frame_cv().wait_until(lock, deadline, [&] {
989 return pipewire.is_frame_ready() || shared_state->stream_dead.load();
993 pipewire.set_frame_ready(
false);
999 static bool pw_format_supported(uint64_t fourcc, std::array<EGLint, MAX_DMABUF_FORMATS> dmabuf_formats) {
1000 for (
const auto &drm_format : dmabuf_formats) {
1001 if (drm_format == fourcc) {
1008 void query_dmabuf_formats(EGLDisplay egl_display) {
1009 EGLint num_dmabuf_formats = 0;
1010 std::array<EGLint, MAX_DMABUF_FORMATS> dmabuf_formats = {0};
1011 eglQueryDmaBufFormatsEXT(egl_display, MAX_DMABUF_FORMATS, dmabuf_formats.data(), &num_dmabuf_formats);
1013 if (num_dmabuf_formats > MAX_DMABUF_FORMATS) {
1014 BOOST_LOG(warning) <<
"[pipewire] Some DMA-BUF formats are being ignored"sv;
1017 for (
const auto &fmt : format_map) {
1018 if (n_dmabuf_infos >= MAX_DMABUF_FORMATS) {
1022 if (!pw_format_supported(fmt.fourcc, dmabuf_formats)) {
1026 EGLint num_modifiers = 0;
1027 std::array<EGLuint64KHR, MAX_DMABUF_MODIFIERS> mods = {0};
1028 eglQueryDmaBufModifiersEXT(egl_display, fmt.fourcc, MAX_DMABUF_MODIFIERS, mods.data(),
nullptr, &num_modifiers);
1030 if (num_modifiers > MAX_DMABUF_MODIFIERS) {
1031 BOOST_LOG(warning) <<
"[pipewire] Some DMA-BUF modifiers are being ignored"sv;
1034 dmabuf_infos[n_dmabuf_infos].format = fmt.pw_format;
1035 dmabuf_infos[n_dmabuf_infos].n_modifiers = MIN(num_modifiers, MAX_DMABUF_MODIFIERS);
1036 dmabuf_infos[n_dmabuf_infos].modifiers =
1037 static_cast<uint64_t *
>(g_memdup2(mods.data(),
sizeof(uint64_t) * dmabuf_infos[n_dmabuf_infos].n_modifiers));
1042 int get_dmabuf_modifiers() {
1043 if (wl_display.
init() < 0) {
1047 auto egl_display = egl::make_display(wl_display.get());
1056 bool has_intel_gpu = std::ifstream(
"/sys/class/drm/card0/device/vendor").good() ||
1057 std::ifstream(
"/sys/class/drm/card1/device/vendor").good();
1058 if (has_intel_gpu) {
1060 auto check_intel = [](
const std::string &path) {
1061 if (std::ifstream f(path); f.good()) {
1064 return vendor ==
"0x8086";
1068 bool intel_present = check_intel(
"/sys/class/drm/card0/device/vendor") ||
1069 check_intel(
"/sys/class/drm/card1/device/vendor");
1070 if (intel_present) {
1071 BOOST_LOG(info) <<
"[pipewire] Hybrid GPU system detected (Intel + discrete) - CUDA will use memory buffers"sv;
1072 display_is_nvidia =
false;
1075 const char *vendor = eglQueryString(egl_display.get(), EGL_VENDOR);
1076 if (vendor && std::string_view(vendor).contains(
"NVIDIA")) {
1077 BOOST_LOG(info) <<
"[pipewire] Pure NVIDIA system - DMA-BUF will be enabled for CUDA"sv;
1078 display_is_nvidia =
true;
1083 if (eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT) {
1084 query_dmabuf_formats(egl_display.get());
1092 std::array<struct dmabuf_format_info_t, MAX_DMABUF_FORMATS> dmabuf_infos;
1094 bool display_is_nvidia =
false;
1095 std::chrono::nanoseconds delay;
1096 std::optional<std::uint64_t> last_pts {};
1097 std::optional<std::uint64_t> last_seq {};
1098 std::uint64_t sequence {};
1103 pipewire_t pipewire;
1104 std::shared_ptr<shared_state_t> shared_state;