UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
sdl2.c
Go to the documentation of this file.
1
7/*
8 * Copyright (c) 2018-2024 CESNET
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, is permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 *
22 * 3. Neither the name of CESNET nor the names of its contributors may be
23 * used to endorse or promote products derived from this software without
24 * specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
27 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
28 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
29 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
30 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
31 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
36 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
37 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 */
48#ifdef __arm64__
49#define SDL_DISABLE_MMINTRIN_H 1
50#define SDL_DISABLE_IMMINTRIN_H 1
51#endif // defined __arm64__
52#if __has_include(<SDL2/SDL.h>)
53#include <SDL2/SDL.h>
54#else
55#include <SDL.h>
56#endif
57
58#include <assert.h> // for assert
59#include <ctype.h> // for toupper
60#include <math.h> // for sqrt
61#include <pthread.h> // for pthread_mutex_unlock, pthread_mutex_lock
62#include <stdbool.h> // for true, bool, false
63#include <stdint.h> // for int64_t, uint32_t
64#include <stdio.h> // for printf, sscanf, snprintf
65#include <stdlib.h> // for atoi, free, calloc
66#include <string.h> // for NULL, strlen, strcmp, strstr, strchr
67#include <time.h> // for timespec_get, TIME_UTC, timespec
68
69#include "compat/htonl.h" // for htonl
70#include "debug.h" // for log_msg, LOG_LEVEL_ERROR, LOG_LEVEL_W...
71#include "host.h" // for get_commandline_param, exit_uv, ADD_T...
72#include "keyboard_control.h" // for keycontrol_register_key, keycontrol_s...
73#include "lib_common.h" // for REGISTER_MODULE, library_class
74#include "messaging.h" // for new_response, msg_universal, RESPONSE...
75#include "module.h" // for module, get_root_module, module_done
76#include "tv.h" // for ts_add_nsec
77#include "types.h" // for video_desc, tile, video_frame, device...
78#include "utils/color_out.h" // for color_printf, TBOLD, TRED
79#include "utils/list.h" // for simple_linked_list_append, simple_lin...
80#include "utils/macros.h" // for STR_LEN
81#include "video_codec.h" // for get_codec_name, codec_is_planar, vc_d...
82#include "video_display.h" // for display_property, get_splashscreen
83#include "video_frame.h" // for vf_free, vf_alloc_desc, video_desc_fr...
84
85#define SDL2_DEINTERLACE_IMPOSSIBLE_MSG_ID 0x327058e5
86#define MAGIC_SDL2 0x3cc234a1
87#define BUFFER_COUNT 2
88#define MOD_NAME "[SDL] "
89
90struct state_sdl2;
91
92static void show_help(const char *driver);
93static void display_frame(struct state_sdl2 *s, struct video_frame *frame);
94static struct video_frame *display_sdl2_getf(void *state);
95static void display_sdl2_new_message(struct module *mod);
96static bool display_sdl2_reconfigure_real(void *state, struct video_desc desc);
97static void loadSplashscreen(struct state_sdl2 *s);
98
100
137
138static const char *deint_to_string(enum deint val) {
139 switch (val) {
140 case DEINT_OFF: return "OFF";
141 case DEINT_ON: return "ON";
142 case DEINT_FORCE: return "FORCE";
143 }
144 return NULL;
145}
146
147static const struct {
148 char key;
149 const char *description;
150} keybindings[] = {
151 {'d', "toggle deinterlace"},
152 {'f', "toggle fullscreen"},
153 {'q', "quit"},
155
156#define SDL_CHECK(cmd) do { int ret = cmd; if (ret < 0) { log_msg(LOG_LEVEL_ERROR, MOD_NAME "Error (%s): %s\n", #cmd, SDL_GetError());} } while(0)
157
158static void display_frame(struct state_sdl2 *s, struct video_frame *frame)
159{
160 if (!frame) {
161 return;
162 }
163
164 SDL_Texture *texture = (SDL_Texture *) frame->callbacks.dispose_udata;
166 size_t pitch = vc_get_linesize(frame->tiles[0].width, frame->color_spec);
167 if (!vc_deinterlace_ex(frame->color_spec, (unsigned char *) frame->tiles[0].data, pitch, (unsigned char *) frame->tiles[0].data, pitch, frame->tiles[0].height)) {
168 log_msg_once(LOG_LEVEL_ERROR, SDL2_DEINTERLACE_IMPOSSIBLE_MSG_ID, MOD_NAME "Cannot deinterlace, unsupported pixel format '%s'!\n", get_codec_name(frame->color_spec));
169 }
170 }
171
172 SDL_RenderClear(s->renderer);
173 SDL_UnlockTexture(texture);
174 SDL_CHECK(SDL_RenderCopy(s->renderer, texture, NULL, NULL));
175 SDL_RenderPresent(s->renderer);
176
177 int pitch = 0;
178 SDL_CHECK(SDL_LockTexture(texture, NULL, (void **) &frame->tiles[0].data, &pitch));
179 assert(pitch == s->texture_pitch);
180
181 if (frame == s->last_frame) {
182 return; // we are only redrawing on window resize
183 }
184
185 pthread_mutex_lock(&s->lock);
187 pthread_mutex_unlock(&s->lock);
188 pthread_cond_signal(&s->frame_consumed_cv);
189 s->last_frame = frame;
190}
191
192static int64_t translate_sdl_key_to_ug(SDL_Keysym sym) {
193 sym.mod &= ~(KMOD_NUM | KMOD_CAPS); // remove num+caps lock modifiers
194
195 // ctrl alone -> do not interpret
196 if (sym.sym == SDLK_LCTRL || sym.sym == SDLK_RCTRL) {
197 return 0;
198 }
199
200 bool ctrl = false;
201 bool shift = false;
202 if (sym.mod & KMOD_CTRL) {
203 ctrl = true;
204 }
205 sym.mod &= ~KMOD_CTRL;
206
207 if (sym.mod & KMOD_SHIFT) {
208 shift = true;
209 }
210 sym.mod &= ~KMOD_SHIFT;
211
212 if (sym.mod != 0) {
213 return -1;
214 }
215
216 if ((sym.sym & SDLK_SCANCODE_MASK) == 0) {
217 if (shift) {
218 sym.sym = toupper(sym.sym);
219 }
220 return ctrl ? K_CTRL(sym.sym) : sym.sym;
221 }
222 switch (sym.sym) {
223 case SDLK_RIGHT: return K_RIGHT;
224 case SDLK_LEFT: return K_LEFT;
225 case SDLK_DOWN: return K_DOWN;
226 case SDLK_UP: return K_UP;
227 case SDLK_PAGEDOWN: return K_PGDOWN;
228 case SDLK_PAGEUP: return K_PGUP;
229 }
230 return -1;
231}
232
233static bool display_sdl2_process_key(struct state_sdl2 *s, int64_t key)
234{
235 switch (key) {
236 case 'd':
238 log_msg(LOG_LEVEL_INFO, "Deinterlacing: %s\n", deint_to_string(s->deinterlace));
239 return true;
240 case 'f':
241 s->fs = !s->fs;
242 SDL_SetWindowFullscreen(s->window, s->fs ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
243 return true;
244 case 'q':
245 exit_uv(0);
246 return true;
247 default:
248 return false;
249 }
250}
251
252static void display_sdl2_run(void *arg)
253{
254 struct state_sdl2 *s = (struct state_sdl2 *) arg;
255
257
258 while (1) {
259 SDL_Event sdl_event;
260 if (!SDL_WaitEvent(&sdl_event)) {
261 log_msg(LOG_LEVEL_ERROR, MOD_NAME "SDL_WaitEvent error: %s\n", SDL_GetError());
262 continue;
263 }
264 if (sdl_event.type == s->sdl_user_reconfigure_event) {
265 pthread_mutex_lock(&s->lock);
266 struct video_desc desc = *(struct video_desc *) sdl_event.user.data1;
268 pthread_mutex_unlock(&s->lock);
269 pthread_cond_signal(&s->reconfigured_cv);
270
271 } else if (sdl_event.type == s->sdl_user_new_frame_event) {
272 if (sdl_event.user.data1 == NULL) { // poison pill received
273 break;
274 }
275 display_frame(s, (struct video_frame *) sdl_event.user.data1);
276 } else if (sdl_event.type == s->sdl_user_new_message_event) {
277 struct msg_universal *msg;
278 while ((msg = (struct msg_universal *) check_message(&s->mod))) {
279 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Received message: %s\n", msg->text);
280 struct response *r;
281 int key;
282 if (strstr(msg->text, "win-title ") == msg->text) {
283 SDL_SetWindowTitle(s->window, msg->text + strlen("win-title "));
284 r = new_response(RESPONSE_OK, NULL);
285 } else if (sscanf(msg->text, "%d", &key) == 1) {
286 if (!display_sdl2_process_key(s, key)) {
287 r = new_response(RESPONSE_BAD_REQUEST, "Unsupported key for SDL");
288 } else {
289 r = new_response(RESPONSE_OK, NULL);
290 }
291 } else {
292 r = new_response(RESPONSE_BAD_REQUEST, "Wrong command");
293 }
294
295 free_message((struct message*) msg, r);
296 }
297 } else if (sdl_event.type == SDL_KEYDOWN) {
298 log_msg(LOG_LEVEL_VERBOSE, MOD_NAME "Pressed key %s (scancode: %d, sym: %d, mod: %d)!\n", SDL_GetKeyName(sdl_event.key.keysym.sym), sdl_event.key.keysym.scancode, sdl_event.key.keysym.sym, sdl_event.key.keysym.mod);
299 int64_t sym = translate_sdl_key_to_ug(sdl_event.key.keysym);
300 if (sym > 0) {
301 if (!display_sdl2_process_key(s, sym)) { // unknown key -> pass to control
303 }
304 } else if (sym == -1) {
305 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Cannot translate key %s (scancode: %d, sym: %d, mod: %d)!\n", SDL_GetKeyName(sdl_event.key.keysym.sym), sdl_event.key.keysym.scancode, sdl_event.key.keysym.sym, sdl_event.key.keysym.mod);
306 }
307 } else if (sdl_event.type == SDL_WINDOWEVENT) {
308 // https://forums.libsdl.org/viewtopic.php?p=38342
309 if (s->keep_aspect && sdl_event.window.event == SDL_WINDOWEVENT_RESIZED) {
310 double area = sdl_event.window.data1 * sdl_event.window.data2;
311 int width = sqrt(area / ((double) s->current_display_desc.height / s->current_display_desc.width));
312 int height = sqrt(area / ((double) s->current_display_desc.width / s->current_display_desc.height));
313 SDL_SetWindowSize(s->window, width, height);
314 debug_msg("[SDL] resizing to %d x %d\n", width, height);
315 }
316 if (sdl_event.window.event == SDL_WINDOWEVENT_EXPOSED
317 || sdl_event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
318 // clear both buffers
319 SDL_RenderClear(s->renderer);
321 SDL_RenderClear(s->renderer);
323 }
324 } else if (sdl_event.type == SDL_QUIT) {
325 exit_uv(0);
326 }
327 }
328}
329
330static void sdl2_print_displays() {
331 for (int i = 0; i < SDL_GetNumVideoDisplays(); ++i) {
332 if (i > 0) {
333 printf(", ");
334 }
335 const char *dname = SDL_GetDisplayName(i);
336 if (dname == NULL) {
337 dname = SDL_GetError();
338 }
339 color_printf(TBOLD("%d") " - %s", i, dname);
340 }
341 printf("\n");
342}
343
344static void
345show_help(const char *driver)
346{
347 SDL_CHECK(SDL_VideoInit(driver));
348 printf("SDL options:\n");
350 TRED("\t-d sdl") "[[:fs|:d|:display=<didx>|:driver=<drv>|:novsync|:"
351 "renderer=<ridx>|:nodecorate|:size[=WxH]|:window_"
352 "flags=<f>|:keep-aspect]*|:help]") "\n");
353 printf("where:\n");
354 color_printf(TBOLD("\td[force]") " - deinterlace (force even for progresive video)\n");
355 color_printf(TBOLD("\t fs") " - fullscreen\n");
356 color_printf(TBOLD("\t <didx>") " - display index, available indices: ");
358 color_printf(TBOLD("\t <drv>") " - one of following: ");
359 for (int i = 0; i < SDL_GetNumVideoDrivers(); ++i) {
360 color_printf("%s" TBOLD("%s"), (i == 0 ? "" : ", "), SDL_GetVideoDriver(i));
361 }
362 color_printf("\n");
363 color_printf(TBOLD(" keep-aspect") " - keep window aspect ratio respecive to the video\n");
364 color_printf(TBOLD(" novsync") " - disable sync on VBlank\n");
365 color_printf(TBOLD(" nodecorate") " - disable window border\n");
367 TBOLD(" size") " - window size in pixels with optional "
368 "position\n"
369 " "
370 "(syntax: " TBOLD(
371 "[<W>x<H>][{+-}<X>[{+-}<Y>]]") ")\n");
372 color_printf(TBOLD("\t <renderer>") " - renderer, one of:");
373 for (int i = 0; i < SDL_GetNumRenderDrivers(); ++i) {
374 SDL_RendererInfo renderer_info;
375 if (SDL_GetRenderDriverInfo(i, &renderer_info) == 0) {
376 color_printf("%s" TBOLD("%s"), (i == 0 ? " " : ", "),
377 renderer_info.name);
378 }
379 }
380 printf("\n");
381 printf("\nKeyboard shortcuts:\n");
382 for (unsigned int i = 0; i < sizeof keybindings / sizeof keybindings[0]; ++i) {
383 color_printf("\t" TBOLD("'%c'") "\t - %s\n", keybindings[i].key, keybindings[i].description);
384 }
385 SDL_version ver;
386 SDL_GetVersion(&ver);
387 printf("\nSDL version (linked): %" PRIu8 ".%" PRIu8 ".%" PRIu8 "\n",
388 ver.major, ver.minor, ver.patch);
389 SDL_VideoQuit();
390 SDL_Quit();
391}
392
394{
395 struct state_sdl2 *s = (struct state_sdl2 *) state;
396
398 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Receiving interlaced video but deinterlacing is off - suggesting toggling it on (press 'd' or pass cmdline option)\n");
399 }
400 if (desc.color_spec == R10k) {
401 MSG(WARNING,
402 "Displaying 10-bit RGB, which is experimental. In case of "
403 "problems use '--param decoder-use-codec='!R10k'` and please report.\n");
404 }
405
406 pthread_mutex_lock(&s->lock);
407
408 SDL_Event event;
409 event.type = s->sdl_user_reconfigure_event;
410 event.user.data1 = &desc;
411 SDL_CHECK(SDL_PushEvent(&event));
412
414 while (s->reconfiguration_status == -1) {
415 pthread_cond_wait(&s->reconfigured_cv, &s->lock);
416 }
417 pthread_mutex_unlock(&s->lock);
418
419 return s->reconfiguration_status;
420}
421
422static const struct {
424 uint32_t second;
425} pf_mapping[] = {
426 { I420, SDL_PIXELFORMAT_IYUV },
427 { UYVY, SDL_PIXELFORMAT_UYVY },
428 { YUYV, SDL_PIXELFORMAT_YUY2 },
429 { RGB, SDL_PIXELFORMAT_RGB24 },
430 { BGR, SDL_PIXELFORMAT_BGR24 },
431#if SDL_COMPILEDVERSION >= SDL_VERSIONNUM(2, 0, 5)
432 { RGBA, SDL_PIXELFORMAT_RGBA32 },
433#else
434 { RGBA, SDL_PIXELFORMAT_ABGR8888 },
435#endif
436 { R10k, SDL_PIXELFORMAT_ARGB2101010 },
438
440 for (unsigned int i = 0; i < sizeof pf_mapping / sizeof pf_mapping[0]; ++i) {
441 if (pf_mapping[i].first == ug_codec) {
442 return pf_mapping[i].second;
443 }
444 }
446 return SDL_PIXELFORMAT_UNKNOWN;
447}
448
450 const int count = sizeof pf_mapping / sizeof pf_mapping[0];
451
452 for (int i = 0; i < count; ++i) {
453 codecs[i] = pf_mapping[i].first;
454 }
455 return count;
456}
457
458static void cleanup_frames(struct state_sdl2 *s) {
459 s->last_frame = NULL;
460 struct video_frame *buffer = NULL;
461 while ((buffer = simple_linked_list_pop(s->free_frame_queue)) != NULL) {
462 vf_free(buffer);
463 }
464}
465
466static void vf_sdl_texture_data_deleter(struct video_frame *buf) {
467 SDL_Texture *texture = (SDL_Texture *) buf->callbacks.dispose_udata;
468 SDL_DestroyTexture(texture);
469}
470
471static bool recreate_textures(struct state_sdl2 *s, struct video_desc desc) {
473
474 for (int i = 0; i < BUFFER_COUNT; ++i) {
475 SDL_Texture *texture = SDL_CreateTexture(s->renderer, get_ug_to_sdl_format(desc.color_spec), SDL_TEXTUREACCESS_STREAMING, desc.width, desc.height);
476 if (!texture) {
477 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unable to create texture: %s\n", SDL_GetError());
478 return false;
479 }
480 struct video_frame *f = vf_alloc_desc(desc);
481 f->callbacks.dispose_udata = (void *) texture;
482 SDL_CHECK(SDL_LockTexture(texture, NULL, (void **) &f->tiles[0].data, &s->texture_pitch));
486 }
487
488 return true;
489}
490
491static bool
493{
494 struct state_sdl2 *s = (struct state_sdl2 *)state;
495
496 log_msg(LOG_LEVEL_NOTICE, "[SDL] Reconfigure to size %dx%d\n", desc.width,
497 desc.height);
498
499 if (s->fixed_size && s->window) {
500 SDL_RenderSetLogicalSize(s->renderer, desc.width, desc.height);
501 return recreate_textures(s, desc);
502 }
503
504 if (s->window) {
505 SDL_DestroyWindow(s->window);
506 }
507 int flags = s->window_flags | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;
508 if (s->fs) {
509 flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
510 }
511 const char *window_title = "UltraGrid - SDL2 Display";
512 if (get_commandline_param("window-title")) {
513 window_title = get_commandline_param("window-title");
514 }
515 int width = s->fixed_w ? s->fixed_w : desc.width;
516 int height = s->fixed_h ? s->fixed_h : desc.height;
517 int x = s->x == SDL_WINDOWPOS_UNDEFINED ? (int) SDL_WINDOWPOS_CENTERED_DISPLAY(s->display_idx) : s->x;
518 int y = s->y == SDL_WINDOWPOS_UNDEFINED ? (int) SDL_WINDOWPOS_CENTERED_DISPLAY(s->display_idx) : s->y;
519 s->window = SDL_CreateWindow(window_title, x, y, width, height, flags);
520 if (!s->window) {
521 log_msg(LOG_LEVEL_ERROR, "[SDL] Unable to create window: %s\n", SDL_GetError());
522 return false;
523 }
524
525 if (s->renderer) {
526 SDL_DestroyRenderer(s->renderer);
527 }
528 s->renderer = SDL_CreateRenderer(s->window, s->renderer_idx, SDL_RENDERER_ACCELERATED | (s->vsync ? SDL_RENDERER_PRESENTVSYNC : 0));
529 if (!s->renderer) {
530 log_msg(LOG_LEVEL_ERROR, "[SDL] Unable to create renderer: %s\n", SDL_GetError());
531 return false;
532 }
533 SDL_RendererInfo renderer_info;
534 if (SDL_GetRendererInfo(s->renderer, &renderer_info) == 0) {
535 log_msg(LOG_LEVEL_NOTICE, "[SDL] Using renderer: %s\n", renderer_info.name);
536 }
537
538 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
539 SDL_RenderSetLogicalSize(s->renderer, desc.width, desc.height);
540
541 if (!recreate_textures(s, desc)) {
542 return false;
543 }
544
546
547 return true;
548}
549
550static void loadSplashscreen(struct state_sdl2 *s) {
553 MSG(WARNING, "Cannot render splashscreeen!\n");
554 vf_free(frame);
555 return;
556 }
557 struct video_frame *splash = display_sdl2_getf(s);
558 memcpy(splash->tiles[0].data, frame->tiles[0].data, frame->tiles[0].data_len);
559 vf_free(frame);
560 display_frame(s, splash); // don't be tempted to use _putf() - it will use event queue and there may arise a race-condition with recv thread
561}
562
563static bool set_size(struct state_sdl2 *s, const char *tok)
564{
565 if (strstr(tok, "fixed_size=") == tok) {
567 MOD_NAME "fixed_size with dimensions is "
568 " deprecated, use size"
569 " instead\n");
570 }
571 tok = strchr(tok, '=') + 1;
572 if (strpbrk(tok, "x+-") == NULL) {
573 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Wrong size spec: %s\n", tok);
574 return false;
575 }
576 if (strchr(tok, 'x') != NULL) {
577 s->fixed_size = true;
578 s->fixed_w = atoi(tok);
579 s->fixed_h = atoi(strchr(tok, 'x') + 1);
580 }
581 tok = strpbrk(tok, "+-");
582 if (tok != NULL) {
583 s->x = atoi(tok);
584 tok = strpbrk(tok + 1, "+-");
585 }
586 if (tok != NULL) {
587 s->y = atoi(tok);
588 }
589 return true;
590}
591
597static int
598get_renderer_idx(const char *renderer)
599{
600 if (renderer == NULL) {
601 return -1; // default
602 }
603 const int renderer_cnt = SDL_GetNumRenderDrivers();
604
605 char *endptr = NULL;
606 const long number = strtol(renderer, &endptr, 0);
607 if (*endptr == '\0') { // valid number
608 if (number < 0 || number >= renderer_cnt) {
609 MSG(ERROR,
610 "Invalid renderer index - valid range [0,%d], got "
611 "%ld\n",
612 renderer_cnt, number);
613 return -2;
614 }
615 return (int) number;
616 }
617
618 for (int i = 0; i < renderer_cnt; ++i) {
619 SDL_RendererInfo renderer_info;
620 if (SDL_GetRenderDriverInfo(i, &renderer_info) == 0) {
621 if (strcmp(renderer, renderer_info.name) == 0) {
622 return i;
623 }
624 }
625 }
626
627 MSG(ERROR, "Unknown renderer name: %s\n", renderer);
628 return -2;
629}
630
631static void *display_sdl2_init(struct module *parent, const char *fmt, unsigned int flags)
632{
634 log_msg(LOG_LEVEL_ERROR, "UltraGrid SDL2 module currently doesn't support audio!\n");
635 return NULL;
636 }
637 const char *driver = NULL;
638 const char *renderer = NULL;
639 struct state_sdl2 *s = calloc(1, sizeof *s);
640
641 s->x = s->y = SDL_WINDOWPOS_UNDEFINED;
642 s->vsync = true;
643
644 if (fmt == NULL) {
645 fmt = "";
646 }
647 char buf[STR_LEN];
648 snprintf(buf, sizeof buf, "%s", fmt);
649 char *tmp = buf;
650 char *tok, *save_ptr;
651 while((tok = strtok_r(tmp, ":", &save_ptr)))
652 {
653 if (strcmp(tok, "d") == 0 || strcmp(tok, "dforce") == 0) {
654 s->deinterlace = strcmp(tok, "d") == 0 ? DEINT_ON : DEINT_OFF;
655 } else if (IS_KEY_PREFIX(tok, "display")) {
656 s->display_idx = atoi(strchr(tok, '=') + 1);
657 } else if (IS_KEY_PREFIX(tok, "driver")) {
658 driver = strchr(tok, '=') + 1;;
659 } else if (IS_PREFIX(tok, "fs")) {
660 s->fs = true;
661 } else if (IS_PREFIX(tok, "help")) {
662 show_help(driver);
663 free(s);
664 return INIT_NOERR;
665 } else if (IS_PREFIX(tok, "novsync")) {
666 s->vsync = false;
667 } else if (IS_PREFIX(tok, "nodecorate")) {
668 s->window_flags |= SDL_WINDOW_BORDERLESS;
669 } else if (IS_PREFIX(tok, "keep-aspect")) {
670 s->keep_aspect = true;
671 } else if (IS_KEY_PREFIX(tok, "fixed_size") ||
672 IS_KEY_PREFIX(tok, "siz=")) {
673 if (!set_size(s, tok)) {
674 free(s);
675 return NULL;
676 }
677 } else if (strcmp(tok, "fixed_size") == 0) {
679 MOD_NAME "fixed_size deprecated, use size with "
680 "dimensions\n");
681 s->fixed_size = true;
682 } else if (IS_KEY_PREFIX(tok, "window_flags")) {
683 int f;
684 if (sscanf(strchr(tok, '=') + 1, "%i", &f) != 1) {
685 log_msg(LOG_LEVEL_ERROR, "Wrong window_flags: %s\n", tok);
686 free(s);
687 return NULL;
688 }
689 s->window_flags |= f;
690 } else if (IS_KEY_PREFIX(tok, "position")) {
691 tok = strchr(tok, '=') + 1;
692 if (strchr(tok, ',') == NULL) {
693 log_msg(LOG_LEVEL_ERROR, "[SDL] position: %s\n", tok);
694 free(s);
695 return NULL;
696 }
697 s->x = atoi(tok);
698 s->y = atoi(strchr(tok, ',') + 1);
700 MOD_NAME "pos is deprecated, use "
701 "\"size=%+d%+d\" instead.\n",
702 s->x, s->y);
703 } else if (IS_KEY_PREFIX(tok, "renderer")) {
704 renderer = strchr(tok, '=') + 1;
705 } else {
706 log_msg(LOG_LEVEL_ERROR, "[SDL] Wrong option: %s\n", tok);
707 free(s);
708 return NULL;
709 }
710 tmp = NULL;
711 }
712
714 if (s->renderer_idx == -2) {
715 return NULL;
716 }
717
718#ifdef __linux__
719 if (driver == NULL && getenv("DISPLAY") == NULL &&
720 getenv("WAYLAND_DISPLAY") == NULL) {
721 MSG(NOTICE, "X11/Wayland doesn't seem to be running (according "
722 "to env vars). Setting driver=KMSDRM.\n");
723 driver = "KMSDRM";
724 }
725#endif // defined __linux__
726
727 if (SDL_VideoInit(driver) < 0) {
728 MSG(ERROR, "Unable to initialize SDL2 video: %s\n",
729 SDL_GetError());
730 free(s);
731 return NULL;
732 }
733 if (SDL_Init(SDL_INIT_EVENTS) < 0) {
734 MSG(ERROR, "Unable to initialize SDL2 events: %s\n",
735 SDL_GetError());
736 free(s);
737 return NULL;
738 }
739 log_msg(LOG_LEVEL_NOTICE, "[SDL] Using driver: %s\n", SDL_GetCurrentVideoDriver());
740
741 SDL_ShowCursor(SDL_DISABLE);
742 SDL_DisableScreenSaver();
743
749
750 pthread_mutex_init(&s->lock, NULL);
751 pthread_cond_init(&s->frame_consumed_cv, NULL);
752 pthread_cond_init(&s->reconfigured_cv , NULL);
753
754 s->sdl_user_new_frame_event = SDL_RegisterEvents(3);
755 assert(s->sdl_user_new_frame_event != (Uint32) -1);
758
760
761 for (unsigned int i = 0; i < sizeof keybindings / sizeof keybindings[0]; ++i) {
762 if (keybindings[i].key == 'q') { // don't report 'q' to avoid accidental close - user can use Ctrl-c there
763 continue;
764 }
765 char key_str[128];
766 snprintf(key_str, sizeof key_str, "%d", keybindings[i].key);
767 keycontrol_register_key(&s->mod, keybindings[i].key, key_str, keybindings[i].description);
768 }
769
770 log_msg(LOG_LEVEL_NOTICE, "SDL2 initialized successfully.\n");
771
772 return (void *) s;
773}
774
775static void display_sdl2_done(void *state)
776{
777 struct state_sdl2 *s = (struct state_sdl2 *)state;
778
779 assert(s->mod.priv_magic == MAGIC_SDL2);
780
782
783 if (s->renderer) {
784 SDL_DestroyRenderer(s->renderer);
785 }
786
787 if (s->window) {
788 SDL_DestroyWindow(s->window);
789 }
790
791 SDL_ShowCursor(SDL_ENABLE);
792
793 SDL_VideoQuit();
794 SDL_QuitSubSystem(SDL_INIT_EVENTS);
795 SDL_Quit();
796
798
799 pthread_mutex_destroy(&s->lock);
800 pthread_cond_destroy(&s->frame_consumed_cv);
801 pthread_cond_destroy(&s->reconfigured_cv);
802
803 module_done(&s->mod);
804
805 free(s);
806}
807
809{
810 struct state_sdl2 *s = (struct state_sdl2 *)state;
811 assert(s->mod.priv_magic == MAGIC_SDL2);
812
813 pthread_mutex_lock(&s->lock);
815 pthread_cond_wait(&s->frame_consumed_cv, &s->lock);
816 }
818 pthread_mutex_unlock(&s->lock);
819
820 return buffer;
821}
822
823static void
824r10k_to_sdl2(size_t count, uint32_t *buf)
825{
826 enum {
827 LOOP_ITEMS = 16,
828 };
829 unsigned int i = 0;
830 for (; i < count / LOOP_ITEMS; ++i) {
831 for (int j = 0; j < LOOP_ITEMS; ++j) {
832 uint32_t val = htonl(*buf);
833 *buf++ = val >> 2;
834 }
835 }
836 i *= LOOP_ITEMS;
837 for (; i < count; ++i) {
838 uint32_t val = htonl(*buf);
839 *buf++ = val >> 2;
840 }
841}
842
843static bool display_sdl2_putf(void *state, struct video_frame *frame, long long timeout_ns)
844{
845 struct state_sdl2 *s = (struct state_sdl2 *)state;
846
847 assert(s->mod.priv_magic == MAGIC_SDL2);
848
849 if (frame == NULL) { // posion pill
850 SDL_Event event;
851 event.type = s->sdl_user_new_frame_event;
852 event.user.data1 = NULL;
853 SDL_CHECK(SDL_PushEvent(&event));
854 return true;
855 }
856
857 // fix endianity
858 if (frame->color_spec == R10k) {
860 (uint32_t *) frame->tiles[0].data);
861 }
862
863 pthread_mutex_lock(&s->lock);
864 if (timeout_ns == PUTF_DISCARD) {
865 assert(frame != NULL);
867 pthread_mutex_unlock(&s->lock);
868 return true;
869 }
870
871 if (timeout_ns > 0) {
872 int rc = 0;
873 while (rc == 0 && simple_linked_list_size(s->free_frame_queue) == 0) {
874 if (timeout_ns == PUTF_BLOCKING) {
875 rc = pthread_cond_wait(&s->frame_consumed_cv, &s->lock);
876 } else {
877 struct timespec ts;
879 ts_add_nsec(&ts, timeout_ns);
880 rc = pthread_cond_timedwait(&s->frame_consumed_cv, &s->lock, &ts);
881 }
882 }
883 }
886 log_msg(LOG_LEVEL_INFO, MOD_NAME "1 frame(s) dropped!\n");
887 pthread_mutex_unlock(&s->lock);
888 return false;
889 }
890 pthread_mutex_unlock(&s->lock);
891 SDL_Event event;
892 event.type = s->sdl_user_new_frame_event;
893 event.user.data1 = frame;
894 SDL_CHECK(SDL_PushEvent(&event));
895
896 return true;
897}
898
899static bool display_sdl2_get_property(void *state, int property, void *val, size_t *len)
900{
901 struct state_sdl2 *s = (struct state_sdl2 *) state;
903 size_t codecs_len = get_supported_pfs(codecs) * sizeof(codec_t);
904
905 switch (property) {
907 if (codecs_len <= *len) {
908 memcpy(val, codecs, codecs_len);
909 *len = codecs_len;
910 } else {
911 return false;
912 }
913 break;
916 *len = sizeof(int);
917 break;
918 default:
919 return false;
920 }
921 return true;
922}
923
925{
926 struct state_sdl2 *s = (struct state_sdl2 *) mod;
927
928 SDL_Event event;
929 event.type = s->sdl_user_new_message_event;
930 SDL_CHECK(SDL_PushEvent(&event));
931}
932
933static void display_sdl2_probe(struct device_info **available_cards, int *count, void (**deleter)(void *)) {
935 *count = 1;
936 *available_cards = (struct device_info *) calloc(1, sizeof(struct device_info));
937 strcpy((*available_cards)[0].dev, "");
938 strcpy((*available_cards)[0].name, "SDL2 SW display");
939 dev_add_option(&(*available_cards)[0], "Deinterlace", "Deinterlace", "deinterlace", ":d", true);
940 dev_add_option(&(*available_cards)[0], "Fullscreen", "Launch as fullscreen", "fullscreen", ":fs", true);
941 dev_add_option(&(*available_cards)[0], "No decorate", "Disable window decorations", "nodecorate", ":nodecorate", true);
942 dev_add_option(&(*available_cards)[0], "Disable vsync", "Disable vsync", "novsync", ":novsync", true);
943
944 (*available_cards)[0].repeatable = true;
945}
946
960
962
#define UNUSED(x)
void ** state
int void * val
struct video_desc desc
struct video_frame * frame
const char * fmt
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.
const char * get_codec_name(codec_t codec)
static void show_help()
Definition wasapi.cpp:193
int color_printf(const char *format,...)
#define TRED(x)
Definition color_out.h:75
#define TBOLD(x)
Definition color_out.h:70
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
void log_msg_once(int level, uint32_t id, const char *msg,...)
Definition debug.cpp:137
#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 MSG(l, fmt,...)
Definition debug.h:96
#define debug_msg(...)
#define debug_msg(...) log_msg(LOG_LEVEL_DEBUG, "[pid/%d +%d %s] ", getpid(), LINE,...
Definition debug.h:92
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
#define PITCH_DEFAULT
default pitch, i. e. respective linesize
#define PUTF_DISCARD
#define PUTF_BLOCKING
#define VIDEO_DISPLAY_ABI_VERSION
#define DISPLAY_FLAG_AUDIO_ANY
struct video_frame * get_splashscreen()
@ DISPLAY_PROPERTY_BUF_PITCH
requested framebuffer pitch - int (bytes), may be PITCH_DEFAULT
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
void module_init_default(struct module *module_data)
Definition module.c:48
void module_register(struct module *module_data, struct module *parent)
Definition module.c:82
void module_done(struct module *module_data)
Definition module.c:93
struct module * get_root_module(struct module *node)
Definition module.c:203
@ MODULE_CLASS_DATA
Definition module.h:86
int count
Definition hd-rum.c:44
void exit_uv(int status)
Definition host.cpp:625
const char * get_commandline_param(const char *key)
Definition host.cpp:894
#define INIT_NOERR
Definition host.h:73
bool keycontrol_register_key(struct module *receiver_mod, int64_t key, const char *message, const char *description)
Registers callback message for given key.
void keycontrol_send_key(struct module *root, int64_t key)
#define K_LEFT
#define K_RIGHT
#define K_PGDOWN
#define K_DOWN
#define K_CTRL(x)
#define K_PGUP
#define K_UP
@ 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
void simple_linked_list_append(struct simple_linked_list *l, void *data)
Definition list.cpp:68
int simple_linked_list_size(struct simple_linked_list *l)
Definition list.cpp:93
struct simple_linked_list * simple_linked_list_init(void)
Definition list.cpp:53
void simple_linked_list_destroy(struct simple_linked_list *l)
Definition list.cpp:58
void * simple_linked_list_pop(struct simple_linked_list *l)
Definition list.cpp:82
#define IS_KEY_PREFIX(tok, key)
expands to true value if <k> from tok in format <k>=<v> is prefix of key
Definition macros.h:138
#define IS_PREFIX(tok, key)
similar as above, but also key without a value is accepted (value optional)
Definition macros.h:142
@ STR_LEN
"standard" string length placeholder
Definition macros.h:134
void free_message(struct message *msg, struct response *r)
struct response * new_response(int status, const char *text)
struct message * check_message(struct module *mod)
#define RESPONSE_OK
Definition messaging.h:57
#define RESPONSE_BAD_REQUEST
Definition messaging.h:60
struct msghdr msg
static struct video_frame * display_sdl2_getf(void *state)
Definition sdl2.c:808
static bool display_sdl2_process_key(struct state_sdl2 *s, int64_t key)
Definition sdl2.c:233
#define MAGIC_SDL2
Definition sdl2.c:86
static void cleanup_frames(struct state_sdl2 *s)
Definition sdl2.c:458
static bool set_size(struct state_sdl2 *s, const char *tok)
Definition sdl2.c:563
static const char * deint_to_string(enum deint val)
Definition sdl2.c:138
static const struct @93 pf_mapping[]
static const struct video_display_info display_sdl2_info
Definition sdl2.c:947
static void vf_sdl_texture_data_deleter(struct video_frame *buf)
Definition sdl2.c:466
static void display_sdl2_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
Definition sdl2.c:933
static int get_renderer_idx(const char *renderer)
Definition sdl2.c:598
static bool display_sdl2_get_property(void *state, int property, void *val, size_t *len)
Definition sdl2.c:899
#define BUFFER_COUNT
Definition sdl2.c:87
codec_t first
Definition sdl2.c:423
static void sdl2_print_displays()
Definition sdl2.c:330
const char * description
Definition sdl2.c:149
static void display_sdl2_new_message(struct module *mod)
Definition sdl2.c:924
static bool display_sdl2_reconfigure_real(void *state, struct video_desc desc)
Definition sdl2.c:492
static void display_sdl2_done(void *state)
Definition sdl2.c:775
static bool display_sdl2_putf(void *state, struct video_frame *frame, long long timeout_ns)
Definition sdl2.c:843
#define SDL_CHECK(cmd)
Definition sdl2.c:156
static void display_frame(struct state_sdl2 *s, struct video_frame *frame)
Definition sdl2.c:158
static int64_t translate_sdl_key_to_ug(SDL_Keysym sym)
Definition sdl2.c:192
static void r10k_to_sdl2(size_t count, uint32_t *buf)
Definition sdl2.c:824
static uint32_t get_ug_to_sdl_format(codec_t ug_codec)
Definition sdl2.c:439
static int get_supported_pfs(codec_t *codecs)
Definition sdl2.c:449
uint32_t second
Definition sdl2.c:424
#define MOD_NAME
Definition sdl2.c:88
static const struct @92 keybindings[]
static bool recreate_textures(struct state_sdl2 *s, struct video_desc desc)
Definition sdl2.c:471
char key
Definition sdl2.c:148
#define SDL2_DEINTERLACE_IMPOSSIBLE_MSG_ID
Definition sdl2.c:85
static bool display_sdl2_reconfigure(void *state, struct video_desc desc)
Definition sdl2.c:393
deint
Definition sdl2.c:99
@ DEINT_OFF
Definition sdl2.c:99
@ DEINT_FORCE
Definition sdl2.c:99
@ DEINT_ON
Definition sdl2.c:99
static void * display_sdl2_init(struct module *parent, const char *fmt, unsigned int flags)
Definition sdl2.c:631
static void loadSplashscreen(struct state_sdl2 *s)
Definition sdl2.c:550
static void display_sdl2_run(void *arg)
Definition sdl2.c:252
const char * parent
Definition settings.cpp:198
const char * name
Definition settings.cpp:193
char dev[1024]
Definition types.h:383
uint32_t priv_magic
optional magic number that can a module set
Definition module.h:113
enum module_class cls
Definition module.h:115
notify_t new_message
if set, notifies module that new message is in queue, receiver lock is hold during the call
Definition module.h:119
Uint32 sdl_user_reconfigure_event
Definition sdl2.c:108
unsigned fixed_w
Definition sdl2.c:122
SDL_Renderer * renderer
Definition sdl2.c:115
struct video_frame * last_frame
Definition sdl2.c:132
struct video_desc current_display_desc
Definition sdl2.c:131
int display_idx
Definition sdl2.c:110
unsigned fixed_h
Definition sdl2.c:122
pthread_cond_t reconfigured_cv
Definition sdl2.c:128
struct module mod
Definition sdl2.c:102
Uint32 sdl_user_new_frame_event
Definition sdl2.c:106
enum deint deinterlace
Definition sdl2.c:118
bool vsync
Definition sdl2.c:120
bool fixed_size
Definition sdl2.c:121
bool fs
Definition sdl2.c:117
int reconfiguration_status
Definition sdl2.c:129
SDL_Window * window
Definition sdl2.c:114
int texture_pitch
Definition sdl2.c:104
int x
Definition sdl2.c:111
uint32_t window_flags
user requested flags
Definition sdl2.c:123
struct simple_linked_list * free_frame_queue
Definition sdl2.c:134
Uint32 sdl_user_new_message_event
Definition sdl2.c:107
pthread_cond_t frame_consumed_cv
Definition sdl2.c:126
int renderer_idx
Definition sdl2.c:113
pthread_mutex_t lock
Definition sdl2.c:125
bool keep_aspect
Definition sdl2.c:119
int y
Definition sdl2.c:112
unsigned int width
Definition types.h:280
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
unsigned int data_len
length of the data
Definition types.h:288
unsigned int height
Definition types.h:281
codec_t color_spec
Definition types.h:182
enum interlacing_t interlacing
Definition types.h:184
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
void(* data_deleter)(struct video_frame *)
Definition types.h:256
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
enum interlacing_t interlacing
Definition types.h:307
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
struct video_frame_callbacks callbacks
Definition types.h:324
codec_t color_spec
Definition types.h:306
uint32_t ts
Definition tfrc.c:101
void ts_add_nsec(struct timespec *ts, long long offset)
Definition tv.c:79
int timespec_get(struct timespec *ts, int base)
Definition tv.c:236
#define TIME_UTC
Definition tv.h:116
void dev_add_option(struct device_info *dev, const char *name, const char *desc, const char *key, const char *opt_str, bool is_boolean)
@ INTERLACED_MERGED
Columngs of both fields are interlaced together.
Definition types.h:167
codec_t
Definition types.h:65
@ YUYV
YCbCr 422 8-bit - Y0 Cb Y1 Cr.
Definition types.h:72
@ R10k
RGB 10-bit packed - RGBX, big-endian (2 bit padding)
Definition types.h:73
@ 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
@ VIDEO_CODEC_COUNT
count of known video codecs (including VIDEO_CODEC_NONE)
Definition types.h:108
@ I420
planar YCbCr 4:2:0
Definition types.h:97
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
codec_t ug_codec
Definition v4l2_common.h:68
bool vc_deinterlace_ex(codec_t codec, unsigned char *src, size_t src_linesize, unsigned char *dst, size_t dst_pitch, size_t lines)
bool codec_is_planar(codec_t codec)
const char * description
Definition cineform.cpp:126
const char * key
Definition cineform.cpp:125
static struct @76 codecs[]