UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
caca.c
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2023-2024 CESNET, z. s. p. o.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
37
38#include <caca.h>
39#include <pthread.h> // for pthread_mutex_unlock, pthread_cond_destroy
40#include <stdbool.h> // for bool, true, false
41#include <stdio.h> // for snprintf
42#include <stdlib.h> // for NULL, calloc, free, getenv, size_t
43#include <string.h> // for strcmp, strlen, memcpy, strchr, strstr
44#include <time.h> // for timespec_get, TIME_UTC, timespec
45
46#include "debug.h"
47#include "lib_common.h"
48#include "utils/color_out.h"
49#include "tv.h"
50#include "video.h"
51#include "video_display.h"
52
53#define MOD_NAME "[caca] "
54
55struct state_caca {
56 caca_canvas_t *canvas;
57 caca_display_t *display;
58 caca_dither_t *dither;
61
63 struct video_frame *f;
64
65 _Bool started;
68 pthread_t thread_id;
69 pthread_mutex_t lock;
70 pthread_cond_t frame_ready_cv;
71 pthread_cond_t frame_consumed_cv;
72};
73
74static bool display_caca_putf(void *state, struct video_frame *frame,
75 long long timeout_ns);
76static void *worker(void *arg);
77
78static void display_caca_probe(struct device_info **available_cards, int *count, void (**deleter)(void *))
79{
80 *deleter = free;
81 *count = 1;
82 *available_cards = calloc(*count, sizeof **available_cards);
83 snprintf((*available_cards)[0].name, sizeof (*available_cards)[0].name, "caca");
84}
85
86static void display_caca_done(void *state)
87{
88 struct state_caca *s = state;
89 if (s->started) {
90 if (!s->should_exit) {
92 }
93 pthread_join(s->thread_id, NULL);
94 }
95 if (s->dither) {
96 caca_free_dither(s->dither);
97 }
98 if (s->display) {
99 caca_free_display(s->display);
100 }
101 if (s->canvas) {
102 caca_free_canvas(s->canvas);
103 }
104 if (s->log_level_stored != -1 && log_level == LOG_LEVEL_ERROR) {
106 }
107 vf_free(s->f);
108 pthread_mutex_destroy(&s->lock);
109 pthread_cond_destroy(&s->frame_ready_cv);
110 pthread_cond_destroy(&s->frame_consumed_cv);
111 free(s);
112}
113
114static void usage() {
115 color_printf("Display " TBOLD("caca") " syntax:\n");
116 color_printf("\t" TBOLD(TRED("caca") "[:driver=<drv>]") "\n");
117 color_printf("\nAvailable drivers:\n");
118 const char * const * drivers = caca_get_display_driver_list();
119 while (*drivers) {
120 const char *driver = *drivers++;
121 const char *desc = *drivers++;
122 color_printf("\t- " TBOLD("%s") " - %s\n", driver, desc ? desc : "(no description");
123 }
124}
125
126static void *display_caca_init(struct module *parent, const char *fmt, unsigned int flags)
127{
128 UNUSED(flags);
129 UNUSED(parent);
130 const char *driver = NULL;
131 if (strlen(fmt) > 0) {
132 if (strstr(fmt, "driver=") == fmt) {
133 driver = strchr(fmt, '=') + 1;
134 } else {
135 usage();
136 return strcmp(fmt, "help") == 0 ? INIT_NOERR : NULL;
137 }
138 }
139 struct state_caca *s = calloc(1, sizeof *s);
140 s->log_level_stored = -1;
141
142 pthread_mutex_init(&s->lock, NULL);
143 pthread_cond_init(&s->frame_ready_cv, NULL);
144 pthread_cond_init(&s->frame_consumed_cv, NULL);
145
146 s->canvas = caca_create_canvas(0, 0);
147 if (!s->canvas) {
148 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to create canvas\n");
150 return NULL;
151 }
152 s->display = caca_create_display_with_driver(s->canvas, driver);
153 if (!s->display) {
154 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to create display\n");
156 return NULL;
157 }
158 driver = caca_get_display_driver(s->display);
159 log_msg(LOG_LEVEL_INFO, MOD_NAME "Using display driver: %s\n", driver);
160
161 if (strcmp(driver, "x11") != 0 && strcmp(driver, "null") != 0) {
162 log_msg(LOG_LEVEL_INFO, MOD_NAME "Disabling keyboard control.\n");
163 set_commandline_param("disable-keyboard-control", "");
164 if (log_level == LOG_LEVEL_INFO) {
166 MSG(INFO,
167 "Setting log level to error - set to a different "
168 "level than info to keep logging.\n");
170 }
171 }
172
173 s->f = get_splashscreen();
174
175 pthread_create(&s->thread_id, NULL, worker, s);
176 s->started = 1;
177
178 return s;
179}
180
182{
183 struct state_caca *s = state;
184 return vf_alloc_desc_data(s->desc);
185}
186
187static void handle_events(struct state_caca *s, struct video_frame *last_frame)
188{
189 caca_event_t e;
190 int ret = 0;
191 while ((ret = caca_get_event(s->display, CACA_EVENT_ANY, &e, 0)) > 0) {
192 switch (e.type) {
193 case CACA_EVENT_KEY_PRESS:
194 if (e.data.key.ch == 'q' || e.data.key.ch == CACA_KEY_CTRL_C) {
195 exit_uv(0);
196 }
197 break;
198 case CACA_EVENT_RESIZE:
199 s->screen_w = e.data.resize.w;
200 s->screen_h = e.data.resize.h;
201 verbose_msg(MOD_NAME "Resized to %dx%d\n", s->screen_w, s->screen_h);
202 if (last_frame) {
203 caca_dither_bitmap(s->canvas, 0, 0, s->screen_w, s->screen_h, s->dither, last_frame->tiles[0].data);
204 caca_refresh_display(s->display);
205 }
206 break;
207 case CACA_EVENT_QUIT:
208 exit_uv(0);
209 break;
210 default:
211 break;
212 }
213 }
214}
215
216static _Bool reconfigure(struct state_caca *s, struct video_desc desc) {
217 enum {
218 RMASK = 0x0000ff,
219 GMASK = 0x00ff00,
220 BMASK = 0xff0000,
221 AMASK = 0x000000,
222 };
223 caca_free_dither(s->dither);
224 s->dither = caca_create_dither(8 * get_bpp(desc.color_spec),
227 RMASK, GMASK, BMASK, AMASK);
228 if (!s->dither) {
229 log_msg(LOG_LEVEL_ERROR, MOD_NAME "Failed to create dither\n");
230 exit_uv(1);
231 return 0;
232 }
233 s->screen_w = caca_get_canvas_width(s->canvas);
234 s->screen_h = caca_get_canvas_height(s->canvas);
235
236 return 1;
237}
238
239static void *worker(void *arg)
240{
241 struct video_desc display_desc = { 0 };
242 struct state_caca *s = arg;
243 struct video_frame *last_frame = NULL;
244 while (1) {
245 struct video_frame *f = NULL;
246 struct timespec ts;
248 ts_add_nsec(&ts, MS_TO_NS(200LL)); // 200 ms
249 pthread_mutex_lock(&s->lock);
250 while (!s->f && !s->should_exit) {
251 pthread_cond_timedwait(&s->frame_ready_cv, &s->lock, &ts);
253 }
254 if (s->should_exit) {
255 pthread_mutex_unlock(&s->lock);
256 break;
257 }
258 f = s->f;
259 s->f = NULL;
260 pthread_mutex_unlock(&s->lock);
261 pthread_cond_signal(&s->frame_consumed_cv);
262 if (!video_desc_eq(display_desc, video_desc_from_frame(f))) {
264 vf_free(f);
265 continue;
266 }
267 display_desc = video_desc_from_frame(f);
268 }
269
271 caca_dither_bitmap(s->canvas, 0, 0, s->screen_w, s->screen_h, s->dither, f->tiles[0].data);
272 caca_refresh_display(s->display);
275 last_frame = f;
276 }
278 return NULL;
279}
280
281static bool display_caca_putf(void *state, struct video_frame *frame, long long timeout_ns)
282{
283 if (timeout_ns == PUTF_DISCARD) {
284 vf_free(frame);
285 return true;
286 }
287
288 struct state_caca *s = state;
289
290 pthread_mutex_lock(&s->lock);
291 while (s->f) {
292 pthread_cond_wait(&s->frame_consumed_cv, &s->lock);
293 }
294 if (frame) {
295 s->f = frame;
296 } else {
297 s->should_exit = 1;
298 }
299 pthread_mutex_unlock(&s->lock);
300 pthread_cond_signal(&s->frame_ready_cv);
301
302 return true;
303}
304
305static bool display_caca_get_property(void *state, int property, void *val, size_t *len)
306{
307 UNUSED(state);
308
309 codec_t c[2] = { RGB, RGBA };
310 if (property == DISPLAY_PROPERTY_CODECS && sizeof c <= *len) {
311 memcpy(val, c, sizeof c);
312 *len = sizeof c;
313 return true;
314 }
315 return false;
316}
317
319{
320 struct state_caca *s = state;
321 s->desc = desc;
322 return true;
323}
324
325static const void *display_caca_info_get() {
326 static _Thread_local struct video_display_info display_caca_info = {
329 NULL, // _run
335 NULL,
336 NULL,
338 };
339 const char *display = getenv("DISPLAY");
340 if (display != NULL && strlen(display) > 0) { // print FPS only if caca has own window (not the terminal)
341 display_caca_info.generic_fps_indicator_prefix = MOD_NAME;
342 }
343 return &display_caca_info;
344};
345
#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.
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
static void display_caca_done(void *state)
Definition caca.c:86
static bool display_caca_putf(void *state, struct video_frame *frame, long long timeout_ns)
Definition caca.c:281
static bool display_caca_get_property(void *state, int property, void *val, size_t *len)
Definition caca.c:305
static struct video_frame * display_caca_getf(void *state)
Definition caca.c:181
static void usage()
Definition caca.c:114
static void display_caca_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
Definition caca.c:78
static void * worker(void *arg)
Definition caca.c:239
static const void * display_caca_info_get()
Definition caca.c:325
static void handle_events(struct state_caca *s, struct video_frame *last_frame)
Definition caca.c:187
#define MOD_NAME
Definition caca.c:53
static bool display_caca_reconfigure(void *state, struct video_desc desc)
Definition caca.c:318
static _Bool reconfigure(struct state_caca *s, struct video_desc desc)
Definition caca.c:216
static void * display_caca_init(struct module *parent, const char *fmt, unsigned int flags)
Definition caca.c:126
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
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define MSG(l, fmt,...)
Definition debug.h:96
#define verbose_msg(...)
Definition debug.h:90
#define PUTF_DISCARD
#define PUTF_BLOCKING
#define VIDEO_DISPLAY_ABI_VERSION
#define DISPLAY_NO_GENERIC_FPS_INDICATOR
struct video_frame * get_splashscreen()
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
int count
Definition hd-rum.c:44
void exit_uv(int status)
Definition host.cpp:625
void set_commandline_param(const char *key, const char *val)
Definition host.cpp:904
#define INIT_NOERR
Definition host.h:73
last_frame
Definition interlace.c:52
volatile int log_level
Definition debug.cpp:65
@ LIBRARY_CLASS_VIDEO_DISPLAY
Definition lib_common.h:79
#define REGISTER_MODULE_WITH_FUNC(name, func, lclass, abi)
Definition lib_common.h:150
const char * parent
Definition settings.cpp:198
const char * name
Definition settings.cpp:193
This struct represents initialized video display state.
caca_display_t * display
Definition caca.c:57
pthread_t thread_id
Definition caca.c:68
int screen_w
Definition caca.c:59
int log_level_stored
Definition caca.c:67
_Bool should_exit
Definition caca.c:66
_Bool started
Definition caca.c:65
pthread_cond_t frame_consumed_cv
Definition caca.c:71
struct video_frame * f
Definition caca.c:63
pthread_mutex_t lock
Definition caca.c:69
struct video_desc desc
Definition caca.c:62
caca_canvas_t * canvas
Definition caca.c:56
pthread_cond_t frame_ready_cv
Definition caca.c:70
caca_dither_t * dither
Definition caca.c:58
int screen_h
Definition caca.c:60
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
codec_t color_spec
Definition types.h:182
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
const char * generic_fps_indicator_prefix
NO_GENERIC_FPS_INDICATOR or pointer to preferred module name.
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
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
#define MS_TO_NS(val_ms)
Definition tv.h:99
codec_t
Definition types.h:65
@ RGB
RGB 8-bit (packed into 24-bit word)
Definition types.h:80
@ RGBA
RGBA 8-bit, big-endian.
Definition types.h:70
This is an umbrella header for video functions.
double get_bpp(codec_t codec)
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.