UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
pipewire.cpp
Go to the documentation of this file.
1
2#include "config.h"
3#include "config_unix.h"
4#include "config_win32.h"
5
6#include <assert.h>
7#include <stdlib.h>
8#include <unistd.h>
9#include <string.h>
10#include <memory>
11#include <atomic>
12
13#include "debug.h"
14#include "host.h"
15#include "lib_common.h"
16#include "utils/color_out.h"
17#include "utils/macros.h"
18#include "utils/misc.h"
19#include "utils/text.h"
20#include "video.h"
21#include "video_codec.h"
22#include "video_display.h"
23
24#include "pipewire_common.hpp"
26#include <spa/param/video/format-utils.h>
27
28#define MOD_NAME "[PW disp] "
29
30namespace{
31 struct frame_deleter{ void operator()(video_frame *f){ vf_free(f); } };
32 using unique_frame = std::unique_ptr<video_frame, frame_deleter>;
33
34 struct memfd_buffer{
35 memfd_buffer() = default;
36 memfd_buffer(const memfd_buffer&) = delete;
37 ~memfd_buffer();
38
39 memfd_buffer& operator=(const memfd_buffer&) = delete;
40
41 /* The buffer is shared by the pipewire buffer and ultragrid.
42 * Since both can be deleted independently at any time from
43 * different threads we need reference counting.
44 */
45 std::atomic<int> ref_count = 0;
46
47 int fd = -1;
48 int size = 0;
49 void *ptr = nullptr;
50
51 /* Access to these pointers is synchronized by the pipewire
52 * thread loop lock.
53 *
54 * The pw_buffer pointer is also used to signal that the
55 * pipewire buffer got removed and should not be queued by
56 * setting it to null in the on_remove_buffer callback.
57 *
58 * The video_frame pointer is used to signal the current
59 * ownership of the frame. The ownership is transferred in the
60 * getf and putf functions.
61 *
62 * If set - the frame is owned by pipewire and should be
63 * deleted in the on_remove_callback.
64 *
65 * If unset - frame is owned
66 * by ultragrid, which should delete it.
67 */
68 pw_buffer *b = nullptr;
69 video_frame *f = nullptr;
70 };
71
72 memfd_buffer::~memfd_buffer(){
73 assert(ref_count == 0);
74 if(ptr){
75 munmap(ptr, size);
76 }
77 if(fd > 0){
78 close(fd);
79 }
80 }
81
82 memfd_buffer *memfd_buf_create(size_t size){
83 auto buf = std::make_unique<memfd_buffer>();
84
85 buf->fd = memfd_create("ultragrid-display", MFD_CLOEXEC);
86 if(buf->fd < 0){
87 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Cannot create memfd\n");
88 return nullptr;
89 }
90
91 buf->size = size;
92 if(ftruncate(buf->fd, size) < 0){
93 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to resize memfd\n");
94 return nullptr;
95 }
96
97 buf->ptr = mmap(nullptr, size, PROT_READ|PROT_WRITE,
98 MAP_SHARED, buf->fd, 0);
99
100 return buf.release();
101 }
102
103 void memfd_buf_unref(memfd_buffer **buffer){
104 auto buf = *buffer;
105 *buffer = nullptr;
106
107 int refcount = buf->ref_count.fetch_sub(1) - 1;
108 if(refcount < 1)
109 delete buf;
110 }
111
112 memfd_buffer *memfd_buf_ref(memfd_buffer *buf){
113 buf->ref_count++;
114 return buf;
115 }
116
117 void memfd_frame_data_deleter(video_frame *f){
118 auto buf = static_cast<memfd_buffer *>(f->callbacks.dispose_udata);
119
120 if(buf)
121 memfd_buf_unref(&buf);
122 }
123}
124
138
139static void on_state_changed(void *state, enum pw_stream_state old, enum pw_stream_state new_state, const char *error)
140{
141 auto s = static_cast<display_pw_state *>(state);
142 log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Stream state change: %s -> %s\n",
143 pw_stream_state_as_string(old),
144 pw_stream_state_as_string(new_state));
145
146 if(error){
147 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Stream error: %s\n", error);
148 }
149
150 pw_thread_loop_signal(s->pw.pipewire_loop.get(), false);
151}
152
153static void on_param_changed(void *state, uint32_t id, const struct spa_pod *param){
154 auto s = static_cast<display_pw_state *>(state);
155
156 if(!param || id != SPA_PARAM_Format)
157 return;
158
159 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "on param changed\n");
160
161 struct spa_video_info_raw format;
162 spa_format_video_raw_parse(param, &format);
163
164 const int MAX_BUFFERS = 16;
165 int stride = vc_get_linesize(s->desc.width, s->desc.color_spec);
166
167 const struct spa_pod *params[1];
168 std::byte buffer[1024];
169 auto pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
170
171 params[0] = (spa_pod *) spa_pod_builder_add_object(&pod_builder,
172 SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
173 SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
174 SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
175 SPA_PARAM_BUFFERS_size, SPA_POD_Int(stride * s->desc.height),
176 SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<<SPA_DATA_MemFd),
177 SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride));
178
179 pw_stream_update_params(s->stream.get(), params, 1);
180}
181
182static void on_add_buffer(void *data, struct pw_buffer *buffer){
183 auto s = static_cast<display_pw_state *>(data);
184
185 int stride = vc_get_linesize(s->desc.width, s->desc.color_spec);
186 int size = stride * s->desc.height;
187
188 struct spa_data *d = buffer->buffer->datas;
189 if ((d->type & (1<<SPA_DATA_MemFd)) == 0) {
190 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Buffer doesn't support MemFd type\n");
191 return;
192 }
193
194 auto buf = memfd_buf_create(size);
195 buffer->user_data = memfd_buf_ref(buf);
196
197 d->type = SPA_DATA_MemFd;
198 d->flags = SPA_DATA_FLAG_READWRITE;
199 d->fd = buf->fd;
200 d->maxsize = buf->size;
201 d->mapoffset = 0;
202
203 auto ug_frame = vf_alloc_desc(s->desc);
204 buf->f = ug_frame;
205 buf->b = buffer;
206
207 ug_frame->callbacks.data_deleter = memfd_frame_data_deleter;
208 ug_frame->callbacks.dispose_udata = memfd_buf_ref(buf);
209 ug_frame->tiles[0].data = (char *) buf->ptr;
210
211 log_msg(LOG_LEVEL_VERBOSE, "Buffer added\n");
212}
213
214static void on_remove_buffer(void * /*data*/, struct pw_buffer *buffer){
215 auto buf = static_cast<memfd_buffer *>(buffer->user_data);
216
217 if(buf->f)
218 vf_free(buf->f);
219
220 buf->b = nullptr;
221 memfd_buf_unref(&buf);
222
223 log_msg(LOG_LEVEL_VERBOSE, "Buffer removed\n");
224}
225
226const static pw_stream_events stream_events = {
227 .version = PW_VERSION_STREAM_EVENTS,
228 .destroy = nullptr,
229 .state_changed = on_state_changed,
230 .control_info = nullptr,
231 .io_changed = nullptr,
232 .param_changed = on_param_changed,
233 .add_buffer = on_add_buffer,
234 .remove_buffer = on_remove_buffer,
235 .process = nullptr,
236 .drained = nullptr,
237#if PW_MAJOR > 0 || PW_MINOR > 3 || (PW_MINOR == 3 && PW_MICRO > 39)
238 .command = nullptr,
239 .trigger_done = nullptr,
240#endif
241};
242
243static void display_pw_help(){
244 color_printf("Pipewire video output.\n");
245 color_printf("Usage\n");
246 color_printf(TERM_BOLD TERM_FG_RED "\t-d pipewire" TERM_FG_RESET "[:target=<device>]\n" TERM_RESET);
247 color_printf("\n");
248
249 color_printf("Devices:\n");
250 print_devices("Stream/Input/Video");
251}
252
253static void *display_pw_init(struct module * /*parent*/, const char *cfg, unsigned int /*flags*/)
254{
255 auto s = std::make_unique<display_pw_state>();
256
257 std::string_view cfg_sv(cfg);
258 while(!cfg_sv.empty()){
259 auto tok = tokenize(cfg_sv, ':', '"');
260
261 auto key = tokenize(tok, '=');
262 auto val = tokenize(tok, '=');
263
264 if(key == "help"){
266 return INIT_NOERR;
267 } else if(key == "target"){
268 s->target = val;
269 }
270 }
271
273
274 log_msg(LOG_LEVEL_INFO, MOD_NAME "Compiled with libpipewire %s\n", pw_get_headers_version());
275 log_msg(LOG_LEVEL_INFO, MOD_NAME "Linked with libpipewire %s\n", pw_get_library_version());
276
277
278 return s.release();
279}
280
281static void display_pw_done(void *state)
282{
283 auto s = std::unique_ptr<display_pw_state>(static_cast<display_pw_state *>(state));
284
285 {
286 pipewire_thread_loop_lock_guard lock(s->pw.pipewire_loop.get());
287 pw_stream_disconnect(s->stream.get());
288 }
289
290 pw_thread_loop_stop(s->pw.pipewire_loop.get());
291}
292
293static struct video_frame *display_pw_getf(void *state)
294{
295 auto s = static_cast<display_pw_state *>(state);
296
297 auto get_dummy = [](display_pw_state *s){
298 if (!s->dummy_frame || video_desc_eq(video_desc_from_frame(s->dummy_frame.get()), s->desc))
299 {
300 s->dummy_frame.reset(vf_alloc_desc_data(s->desc));
301 }
302 return s->dummy_frame.get();
303 };
304
305 pipewire_thread_loop_lock_guard lock(s->pw.pipewire_loop.get());
306
307 const char *error = nullptr;
308 auto stream_state = pw_stream_get_state(s->stream.get(), &error);
309 if(stream_state != PW_STREAM_STATE_STREAMING)
310 return get_dummy(s);
311
312 struct pw_buffer *b = pw_stream_dequeue_buffer(s->stream.get());
313 if (!b) {
314 log_msg(LOG_LEVEL_WARNING, "Out of buffers!\n");
315 return get_dummy(s);
316 }
317
318 auto buf = static_cast<memfd_buffer *>(b->user_data);
319 s->in_flight_frame.reset(std::exchange(buf->f, nullptr));
320
321 b->buffer->datas[0].chunk->size = s->in_flight_frame->tiles[0].data_len;
322 b->buffer->datas[0].chunk->offset = 0;
323 b->buffer->datas[0].chunk->stride = vc_get_linesize(s->desc.width, s->desc.color_spec);
324
325 return s->in_flight_frame.get();
326}
327
328
329static bool display_pw_putf(void *state, struct video_frame *frame, long long flags)
330{
331 auto s = static_cast<display_pw_state *>(state);
332
333 if (flags == PUTF_DISCARD || frame == NULL || frame == s->dummy_frame.get()) {
334 return true;
335 }
336
337 pipewire_thread_loop_lock_guard lock(s->pw.pipewire_loop.get());
338
339 auto buf = static_cast<memfd_buffer *>(frame->callbacks.dispose_udata);
340
341 if(!buf->b){
342 //Frame is invalid - buffer got removed
343 return true;
344 }
345
346 assert(frame == s->in_flight_frame.get());
347 buf->f = s->in_flight_frame.release();
348
349 pw_stream_queue_buffer(s->stream.get(), buf->b);
350
351 return true;
352}
353
354static bool display_pw_get_property(void *state, int property, void *val, size_t *len)
355{
356 [[maybe_unused]] auto s = static_cast<display_pw_state *>(state);
357
358 codec_t codecs[] = {UYVY, RGB, RGBA, BGR};
359 int rgb_shift[] = {0, 8, 16};
360
361 switch (property) {
363 if(*len < sizeof(codecs)) {
364 return false;
365 }
366 *len = sizeof(codecs);
367 memcpy(val, codecs, *len);
368 break;
370 if(sizeof(rgb_shift) > *len) {
371 return false;
372 }
373 memcpy(val, rgb_shift, sizeof(rgb_shift));
374 *len = sizeof(rgb_shift);
375 break;
376 default:
377 return false;
378 }
379 return true;
380}
381
382static bool display_pw_reconfigure(void *state, struct video_desc desc)
383{
384 auto s = static_cast<display_pw_state *>(state);
385
386 s->desc = desc;
387
388 std::string node_name = "ultragrid_out_";
389 {
390 char buf[32];
391 snprintf(buf, sizeof(buf), "%ld", (long) getpid());
392 node_name += buf;
393 }
394
395 auto props = pw_properties_new(
396 PW_KEY_MEDIA_TYPE, "Video",
397 PW_KEY_MEDIA_CATEGORY, "Source",
398 PW_KEY_MEDIA_ROLE, "Communication",
399 PW_KEY_APP_NAME, "UltraGrid",
400 PW_KEY_APP_ICON_NAME, "ultragrid",
401 PW_KEY_NODE_NAME, node_name.c_str(),
402 PW_KEY_NODE_DESCRIPTION, "UltraGrid playback",
403 STREAM_TARGET_PROPERTY_KEY, s->target.c_str(),
404 nullptr);
405
406 pipewire_thread_loop_lock_guard lock(s->pw.pipewire_loop.get());
407
408 s->stream.reset(pw_stream_new(
409 s->pw.pipewire_core.get(),
410 "UltraGrid video out",
411 props));
412
413 pw_stream_add_listener(
414 s->stream.get(),
415 &s->stream_listener.get(),
417 s);
418
419 const struct spa_pod *params[1];
420
421 auto framerate = SPA_FRACTION(
422 static_cast<unsigned>(get_framerate_n(desc.fps)),
423 static_cast<unsigned>(get_framerate_d(desc.fps)));
424 auto size = SPA_RECTANGLE(desc.width, desc.height);
425
426 std::byte buffer[1024];
427 auto pod_builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
428
429 params[0] = static_cast<spa_pod *> (spa_pod_builder_add_object( &pod_builder,
430 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
431 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
432 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
433 SPA_FORMAT_VIDEO_format, SPA_POD_Id(pw_fmt_from_uv_codec(desc.color_spec)),
434 SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&size),
435 SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerate)));
436
437 pw_stream_connect(s->stream.get(),
438 PW_DIRECTION_OUTPUT,
439 PW_ID_ANY,
440 static_cast<pw_stream_flags>(
441 PW_STREAM_FLAG_AUTOCONNECT |
442 PW_STREAM_FLAG_DRIVER |
443 PW_STREAM_FLAG_ALLOC_BUFFERS),
444 &params[0], 1);
445
446 return true;
447}
448
449static void display_pw_probe(struct device_info **available_cards, int *count, void (**deleter)(void *)) {
451 *available_cards = NULL;
452 *count = 0;
453}
454
455static const struct video_display_info display_pw_info = {
458 NULL, // _run
464 NULL, // _put_audio_frame
465 NULL, // _reconfigure_audio
466 MOD_NAME,
467};
468
#define UNUSED(x)
void ** state
int void * val
struct video_desc desc
struct video_frame * frame
int void size_t * len
const char unsigned int flags
int void(** deleter)(void *))
int property
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(struct video_desc desc)
Allocates video frame accordig given desc.
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
static void on_state_changed(void *state, enum pw_stream_state old, enum pw_stream_state new_state, const char *error)
Definition pipewire.cpp:162
static const pw_stream_events stream_events
Definition pipewire.cpp:177
static void on_param_changed(void *state, uint32_t id, const struct spa_pod *param)
Definition pipewire.cpp:103
#define MOD_NAME
Definition audio.cpp:110
int color_printf(const char *format,...)
#define TERM_BOLD
Definition color_out.h:48
#define TERM_FG_RED
Definition color_out.h:50
#define TERM_RESET
Definition color_out.h:47
#define TERM_FG_RESET
Definition color_out.h:59
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_NOTICE
information that may be interesting
Definition debug.h:61
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
#define PUTF_DISCARD
#define VIDEO_DISPLAY_ABI_VERSION
@ DISPLAY_PROPERTY_RGB_SHIFT
red,green,blue shift - int[3] (bits)
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
int count
Definition hd-rum.c:44
#define INIT_NOERR
Definition host.h:73
@ LIBRARY_CLASS_VIDEO_DISPLAY
Definition lib_common.h:79
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
Definition lib_common.h:144
int get_framerate_d(double fps)
Definition misc.cpp:190
int get_framerate_n(double fps)
Definition misc.cpp:184
void print_devices(std::string_view media_class)
bool initialize_pw_common(pipewire_state_common &s, int fd)
raii_uniq_handle< pw_stream, pw_stream_destroy > pw_stream_uniq
spa_video_format pw_fmt_from_uv_codec(codec_t codec)
#define STREAM_TARGET_PROPERTY_KEY
const char * param
Definition settings.cpp:195
std::string_view tokenize(std::string_view &str, char delim, char quot)
Tokenizer for string_view.
unique_frame dummy_frame
Definition pipewire.cpp:133
pipewire_state_common pw
Definition pipewire.cpp:126
spa_hook_uniq stream_listener
Definition pipewire.cpp:129
pw_stream_uniq stream
Definition pipewire.cpp:128
std::string target
Definition pipewire.cpp:131
video_desc desc
Definition pipewire.cpp:136
unique_frame in_flight_frame
Definition pipewire.cpp:134
void operator()(video_frame *f)
Definition pipewire.cpp:77
codec_t color_spec
Definition types.h:182
double fps
Definition types.h:183
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct video_frame_callbacks callbacks
Definition types.h:324
codec_t
Definition types.h:65
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ UYVY
YCbCr 422 8-bit - Cb Y0 Cr Y1.
Definition types.h:71
@ BGR
8-bit BGR
Definition types.h:88
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
This is an umbrella header for video functions.
static void on_remove_buffer(void *, struct pw_buffer *)
Definition pipewire.cpp:322
static void on_add_buffer(void *, struct pw_buffer *)
Definition pipewire.cpp:317
std::unique_ptr< video_frame, frame_deleter > unique_frame
Definition pipewire.cpp:78
const char * key
Definition cineform.cpp:125
static struct @76 codecs[]
static struct video_frame * display_pw_getf(void *state)
Definition pipewire.cpp:293
static bool display_pw_get_property(void *state, int property, void *val, size_t *len)
Definition pipewire.cpp:354
static bool display_pw_putf(void *state, struct video_frame *frame, long long flags)
Definition pipewire.cpp:329
static void * display_pw_init(struct module *, const char *cfg, unsigned int)
Definition pipewire.cpp:253
static bool display_pw_reconfigure(void *state, struct video_desc desc)
Definition pipewire.cpp:382
static void display_pw_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
Definition pipewire.cpp:449
static void display_pw_help()
Definition pipewire.cpp:243
static const struct video_display_info display_pw_info
Definition pipewire.cpp:455
static void display_pw_done(void *state)
Definition pipewire.cpp:281
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.