1 /* Spa
2  *
3  * Copyright (C) 2020, Collabora Ltd.
4  *     Author: Raghavendra Rao Sidlagatta <raghavendra.rao@collabora.com>
5  *
6  * local-libcamera.c
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a
9  * copy of this software and associated documentation files (the "Software"),
10  * to deal in the Software without restriction, including without limitation
11  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12  * and/or sell copies of the Software, and to permit persons to whom the
13  * Software is furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice (including the next
16  * paragraph) shall be included in all copies or substantial portions of the
17  * Software.
18  *
19  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
22  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25  * DEALINGS IN THE SOFTWARE.
26  */
27 
28 /*
29  [title]
30  Example using libspa-libcamera, with only \ref api_spa
31  [title]
32  */
33 
34 #include "config.h"
35 
36 #include <string.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <dlfcn.h>
41 #include <poll.h>
42 #include <pthread.h>
43 #include <errno.h>
44 #include <sys/mman.h>
45 
46 #include <SDL2/SDL.h>
47 
48 #include <spa/support/plugin.h>
49 #include <spa/utils/names.h>
50 #include <spa/utils/result.h>
51 #include <spa/utils/string.h>
52 #include <spa/support/log-impl.h>
53 #include <spa/support/loop.h>
54 #include <spa/node/node.h>
55 #include <spa/node/io.h>
56 #include <spa/node/utils.h>
57 #include <spa/param/param.h>
58 #include <spa/param/props.h>
59 #include <spa/param/video/format-utils.h>
60 #include <spa/debug/pod.h>
61 
62 #define WIDTH   640
63 #define HEIGHT  480
64 
65 static SPA_LOG_IMPL(default_log);
66 
67 #define MAX_BUFFERS     8
68 
69 #define USE_BUFFER 		false
70 
71 struct buffer {
72 	struct spa_buffer buffer;
73 	struct spa_meta metas[1];
74 	struct spa_meta_header header;
75 	struct spa_data datas[1];
76 	struct spa_chunk chunks[1];
77 	SDL_Texture *texture;
78 };
79 
80 struct data {
81 	const char *plugin_dir;
82 	struct spa_log *log;
83 	struct spa_system *system;
84 	struct spa_loop *loop;
85 	struct spa_loop_control *control;
86 
87 	struct spa_support support[5];
88 	uint32_t n_support;
89 
90 	struct spa_node *source;
91 	struct spa_hook listener;
92 	struct spa_io_buffers source_output[1];
93 
94 	SDL_Renderer *renderer;
95 	SDL_Window *window;
96 	SDL_Texture *texture;
97 
98 	bool use_buffer;
99 
100 	bool running;
101 	pthread_t thread;
102 
103 	struct spa_buffer *bp[MAX_BUFFERS];
104 	struct buffer buffers[MAX_BUFFERS];
105 	unsigned int n_buffers;
106 };
107 
load_handle(struct data * data,struct spa_handle ** handle,const char * lib,const char * name)108 static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name)
109 {
110 	int res;
111 	void *hnd;
112 	spa_handle_factory_enum_func_t enum_func;
113 	uint32_t i;
114 
115 	char *path = NULL;
116 
117 	if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) {
118 		return -ENOMEM;
119 	}
120 	if ((hnd = dlopen(path, RTLD_NOW)) == NULL) {
121 		printf("can't load %s: %s\n", path, dlerror());
122 		free(path);
123 		return -errno;
124 	}
125 	free(path);
126 	if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) {
127 		printf("can't find enum function\n");
128 		return -errno;
129 	}
130 
131 	for (i = 0;;) {
132 		const struct spa_handle_factory *factory;
133 
134 		if ((res = enum_func(&factory, &i)) <= 0) {
135 			if (res != 0)
136 				printf("can't enumerate factories: %s\n", spa_strerror(res));
137 			break;
138 		}
139 		if (!spa_streq(factory->name, name))
140 			continue;
141 
142 		*handle = calloc(1, spa_handle_factory_get_size(factory, NULL));
143 		if ((res = spa_handle_factory_init(factory, *handle,
144 						NULL, data->support,
145 						data->n_support)) < 0) {
146 			printf("can't make factory instance: %d\n", res);
147 			return res;
148 		}
149 		return 0;
150 	}
151 	return -EBADF;
152 }
153 
make_node(struct data * data,struct spa_node ** node,const char * lib,const char * name)154 static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name)
155 {
156 	struct spa_handle *handle = NULL;
157 	void *iface;
158 	int res;
159 
160 	if ((res = load_handle(data, &handle, lib, name)) < 0)
161 		return res;
162 
163 	if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) {
164 		printf("can't get interface %d\n", res);
165 		return res;
166 	}
167 	*node = iface;
168 	return 0;
169 }
170 
on_source_ready(void * _data,int status)171 static int on_source_ready(void *_data, int status)
172 {
173 	struct data *data = _data;
174 	int res;
175 	struct buffer *b;
176 	void *sdata, *ddata;
177 	int sstride, dstride;
178 	int i;
179 	uint8_t *src, *dst;
180 	struct spa_data *datas;
181 	struct spa_io_buffers *io = &data->source_output[0];
182 
183 	if (io->status != SPA_STATUS_HAVE_DATA ||
184 	    io->buffer_id >= MAX_BUFFERS)
185 		return -EINVAL;
186 
187 	b = &data->buffers[io->buffer_id];
188 	io->status = SPA_STATUS_NEED_DATA;
189 
190 	datas = b->buffer.datas;
191 
192 	if (b->texture) {
193 		SDL_Texture *texture = b->texture;
194 
195 		SDL_UnlockTexture(texture);
196 
197 		SDL_RenderClear(data->renderer);
198 		SDL_RenderCopy(data->renderer, texture, NULL, NULL);
199 		SDL_RenderPresent(data->renderer);
200 
201 		if (SDL_LockTexture(texture, NULL, &sdata, &sstride) < 0) {
202 			fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
203 			return -EIO;
204 		}
205 	} else {
206 		uint8_t *map;
207 
208 		if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
209 			fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
210 			return -EIO;
211 		}
212 		sdata = datas[0].data;
213 		if (datas[0].type == SPA_DATA_MemFd ||
214 		    datas[0].type == SPA_DATA_DmaBuf) {
215 			map = mmap(NULL, datas[0].maxsize + datas[0].mapoffset, PROT_READ,
216 				   MAP_PRIVATE, datas[0].fd, 0);
217 			if (map == MAP_FAILED)
218 				return -errno;
219 			sdata = SPA_PTROFF(map, datas[0].mapoffset, uint8_t);
220 		} else if (datas[0].type == SPA_DATA_MemPtr) {
221 			map = NULL;
222 			sdata = datas[0].data;
223 		} else
224 			return -EIO;
225 
226 		sstride = datas[0].chunk->stride;
227 
228 		for (i = 0; i < HEIGHT; i++) {
229 			src = ((uint8_t *) sdata + i * sstride);
230 			dst = ((uint8_t *) ddata + i * dstride);
231 			memcpy(dst, src, SPA_MIN(sstride, dstride));
232 		}
233 		SDL_UnlockTexture(data->texture);
234 
235 		SDL_RenderClear(data->renderer);
236 		SDL_RenderCopy(data->renderer, data->texture, NULL, NULL);
237 		SDL_RenderPresent(data->renderer);
238 
239 		if (map)
240 			munmap(map, datas[0].maxsize + datas[0].mapoffset);
241 	}
242 
243 	if ((res = spa_node_process(data->source)) < 0)
244 		printf("got process error %d\n", res);
245 
246 	return 0;
247 }
248 
249 static const struct spa_node_callbacks source_callbacks = {
250 	SPA_VERSION_NODE_CALLBACKS,
251 	.ready = on_source_ready,
252 };
253 
make_nodes(struct data * data,const char * device)254 static int make_nodes(struct data *data, const char *device)
255 {
256 	int res;
257 	struct spa_pod *props;
258 	struct spa_pod_builder b = { 0 };
259 	uint8_t buffer[256];
260 	uint32_t index;
261 
262 	if ((res =
263 	     make_node(data, &data->source,
264 		     "libcamera/libspa-libcamera.so",
265 		     SPA_NAME_API_LIBCAMERA_SOURCE)) < 0) {
266 		printf("can't create libcamera-source: %d\n", res);
267 		return res;
268 	}
269 
270 	spa_node_set_callbacks(data->source, &source_callbacks, data);
271 
272 	index = 0;
273 	spa_pod_builder_init(&b, buffer, sizeof(buffer));
274 	if ((res = spa_node_enum_params_sync(data->source, SPA_PARAM_Props,
275 			&index, NULL, &props, &b)) == 1) {
276 		spa_debug_pod(0, NULL, props);
277 	}
278 
279 	spa_pod_builder_init(&b, buffer, sizeof(buffer));
280 	props = spa_pod_builder_add_object(&b,
281 		SPA_TYPE_OBJECT_Props, 0,
282 		SPA_PROP_device, SPA_POD_String(device ? device : "/dev/media0"));
283 
284 	if ((res = spa_node_set_param(data->source, SPA_PARAM_Props, 0, props)) < 0)
285 		printf("got set_props error %d\n", res);
286 
287 	return res;
288 }
289 
setup_buffers(struct data * data)290 static int setup_buffers(struct data *data)
291 {
292 	int i;
293 
294 	for (i = 0; i < MAX_BUFFERS; i++) {
295 		struct buffer *b = &data->buffers[i];
296 
297 		data->bp[i] = &b->buffer;
298 
299 		b->texture = NULL;
300 
301 		b->buffer.metas = b->metas;
302 		b->buffer.n_metas = 1;
303 		b->buffer.datas = b->datas;
304 		b->buffer.n_datas = 1;
305 
306 		b->header.flags = 0;
307 		b->header.seq = 0;
308 		b->header.pts = 0;
309 		b->header.dts_offset = 0;
310 		b->metas[0].type = SPA_META_Header;
311 		b->metas[0].data = &b->header;
312 		b->metas[0].size = sizeof(b->header);
313 
314 		b->datas[0].type = SPA_DATA_DmaBuf;
315 		b->datas[0].flags = 0;
316 		b->datas[0].fd = -1;
317 		b->datas[0].mapoffset = 0;
318 		b->datas[0].maxsize = 0;
319 		b->datas[0].data = NULL;
320 		b->datas[0].chunk = &b->chunks[0];
321 		b->datas[0].chunk->offset = 0;
322 		b->datas[0].chunk->size = 0;
323 		b->datas[0].chunk->stride = 0;
324 	}
325 	data->n_buffers = MAX_BUFFERS;
326 	return 0;
327 }
328 
sdl_alloc_buffers(struct data * data)329 static int sdl_alloc_buffers(struct data *data)
330 {
331 	int i;
332 
333 	for (i = 0; i < MAX_BUFFERS; i++) {
334 		struct buffer *b = &data->buffers[i];
335 		SDL_Texture *texture;
336 		void *ptr;
337 		int stride;
338 
339 		texture = SDL_CreateTexture(data->renderer,
340 					    SDL_PIXELFORMAT_YUY2,
341 					    SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
342 		if (!texture) {
343 			printf("can't create texture: %s\n", SDL_GetError());
344 			return -ENOMEM;
345 		}
346 		if (SDL_LockTexture(texture, NULL, &ptr, &stride) < 0) {
347 			fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
348 			return -EIO;
349 		}
350 		b->texture = texture;
351 
352 		b->datas[0].type = SPA_DATA_DmaBuf;
353 		b->datas[0].maxsize = stride * HEIGHT;
354 		b->datas[0].data = ptr;
355 		b->datas[0].chunk->offset = 0;
356 		b->datas[0].chunk->size = stride * HEIGHT;
357 		b->datas[0].chunk->stride = stride;
358 	}
359 	return 0;
360 }
361 
negotiate_formats(struct data * data)362 static int negotiate_formats(struct data *data)
363 {
364 	int res;
365 	struct spa_pod *format;
366 	uint8_t buffer[256];
367 	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
368 
369 	data->source_output[0] = SPA_IO_BUFFERS_INIT;
370 
371 	if ((res =
372 	     spa_node_port_set_io(data->source,
373 				  SPA_DIRECTION_OUTPUT, 0,
374 				  SPA_IO_Buffers,
375 				  &data->source_output[0], sizeof(data->source_output[0]))) < 0)
376 		return res;
377 
378 	format = spa_format_video_raw_build(&b, 0,
379 			&SPA_VIDEO_INFO_RAW_INIT(
380 				.format =  SPA_VIDEO_FORMAT_YUY2,
381 				.size = SPA_RECTANGLE(WIDTH, HEIGHT),
382 				.framerate = SPA_FRACTION(25,1)));
383 
384 	if ((res = spa_node_port_set_param(data->source,
385 					   SPA_DIRECTION_OUTPUT, 0,
386 					   SPA_PARAM_Format, 0,
387 					   format)) < 0)
388 		return res;
389 
390 
391 	setup_buffers(data);
392 
393 	if (data->use_buffer) {
394 		if ((res = sdl_alloc_buffers(data)) < 0)
395 			return res;
396 
397 		if ((res = spa_node_port_use_buffers(data->source,
398 						SPA_DIRECTION_OUTPUT, 0, 0,
399 						data->bp, data->n_buffers)) < 0) {
400 			printf("can't allocate buffers: %s\n", spa_strerror(res));
401 			return -1;
402 		}
403 	} else {
404 		unsigned int n_buffers;
405 
406 		data->texture = SDL_CreateTexture(data->renderer,
407 						  SDL_PIXELFORMAT_YUY2,
408 						  SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT);
409 		if (!data->texture) {
410 			printf("can't create texture: %s\n", SDL_GetError());
411 			return -1;
412 		}
413 		n_buffers = MAX_BUFFERS;
414 		if ((res = spa_node_port_use_buffers(data->source,
415 						SPA_DIRECTION_OUTPUT, 0,
416 						SPA_NODE_BUFFERS_FLAG_ALLOC,
417 						data->bp, n_buffers)) < 0) {
418 			printf("can't allocate buffers: %s\n", spa_strerror(res));
419 			return -1;
420 		}
421 		data->n_buffers = n_buffers;
422 	}
423 	return 0;
424 }
425 
loop(void * user_data)426 static void *loop(void *user_data)
427 {
428 	struct data *data = user_data;
429 
430 	printf("enter thread\n");
431     spa_loop_control_enter(data->control);
432 
433     while (data->running) {
434 		spa_loop_control_iterate(data->control, -1);
435 	}
436 
437 	printf("leave thread\n");
438     spa_loop_control_leave(data->control);
439 	return NULL;
440 }
441 
run_async_source(struct data * data)442 static void run_async_source(struct data *data)
443 {
444 	int res, err;
445 	struct spa_command cmd;
446 	SDL_Event event;
447 	bool running = true;
448 
449 	printf("starting...\n\n");
450 	cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start);
451 	if ((res = spa_node_send_command(data->source, &cmd)) < 0)
452 		printf("got error %d\n", res);
453 
454 	spa_loop_control_leave(data->control);
455 
456 	data->running = true;
457 	if ((err = pthread_create(&data->thread, NULL, loop, data)) != 0) {
458 		printf("can't create thread: %d %s", err, strerror(err));
459 		data->running = false;
460 	}
461 
462 	while (running && SDL_WaitEvent(&event)) {
463 		switch (event.type) {
464 		case SDL_QUIT:
465 			running = false;
466 			break;
467 		}
468 	}
469 
470 	if (data->running) {
471 		data->running = false;
472 		pthread_join(data->thread, NULL);
473 	}
474 
475 	spa_loop_control_enter(data->control);
476 
477 	printf("pausing...\n\n");
478 	cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause);
479 	if ((res = spa_node_send_command(data->source, &cmd)) < 0)
480 		printf("got error %d\n", res);
481 }
482 
main(int argc,char * argv[])483 int main(int argc, char *argv[])
484 {
485 	struct data data = { 0 };
486 	int res;
487 	const char *str;
488 	struct spa_handle *handle = NULL;
489 	void *iface;
490 
491 	if ((str = getenv("SPA_PLUGIN_DIR")) == NULL)
492 		str = PLUGINDIR;
493 	data.plugin_dir = str;
494 
495 	if ((res = load_handle(&data, &handle,
496 					"support/libspa-support.so",
497 					SPA_NAME_SUPPORT_SYSTEM)) < 0)
498 		return res;
499 
500 	if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) {
501 		printf("can't get System interface %d\n", res);
502 		return res;
503 	}
504 	data.system = iface;
505 	data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data.system);
506 
507 	if ((res = load_handle(&data, &handle,
508 					"support/libspa-support.so",
509 					SPA_NAME_SUPPORT_LOOP)) < 0)
510 		return res;
511 
512 	if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) {
513 		printf("can't get interface %d\n", res);
514 		return res;
515 	}
516 	data.loop = iface;
517 	if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) {
518 		printf("can't get interface %d\n", res);
519 		return res;
520 	}
521 	data.control = iface;
522 
523 	data.use_buffer = USE_BUFFER;
524 
525 	data.log = &default_log.log;
526 
527 	if ((str = getenv("SPA_DEBUG")))
528 		data.log->level = atoi(str);
529 
530 	data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log);
531 	data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data.loop);
532 	data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data.loop);
533 
534 	if (SDL_Init(SDL_INIT_VIDEO) < 0) {
535 		printf("can't initialize SDL: %s\n", SDL_GetError());
536 		return -1;
537 	}
538 
539 	if (SDL_CreateWindowAndRenderer
540 	    (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
541 		printf("can't create window: %s\n", SDL_GetError());
542 		return -1;
543 	}
544 
545 	if ((res = make_nodes(&data, argv[1])) < 0) {
546 		printf("can't make nodes: %d\n", res);
547 		return -1;
548 	}
549 
550 	if ((res = negotiate_formats(&data)) < 0) {
551 		printf("can't negotiate nodes: %d\n", res);
552 		return -1;
553 	}
554 
555 	spa_loop_control_enter(data.control);
556 	run_async_source(&data);
557 	spa_loop_control_leave(data.control);
558 
559 	SDL_DestroyRenderer(data.renderer);
560 
561 	return 0;
562 }
563