50#include <pipewire/pipewire.h>
51#include <pipewire/version.h>
52#include <spa/utils/result.h>
53#include <spa/param/video/format-utils.h>
54#include <spa/param/props.h>
55#include <spa/debug/types.h>
69#define MOD_NAME "[PW vcap] "
92#ifdef HAVE_DBUS_SCREENCAST
93 ScreenCastPortal portal;
108 bool show_cursor =
false;
109 std::string restore_file =
"";
112 std::string target =
"";
119 <<
"\" -> \""<<pw_stream_state_as_string(
state)<<
"\"\n";
121 if (error !=
nullptr) {
131 if (
param ==
nullptr ||
id != SPA_PARAM_Format)
134 int parse_format_ret = spa_format_parse(
param, &s->format.media_type, &s->format.media_subtype);
135 assert(parse_format_ret > 0);
137 if(s->format.media_type != SPA_MEDIA_TYPE_video
138 || s->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
144 auto& raw_format = s->format.info.raw;
145 spa_format_video_raw_parse(
param, &raw_format);
149 s->desc.width = raw_format.size.width;
150 s->desc.height = raw_format.size.height;
151 if(raw_format.framerate.num != 0){
153 s->desc.fps =
static_cast<double>(raw_format.framerate.num) / raw_format.framerate.denom;
157 if(raw_format.max_framerate.num != 0){
158 s->desc.fps =
static_cast<double>(raw_format.max_framerate.num) / raw_format.max_framerate.denom;
166 s->desc.tile_count = 1;
171 int32_t size = linesize * s->desc.height;
173 uint8_t params_buffer[1024];
175 struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(params_buffer,
sizeof(params_buffer));
176 const struct spa_pod *
params[3] = {};
179 params[n_params++] =
static_cast<spa_pod *
>(spa_pod_builder_add_object(&builder,
180 SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
181 SPA_PARAM_BUFFERS_buffers,
183 SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
184 SPA_PARAM_BUFFERS_size, SPA_POD_Int(size),
185 SPA_PARAM_BUFFERS_stride, SPA_POD_Int(linesize),
186 SPA_PARAM_BUFFERS_dataType,
187 SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemPtr)))
190 if(s->user_options.crop) {
191 params[n_params++] =
static_cast<spa_pod *
>(spa_pod_builder_add_object(&builder,
192 SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
193 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
195 SPA_POD_Int(
sizeof(
struct spa_meta_region)))
199 pw_stream_update_params(s->stream.get(),
params, n_params);
203 auto offset = src->datas[0].chunk->offset;
204 auto chunk_size = src->datas[0].chunk->size;
205 auto stride = src->datas[0].chunk->stride;
207 auto width = size.width;
208 auto height = size.height;
210 unsigned start_x = 0;
211 unsigned start_y = 0;
213 width = crop->size.width;
214 height = crop->size.height;
215 start_x = crop->position.x;
216 start_y = crop->position.y;
220 stride = chunk_size / size.height;
224 bool swap_red_blue =
fmt == SPA_VIDEO_FORMAT_BGRA ||
fmt == SPA_VIDEO_FORMAT_BGRx;
225 for(
unsigned i = 0; i < height; i++){
226 auto src_p =
static_cast<unsigned char *
>(src->datas[0].data) +
offset + skip + stride * (i + start_y);
227 auto dst_p =
reinterpret_cast<unsigned char *
>(dst->
tiles[0].
data) + linesize * i;
235 memcpy(dst_p, src_p, linesize);
248 [[maybe_unused]]
int n_buffers_from_pw = 0;
249 while((buffer = pw_stream_dequeue_buffer(s->stream.get())) !=
nullptr){
254 assert(buffer->buffer !=
nullptr);
255 assert(buffer->buffer->datas !=
nullptr);
256 assert(buffer->buffer->n_datas == 1);
257 assert(buffer->buffer->datas[0].data !=
nullptr);
259 if(buffer->buffer->datas[0].chunk ==
nullptr || buffer->buffer->datas[0].chunk->size == 0) {
261 pw_stream_queue_buffer(s->stream.get(), buffer);
266 std::lock_guard<std::mutex>
lock(s->mut);
267 if(!s->blank_frames.empty()){
268 next_frame = std::move(s->blank_frames.back());
269 s->blank_frames.pop_back();
275 pw_stream_queue_buffer(s->stream.get(), buffer);
279 spa_region *crop_region =
nullptr;
280 if (s->user_options.crop) {
281 spa_meta_region *meta_crop_region =
static_cast<spa_meta_region*
>(spa_buffer_find_meta_data(buffer->buffer, SPA_META_VideoCrop,
sizeof(*meta_crop_region)));
282 if (meta_crop_region !=
nullptr && spa_meta_region_is_valid(meta_crop_region))
283 crop_region = &meta_crop_region->region;
289 s->desc.width = crop_region->size.width;
290 s->desc.height = crop_region->size.height;
299 auto& raw_format = s->format.info.raw;
304 s->sending_frames.push(std::move(next_frame));
305 pw_stream_queue_buffer(s->stream.get(), buffer);
328 .version = PW_VERSION_STREAM_EVENTS,
331 .control_info =
nullptr,
332 .io_changed =
nullptr,
338#if PW_MAJOR > 0 || PW_MINOR > 3 || (PW_MINOR == 3 && PW_MICRO > 39)
340 .trigger_done =
nullptr,
346 const struct spa_pod *
params[2] = {};
347 uint8_t params_buffer[1024];
348 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(params_buffer,
sizeof(params_buffer));
352 std::string node_name =
"ultragrid_in_";
355 snprintf(buf,
sizeof(buf),
"%ld", (
long) getpid());
359 auto props = pw_properties_new(
360 PW_KEY_MEDIA_TYPE,
"Video",
361 PW_KEY_MEDIA_CATEGORY,
"Capture",
362 PW_KEY_MEDIA_ROLE,
"Communication",
363 PW_KEY_APP_NAME,
"UltraGrid",
364 PW_KEY_APP_ICON_NAME,
"ultragrid",
365 PW_KEY_NODE_NAME, node_name.c_str(),
366 PW_KEY_NODE_DESCRIPTION,
"UltraGrid capture",
379 assert(s->
stream !=
nullptr);
381 pw_stream_add_listener(
387 auto size_rect_def = SPA_RECTANGLE(1920, 1080);
388 auto size_rect_min = SPA_RECTANGLE(1, 1);
389 auto size_rect_max = SPA_RECTANGLE(3840, 2160);
392 auto framerate_min = SPA_FRACTION(0, 1);
393 auto framerate_max = SPA_FRACTION(600, 1);
395 const int n_params = 1;
396 params[0] =
static_cast<spa_pod *
> (spa_pod_builder_add_object(
397 &pod_builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
398 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
399 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
400 SPA_FORMAT_VIDEO_format,
401 SPA_POD_CHOICE_ENUM_Id(8,
402 SPA_VIDEO_FORMAT_UYVY,
403 SPA_VIDEO_FORMAT_UYVY,
404 SPA_VIDEO_FORMAT_RGB,
405 SPA_VIDEO_FORMAT_RGBA,
406 SPA_VIDEO_FORMAT_RGBx,
407 SPA_VIDEO_FORMAT_YUY2,
408 SPA_VIDEO_FORMAT_BGRA,
409 SPA_VIDEO_FORMAT_BGRx
411 SPA_FORMAT_VIDEO_size,
412 SPA_POD_CHOICE_RANGE_Rectangle(
416 SPA_FORMAT_VIDEO_framerate,
417 SPA_POD_CHOICE_RANGE_Fraction(
423 auto flags = PW_STREAM_FLAG_MAP_BUFFERS |
424 PW_STREAM_FLAG_DONT_RECONNECT;
427 flags |= PW_STREAM_FLAG_AUTOCONNECT;
430 int res = pw_stream_connect(s->
stream.get(),
433 static_cast<pw_stream_flags
>(
flags),
436 fprintf(stderr,
MOD_NAME "can't connect: %s\n", spa_strerror(res));
440 pw_stream_set_active(s->
stream.get(),
true);
450 snprintf((*available_devices)[0].
name,
sizeof (*available_devices)[0].
name,
"Screen capture PipeWire");
459 snprintf((*available_devices)[0].
name,
sizeof (*available_devices)[0].
name,
"Generic PipeWire video capture");
463 auto param = [](
const char*
name) -> std::ostream& {
468 std::cout <<
"Screen capture using PipeWire and ScreenCast freedesktop portal API\n";
469 std::cout <<
"Usage: -t screen_pw[:cursor|:nocrop|:fps=<fps>|:restore=<token_file>]]\n";
470 param(
"cursor") <<
"make the cursor visible (default hidden)\n";
471 param(
"nocrop") <<
"when capturing a window do not crop out the empty background\n";
472 param(
"<fps>") <<
"prefered FPS passed to PipeWire (PipeWire may ignore it)\n";
473 param(
"<token_file>") <<
"restore the selected window/display from a file.\n\t\tIf not possible, display the selection dialog and save the token to the file specified.\n";
489 std::istringstream params_stream(
fmt);
492 while (std::getline(params_stream,
param,
':')) {
493 if (
param ==
"help") {
499 }
else if (
param ==
"cursor") {
501 }
else if (
param ==
"nocrop") {
504 auto split_index =
param.find(
'=');
505 if(split_index != std::string::npos && split_index != 0){
506 std::string
name =
param.substr(0, split_index);
507 std::string value =
param.substr(split_index + 1);
509 if (
name ==
"fps" ||
name ==
"FPS"){
510 std::istringstream is(value);
513 }
else if(
name ==
"restore"){
516 }
else if(
name ==
"target"){
530#ifdef HAVE_DBUS_SCREENCAST
537 auto s = std::make_unique<vcap_pw_state>();
548 s->blank_frames.emplace_back(
vf_alloc(1));
550 auto portalResult = s->portal.run(s->user_options.restore_file, s->user_options.show_cursor);
552 if (portalResult.pipewire_fd == -1) {
556 s->fd = portalResult.pipewire_fd;
562 s->node = portalResult.pipewire_node;
567 *
state = s.release();
579 auto s = std::make_unique<vcap_pw_state>();
588 s->blank_frames.emplace_back(
vf_alloc(1));
595 *
state = s.release();
606 pw_stream_disconnect(s->stream.get());
609 pw_thread_loop_stop(s->pw.pipewire_loop.get());
618 assert(
state !=
nullptr);
622 if(s->in_flight_frame.get() !=
nullptr){
623 s->blank_frames.push_back(std::move(s->in_flight_frame));
626 using namespace std::chrono_literals;
627 s->sending_frames.timed_pop(s->in_flight_frame, 500ms);
628 return s->in_flight_frame.get();
631#ifdef HAVE_DBUS_SCREENCAST
635 vidcap_screen_pw_init,
const char unsigned int flags
struct audio_frame ** audio
int void(** deleter)(void *))
struct video_frame * vf_alloc(int count)
Allocates blank video frame.
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_desc video_desc_from_frame(const struct video_frame *frame)
Returns struct video_desc from video frame.
int vc_get_linesize(unsigned int width, codec_t codec)
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
static const pw_stream_events stream_events
static void on_process(void *state)
simple blocking synchronized queue
int color_printf(const char *format,...)
static __device__ const float offset
void log_msg(int level, const char *format,...)
#define LOG_LEVEL_NOTICE
information that may be interesting
#define LOG_LEVEL_INFO
normal reporting
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_VERBOSE
#define LOG_LEVEL_WARNING
less severe errors
#define VIDCAP_INIT_NOERR
state not initialized, other action performed (typically help)
#define VIDCAP_INIT_OK
initialization successful
#define VIDEO_CAPTURE_ABI_VERSION
#define VIDCAP_INIT_FAIL
error ocured
#define VIDCAP_INIT_AUDIO_NOT_SUPPORTED
card does not support audio
@ LIBRARY_CLASS_VIDEO_CAPTURE
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
void print_devices(std::string_view media_class)
bool initialize_pw_common(pipewire_state_common &s, int fd)
codec_t uv_codec_from_pw_fmt(spa_video_format fmt)
raii_uniq_handle< pw_stream, pw_stream_destroy > pw_stream_uniq
#define STREAM_TARGET_PROPERTY_KEY
decoder_func_t vc_copylineRGBA
struct used to comunicate with audio capture/playback device
void operator()(video_frame *f)
pw_thread_loop_uniq pipewire_loop
pw_core_uniq pipewire_core
char * data
Raw tile data. Pointer must be at least 4B aligned.
unsigned int data_len
length of the data
enum vcap_pw_state::Mode mode
struct spa_video_info format
struct vcap_pw_state::@71 user_options
unique_frame in_flight_frame
synchronized_queue< unique_frame, QUEUE_SIZE > sending_frames
std::vector< unique_frame > blank_frames
spa_hook_uniq stream_listener
Struct video_frame represents a video frame and contains video description.
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
@ PROGRESSIVE
progressive frame
static constexpr int MIN_BUFFERS_PW
static void vidcap_screen_pw_probe(struct device_info **available_devices, int *count, void(**deleter)(void *))
static void show_screen_help()
static constexpr int DEFAULT_BUFFERS_PW
static constexpr int DEFAULT_EXPECTING_FPS
static void on_remove_buffer(void *, struct pw_buffer *)
static constexpr int QUEUE_SIZE
static void on_stream_param_changed(void *state, uint32_t id, const struct spa_pod *param)
static void vidcap_screen_pw_done(void *state)
static void on_stream_state_changed(void *, enum pw_stream_state old, enum pw_stream_state state, const char *error)
static void vidcap_pw_probe(struct device_info **available_devices, int *count, void(**deleter)(void *))
static void show_generic_help()
static const struct video_capture_info vidcap_pw_info
static struct video_frame * vidcap_screen_pw_grab(void *state, struct audio_frame **audio)
static int vidcap_pw_init(struct vidcap_params *params, void **state)
static void on_drained(void *)
static int start_pipewire(vcap_pw_state *s)
static void pw_frame_to_uv_frame_memcpy(video_frame *dst, spa_buffer *src, spa_video_format fmt, spa_rectangle size, const spa_region *crop)
static int parse_params(struct vidcap_params *params, vcap_pw_state *s)
static constexpr int MAX_BUFFERS_PW
static void on_add_buffer(void *, struct pw_buffer *)
std::unique_ptr< video_frame, frame_deleter > unique_frame
const char * vidcap_params_get_fmt(const struct vidcap_params *params)
unsigned int vidcap_params_get_flags(const struct vidcap_params *params)
#define VIDCAP_FLAG_AUDIO_ANY
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.