From 9024cc4444c8d5edd7c46140da38526a47d665af Mon Sep 17 00:00:00 2001 From: raghu447 Date: Mon, 20 Apr 2020 12:26:50 +0530 Subject: [PATCH] Integrating libcamera --- meson_options.txt | 4 + spa/examples/local-libcamera.c | 542 +++++++++ spa/examples/meson.build | 4 + spa/include/spa/param/param.h | 1 + spa/include/spa/utils/keys.h | 14 + spa/include/spa/utils/names.h | 7 + spa/meson.build | 3 + spa/plugins/audioconvert/fmt-ops-avx2.c | 3 + spa/plugins/libcamera/libcamera-client.c | 249 ++++ spa/plugins/libcamera/libcamera-device.c | 289 +++++ spa/plugins/libcamera/libcamera-source.c | 1026 +++++++++++++++++ spa/plugins/libcamera/libcamera-utils.c | 955 +++++++++++++++ spa/plugins/libcamera/libcamera.c | 55 + spa/plugins/libcamera/libcamera.h | 43 + spa/plugins/libcamera/libcamera_wrapper.cpp | 945 +++++++++++++++ spa/plugins/libcamera/libcamera_wrapper.h | 128 ++ spa/plugins/libcamera/meson.build | 12 + spa/plugins/meson.build | 3 + src/daemon/pipewire.conf.in | 3 +- .../media-session/libcamera-monitor.c | 498 ++++++++ src/examples/media-session/media-session.c | 8 +- src/examples/media-session/v4l2-endpoint.c | 35 +- src/examples/meson.build | 2 + src/pipewire/buffers.c | 10 +- 24 files changed, 4826 insertions(+), 13 deletions(-) create mode 100644 spa/examples/local-libcamera.c create mode 100644 spa/plugins/libcamera/libcamera-client.c create mode 100644 spa/plugins/libcamera/libcamera-device.c create mode 100644 spa/plugins/libcamera/libcamera-source.c create mode 100644 spa/plugins/libcamera/libcamera-utils.c create mode 100644 spa/plugins/libcamera/libcamera.c create mode 100644 spa/plugins/libcamera/libcamera.h create mode 100644 spa/plugins/libcamera/libcamera_wrapper.cpp create mode 100644 spa/plugins/libcamera/libcamera_wrapper.h create mode 100644 spa/plugins/libcamera/meson.build create mode 100644 src/examples/media-session/libcamera-monitor.c diff --git a/meson_options.txt b/meson_options.txt index b2b2af73f..6988c4f0e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -92,6 +92,10 @@ option('v4l2', description: 'Enable v4l2 spa plugin integration', type: 'boolean', value: true) +option('libcamera', + description: 'Enable libcamera spa plugin integration', + type: 'boolean', + value: true) option('videoconvert', description: 'Enable videoconvert spa plugin integration', type: 'boolean', diff --git a/spa/examples/local-libcamera.c b/spa/examples/local-libcamera.c new file mode 100644 index 000000000..cd08a5b37 --- /dev/null +++ b/spa/examples/local-libcamera.c @@ -0,0 +1,542 @@ +/* Spa + * + * Copyright (C) 2020, Collabora Ltd. + * Author: Raghavendra Rao Sidlagatta + * + * local-libcamera.c + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WIDTH 640 +#define HEIGHT 480 + +static SPA_LOG_IMPL(default_log); + +#define PATH "build/spa/plugins/" + +#define MAX_BUFFERS 8 + +#define USE_BUFFER false + +struct buffer { + struct spa_buffer buffer; + struct spa_meta metas[1]; + struct spa_meta_header header; + struct spa_data datas[1]; + struct spa_chunk chunks[1]; + SDL_Texture *texture; +}; + +struct data { + struct spa_log *log; + struct spa_system *system; + struct spa_loop *loop; + struct spa_loop_control *control; + + struct spa_support support[5]; + uint32_t n_support; + + struct spa_node *source; + struct spa_hook listener; + struct spa_io_buffers source_output[1]; + + SDL_Renderer *renderer; + SDL_Window *window; + SDL_Texture *texture; + + bool use_buffer; + + bool running; + pthread_t thread; + + struct spa_buffer *bp[MAX_BUFFERS]; + struct buffer buffers[MAX_BUFFERS]; + unsigned int n_buffers; +}; + +static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) +{ + int res; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + uint32_t i; + + if ((hnd = dlopen(lib, RTLD_NOW)) == NULL) { + printf("can't load %s: %s\n", lib, dlerror()); + return -errno; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + printf("can't find enum function\n"); + return -errno; + } + + for (i = 0;;) { + const struct spa_handle_factory *factory; + + if ((res = enum_func(&factory, &i)) <= 0) { + if (res != 0) + printf("can't enumerate factories: %s\n", spa_strerror(res)); + break; + } + if (strcmp(factory->name, name)) + continue; + + *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); + if ((res = spa_handle_factory_init(factory, *handle, + NULL, data->support, + data->n_support)) < 0) { + printf("can't make factory instance: %d\n", res); + return res; + } + return 0; + } + return -EBADF; +} + +static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name) +{ + struct spa_handle *handle = NULL; + void *iface; + int res; + + if ((res = load_handle(data, &handle, lib, name)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + *node = iface; + return 0; +} + +static int on_source_ready(void *_data, int status) +{ + struct data *data = _data; + int res; + struct buffer *b; + void *sdata, *ddata; + int sstride, dstride; + int i; + uint8_t *src, *dst; + struct spa_data *datas; + struct spa_io_buffers *io = &data->source_output[0]; + + if (io->status != SPA_STATUS_HAVE_DATA || + io->buffer_id >= MAX_BUFFERS) + return -EINVAL; + + b = &data->buffers[io->buffer_id]; + io->status = SPA_STATUS_NEED_DATA; + + datas = b->buffer.datas; + + if (b->texture) { + SDL_Texture *texture = b->texture; + + SDL_UnlockTexture(texture); + + SDL_RenderClear(data->renderer); + SDL_RenderCopy(data->renderer, texture, NULL, NULL); + SDL_RenderPresent(data->renderer); + + if (SDL_LockTexture(texture, NULL, &sdata, &sstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return -EIO; + } + } else { + uint8_t *map; + + if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return -EIO; + } + sdata = datas[0].data; + if (datas[0].type == SPA_DATA_MemFd || + datas[0].type == SPA_DATA_DmaBuf) { + map = mmap(NULL, datas[0].maxsize + datas[0].mapoffset, PROT_READ, + MAP_PRIVATE, datas[0].fd, 0); + if (map == MAP_FAILED) + return -errno; + sdata = SPA_MEMBER(map, datas[0].mapoffset, uint8_t); + } else if (datas[0].type == SPA_DATA_MemPtr) { + map = NULL; + sdata = datas[0].data; + } else + return -EIO; + + sstride = datas[0].chunk->stride; + + for (i = 0; i < HEIGHT; i++) { + src = ((uint8_t *) sdata + i * sstride); + dst = ((uint8_t *) ddata + i * dstride); + memcpy(dst, src, SPA_MIN(sstride, dstride)); + } + SDL_UnlockTexture(data->texture); + + SDL_RenderClear(data->renderer); + SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); + SDL_RenderPresent(data->renderer); + + if (map) + munmap(map, datas[0].maxsize + datas[0].mapoffset); + } + + if ((res = spa_node_process(data->source)) < 0) + printf("got process error %d\n", res); + + return 0; +} + +static const struct spa_node_callbacks source_callbacks = { + SPA_VERSION_NODE_CALLBACKS, + .ready = on_source_ready, +}; + +static int make_nodes(struct data *data, const char *device) +{ + int res; + struct spa_pod *props; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[256]; + uint32_t index; + + if ((res = + make_node(data, &data->source, + PATH "libcamera/libspa-libcamera.so", + SPA_NAME_API_LIBCAMERA_SOURCE)) < 0) { + printf("can't create libcamera-source: %d\n", res); + return res; + } + + spa_node_set_callbacks(data->source, &source_callbacks, data); + + index = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if ((res = spa_node_enum_params_sync(data->source, SPA_PARAM_Props, + &index, NULL, &props, &b)) == 1) { + spa_debug_pod(0, NULL, props); + } + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + props = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_device, SPA_POD_String(device ? device : "/dev/media0")); + + if ((res = spa_node_set_param(data->source, SPA_PARAM_Props, 0, props)) < 0) + printf("got set_props error %d\n", res); + + return res; +} + +static int setup_buffers(struct data *data) +{ + int i; + + for (i = 0; i < MAX_BUFFERS; i++) { + struct buffer *b = &data->buffers[i]; + + data->bp[i] = &b->buffer; + + b->texture = NULL; + + b->buffer.metas = b->metas; + b->buffer.n_metas = 1; + b->buffer.datas = b->datas; + b->buffer.n_datas = 1; + + b->header.flags = 0; + b->header.seq = 0; + b->header.pts = 0; + b->header.dts_offset = 0; + b->metas[0].type = SPA_META_Header; + b->metas[0].data = &b->header; + b->metas[0].size = sizeof(b->header); + + b->datas[0].type = SPA_DATA_DmaBuf; + b->datas[0].flags = 0; + b->datas[0].fd = -1; + b->datas[0].mapoffset = 0; + b->datas[0].maxsize = 0; + b->datas[0].data = NULL; + b->datas[0].chunk = &b->chunks[0]; + b->datas[0].chunk->offset = 0; + b->datas[0].chunk->size = 0; + b->datas[0].chunk->stride = 0; + } + data->n_buffers = MAX_BUFFERS; + return 0; +} + +static int sdl_alloc_buffers(struct data *data) +{ + int i; + + for (i = 0; i < MAX_BUFFERS; i++) { + struct buffer *b = &data->buffers[i]; + SDL_Texture *texture; + void *ptr; + int stride; + + texture = SDL_CreateTexture(data->renderer, + SDL_PIXELFORMAT_YUY2, + SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); + if (!texture) { + printf("can't create texture: %s\n", SDL_GetError()); + return -ENOMEM; + } + if (SDL_LockTexture(texture, NULL, &ptr, &stride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return -EIO; + } + b->texture = texture; + + b->datas[0].type = SPA_DATA_DmaBuf; + b->datas[0].maxsize = stride * HEIGHT; + b->datas[0].data = ptr; + b->datas[0].chunk->offset = 0; + b->datas[0].chunk->size = stride * HEIGHT; + b->datas[0].chunk->stride = stride; + } + return 0; +} + +static int negotiate_formats(struct data *data) +{ + int res; + struct spa_pod *format; + uint8_t buffer[256]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + data->source_output[0] = SPA_IO_BUFFERS_INIT; + + if ((res = + spa_node_port_set_io(data->source, + SPA_DIRECTION_OUTPUT, 0, + SPA_IO_Buffers, + &data->source_output[0], sizeof(data->source_output[0]))) < 0) + return res; + + format = spa_format_video_raw_build(&b, 0, + &SPA_VIDEO_INFO_RAW_INIT( + .format = SPA_VIDEO_FORMAT_YUY2, + .size = SPA_RECTANGLE(WIDTH, HEIGHT), + .framerate = SPA_FRACTION(25,1))); + + if ((res = spa_node_port_set_param(data->source, + SPA_DIRECTION_OUTPUT, 0, + SPA_PARAM_Format, 0, + format)) < 0) + return res; + + + setup_buffers(data); + + if (data->use_buffer) { + if ((res = sdl_alloc_buffers(data)) < 0) + return res; + + if ((res = spa_node_port_use_buffers(data->source, + SPA_DIRECTION_OUTPUT, 0, 0, + data->bp, data->n_buffers)) < 0) { + printf("can't allocate buffers: %s\n", spa_strerror(res)); + return -1; + } + } else { + unsigned int n_buffers; + + data->texture = SDL_CreateTexture(data->renderer, + SDL_PIXELFORMAT_YUY2, + SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); + if (!data->texture) { + printf("can't create texture: %s\n", SDL_GetError()); + return -1; + } + n_buffers = MAX_BUFFERS; + if ((res = spa_node_port_use_buffers(data->source, + SPA_DIRECTION_OUTPUT, 0, + SPA_NODE_BUFFERS_FLAG_ALLOC, + data->bp, n_buffers)) < 0) { + printf("can't allocate buffers: %s\n", spa_strerror(res)); + return -1; + } + data->n_buffers = n_buffers; + } + return 0; +} + +static void *loop(void *user_data) +{ + struct data *data = user_data; + + printf("enter thread\n"); + spa_loop_control_enter(data->control); + + while (data->running) { + spa_loop_control_iterate(data->control, -1); + } + + printf("leave thread\n"); + spa_loop_control_leave(data->control); + return NULL; +} + +static void run_async_source(struct data *data) +{ + int res, err; + struct spa_command cmd; + SDL_Event event; + bool running = true; + + printf("starting...\n\n"); + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); + if ((res = spa_node_send_command(data->source, &cmd)) < 0) + printf("got error %d\n", res); + + spa_loop_control_leave(data->control); + + data->running = true; + if ((err = pthread_create(&data->thread, NULL, loop, data)) != 0) { + printf("can't create thread: %d %s", err, strerror(err)); + data->running = false; + } + + while (running && SDL_WaitEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + running = false; + break; + } + } + + if (data->running) { + data->running = false; + pthread_join(data->thread, NULL); + } + + spa_loop_control_enter(data->control); + + printf("pausing...\n\n"); + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); + if ((res = spa_node_send_command(data->source, &cmd)) < 0) + printf("got error %d\n", res); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0 }; + int res; + const char *str; + struct spa_handle *handle = NULL; + void *iface; + + if ((res = load_handle(&data, &handle, + PATH "support/libspa-support.so", + SPA_NAME_SUPPORT_SYSTEM)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { + printf("can't get System interface %d\n", res); + return res; + } + data.system = iface; + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data.system); + + if ((res = load_handle(&data, &handle, + PATH "support/libspa-support.so", + SPA_NAME_SUPPORT_LOOP)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data.loop = iface; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data.control = iface; + + data.use_buffer = USE_BUFFER; + + data.log = &default_log.log; + + if ((str = getenv("SPA_DEBUG"))) + data.log->level = atoi(str); + + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log); + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data.loop); + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data.loop); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + printf("can't initialize SDL: %s\n", SDL_GetError()); + return -1; + } + + if (SDL_CreateWindowAndRenderer + (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { + printf("can't create window: %s\n", SDL_GetError()); + return -1; + } + + if ((res = make_nodes(&data, argv[1])) < 0) { + printf("can't make nodes: %d\n", res); + return -1; + } + + if ((res = negotiate_formats(&data)) < 0) { + printf("can't negotiate nodes: %d\n", res); + return -1; + } + + spa_loop_control_enter(data.control); + run_async_source(&data); + spa_loop_control_leave(data.control); + + SDL_DestroyRenderer(data.renderer); + + return 0; +} diff --git a/spa/examples/meson.build b/spa/examples/meson.build index f84067fac..e3c9b2afd 100644 --- a/spa/examples/meson.build +++ b/spa/examples/meson.build @@ -3,6 +3,10 @@ if sdl_dep.found() include_directories : [spa_inc ], dependencies : [dl_lib, sdl_dep, pthread_lib], install : false) + executable('local-libcamera', 'local-libcamera.c', + include_directories : [spa_inc ], + dependencies : [dl_lib, sdl_dep, pthread_lib, libcamera_dep], + install : false) endif executable('example-control', 'example-control.c', diff --git a/spa/include/spa/param/param.h b/spa/include/spa/param/param.h index 8dcea2d33..85917ae8c 100644 --- a/spa/include/spa/param/param.h +++ b/spa/include/spa/param/param.h @@ -75,6 +75,7 @@ enum spa_param_buffers { SPA_PARAM_BUFFERS_stride, /**< stride of data block memory (Int) */ SPA_PARAM_BUFFERS_align, /**< alignment of data block memory (Int) */ SPA_PARAM_BUFFERS_dataType, /**< possible memory types (Int, mask of enum spa_data_type) */ + SPA_PARAM_BUFFERS_datas, /**< number of datas (Int) */ }; /** properties for SPA_TYPE_OBJECT_ParamMeta */ diff --git a/spa/include/spa/utils/keys.h b/spa/include/spa/utils/keys.h index 0bfde80d4..3b0d78e69 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -77,6 +77,20 @@ extern "C" { #define SPA_KEY_API_V4L2_PATH "api.v4l2.path" /**< v4l2 device path as can be * used in open() */ +/** keys for libcamera api */ +#define SPA_KEY_API_LIBCAMERA "api.libcamera" /**< key for the libcamera api */ +#define SPA_KEY_API_LIBCAMERA_PATH "api.libcamera.path" /**< libcamera device path as can be + * used in open() */ + +/** info from libcamera_capability */ +#define SPA_KEY_API_LIBCAMERA_CAP_DRIVER "api.libcamera.cap.driver" /**< driver from capbility */ +#define SPA_KEY_API_LIBCAMERA_CAP_CARD "api.libcamera.cap.card" /**< caps from capability */ +#define SPA_KEY_API_LIBCAMERA_CAP_BUS_INFO "api.libcamera.cap.bus_info"/**< bus_info from capability */ +#define SPA_KEY_API_LIBCAMERA_CAP_VERSION "api.libcamera.cap.version" /**< version from capability as %u.%u.%u */ +#define SPA_KEY_API_LIBCAMERA_CAP_CAPABILITIES \ + "api.libcamera.cap.capabilities" /**< capabilities from capability */ +#define SPA_KEY_API_LIBCAMERA_CAP_DEVICE_CAPS \ + "api.libcamera.cap.device-caps" /**< device_caps from capability */ /** info from v4l2_capability */ #define SPA_KEY_API_V4L2_CAP_DRIVER "api.v4l2.cap.driver" /**< driver from capbility */ #define SPA_KEY_API_V4L2_CAP_CARD "api.v4l2.cap.card" /**< caps from capability */ diff --git a/spa/include/spa/utils/names.h b/spa/include/spa/utils/names.h index 96715c990..f3af4d73c 100644 --- a/spa/include/spa/utils/names.h +++ b/spa/include/spa/utils/names.h @@ -112,6 +112,13 @@ extern "C" { #define SPA_NAME_API_V4L2_DEVICE "api.v4l2.device" /**< a v4l2 Device interface */ #define SPA_NAME_API_V4L2_SOURCE "api.v4l2.source" /**< a v4l2 Node interface for * capturing */ + +/** keys for libcamera factory names */ +#define SPA_NAME_API_LIBCAMERA_ENUM_CLIENT "api.libcamera.enum.client" /**< a libcamera client Device interface */ +#define SPA_NAME_API_LIBCAMERA_DEVICE "api.libcamera.device" /**< a libcamera Device interface */ +#define SPA_NAME_API_LIBCAMERA_SOURCE "api.libcamera.source" /**< a libcamera Node interface for + * capturing */ + /** keys for jack factory names */ #define SPA_NAME_API_JACK_DEVICE "api.jack.device" /**< a jack device. This is a * client connected to a server */ diff --git a/spa/meson.build b/spa/meson.build index 0b561a1d8..3641f8a75 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -33,6 +33,9 @@ if get_option('spa-plugins') if get_option('vulkan') vulkan_dep = dependency('vulkan') endif + if get_option('libcamera') + libcamera_dep = dependency('camera') + endif subdir('plugins') endif diff --git a/spa/plugins/audioconvert/fmt-ops-avx2.c b/spa/plugins/audioconvert/fmt-ops-avx2.c index 065fa997e..82750dc4c 100644 --- a/spa/plugins/audioconvert/fmt-ops-avx2.c +++ b/spa/plugins/audioconvert/fmt-ops-avx2.c @@ -34,6 +34,9 @@ # define _mm256_setr_m128i(v0, v1) _mm256_set_m128i((v1), (v0)) #endif +#define _mm256_set_m128i(v0, v1) _mm256_insertf128_si256(_mm256_castsi128_si256(v1), (v0), 1) +#define _mm256_setr_m128i(v0, v1) _mm256_set_m128i((v1), (v0)) + static void conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) diff --git a/spa/plugins/libcamera/libcamera-client.c b/spa/plugins/libcamera/libcamera-client.c new file mode 100644 index 000000000..f5d755222 --- /dev/null +++ b/spa/plugins/libcamera/libcamera-client.c @@ -0,0 +1,249 @@ +/* Spa libcamera client + * + * Copyright (C) 2020, Collabora Ltd. + * Author: Raghavendra Rao Sidlagatta + * + * libcamera-client.c + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libcamera.h" + +#define NAME "libcamera-client" + +struct impl { + struct spa_handle handle; + struct spa_device device; + + struct spa_log *log; + struct spa_loop *main_loop; + + struct spa_hook_list hooks; + + uint64_t info_all; + struct spa_device_info info; + + struct spa_source source; + struct spa_libcamera_device dev; +}; + +static int emit_object_info(struct impl *this, uint32_t id) +{ + struct spa_device_object_info info; + const char *str; + struct spa_dict_item items[20]; + uint32_t n_items = 0; + + info = SPA_DEVICE_OBJECT_INFO_INIT(); + + info.type = SPA_TYPE_INTERFACE_Device; + info.factory_name = SPA_NAME_API_LIBCAMERA_DEVICE; + info.change_mask = (SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | + SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS); + info.flags = 0; + + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ENUM_API,"libcamera-client"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "libcamera"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Video/Device"); + + info.props = &SPA_DICT_INIT(items, n_items); + spa_device_emit_object_info(&this->hooks, id, &info); + + return 1; +} + +static const struct spa_dict_item device_info_items[] = { + { SPA_KEY_DEVICE_API, "libcamera" }, + { SPA_KEY_DEVICE_NICK, "libcamera-client" }, + { SPA_KEY_API_UDEV_MATCH, "libcamera" }, +}; + + +static void emit_device_info(struct impl *this, bool full) +{ + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + this->info.props = &SPA_DICT_INIT_ARRAY(device_info_items); + spa_device_emit_info(&this->hooks, &this->info); + this->info.change_mask = 0; + } +} + +static void impl_hook_removed(struct spa_hook *hook) +{ + return; +} + +static int +impl_device_add_listener(void *object, struct spa_hook *listener, + const struct spa_device_events *events, void *data) +{ + int res; + struct impl *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(events != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_device_info(this, true); + + emit_object_info(this, 0); + + spa_hook_list_join(&this->hooks, &save); + + listener->removed = impl_hook_removed; + listener->priv = this; + + return 0; +} + +static const struct spa_device_methods impl_device = { + SPA_VERSION_DEVICE_METHODS, + .add_listener = impl_device_add_listener, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (strcmp(type, SPA_TYPE_INTERFACE_Device) == 0) + *interface = &this->device; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this = (struct impl *) handle; + + if(this->dev.camera) { + deleteLibCamera(this->dev.camera); + free(this->dev.camera); + this->dev.camera = NULL; + } + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + + if (this->main_loop == NULL) { + spa_log_error(this->log, "a main-loop is needed"); + return -EINVAL; + } + spa_hook_list_init(&this->hooks); + + this->device.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &impl_device, this); + + this->info = SPA_DEVICE_INFO_INIT(); + this->info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | + SPA_DEVICE_CHANGE_MASK_PROPS; + this->info.flags = 0; + + if(this->dev.camera == NULL) { + this->dev.camera = (LibCamera*)newLibCamera(); + libcamera_set_log(this->dev.camera, this->dev.log); + } + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Device,}, +}; + +static int +impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + if (*index >= SPA_N_ELEMENTS(impl_interfaces)) + return 0; + + *info = &impl_interfaces[(*index)++]; + return 1; +} + +const struct spa_handle_factory spa_libcamera_client_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_LIBCAMERA_ENUM_CLIENT, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/libcamera/libcamera-device.c b/spa/plugins/libcamera/libcamera-device.c new file mode 100644 index 000000000..ba9289ff9 --- /dev/null +++ b/spa/plugins/libcamera/libcamera-device.c @@ -0,0 +1,289 @@ +/* Spa libcamera Source + * + * Copyright (C) 2020, Collabora Ltd. + * Author: Raghavendra Rao Sidlagatta + * + * libcamera-device.c + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libcamera.h" + +#define NAME "libcamera-device" + +static const char default_device[] = "/dev/media0"; + +struct props { + char device[64]; + char device_name[128]; + int device_fd; +}; + +static void reset_props(struct props *props) +{ + strncpy(props->device, default_device, 64); +} + +struct impl { + struct spa_handle handle; + struct spa_device device; + + struct spa_log *log; + + struct props props; + + struct spa_hook_list hooks; + + struct spa_libcamera_device dev; +}; + +static int emit_info(struct impl *this, bool full) +{ + int res, err, fd; + struct spa_dict_item items[10]; + uint32_t n_items = 0; + struct spa_device_info info; + struct spa_param_info params[2]; + char path[128], version[16], capabilities[16], device_caps[16]; + + if ((res = spa_libcamera_open(&this->dev)) < 0) + return res; + + info = SPA_DEVICE_INFO_INIT(); + + info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; + fd = get_dev_fd(&this->dev); + + do { + err = ioctl(this->dev.fd, MEDIA_IOC_DEVICE_INFO, &this->dev.dev_info); + } while (err == -1 && errno == EINTR); + + if(err < 0) { + spa_log_error(this->log, "%s:: Failed to query MEDIA_IOC_DEVICE_INFO on fd %d\n", __FUNCTION__, this->dev.fd); + } + +#define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) + snprintf(path, sizeof(path), "libcamera:%s", this->props.device); + ADD_ITEM(SPA_KEY_OBJECT_PATH, path); + ADD_ITEM(SPA_KEY_DEVICE_API, "libcamera"); + ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device"); + ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, (char *)this->props.device); + ADD_ITEM(SPA_KEY_API_LIBCAMERA_CAP_DRIVER, (char *)this->dev.dev_info.driver); + ADD_ITEM(SPA_KEY_API_LIBCAMERA_CAP_CARD, (char *)this->dev.dev_info.model); + ADD_ITEM(SPA_KEY_API_LIBCAMERA_CAP_BUS_INFO, (char *)this->dev.dev_info.bus_info); + snprintf(version, sizeof(version), "%u.%u.%u", + (this->dev.dev_info.media_version >> 16) & 0xFF, + (this->dev.dev_info.media_version >> 8) & 0xFF, + (this->dev.dev_info.media_version) & 0xFF); + ADD_ITEM(SPA_KEY_API_LIBCAMERA_CAP_VERSION, version); +#undef ADD_ITEM + info.props = &SPA_DICT_INIT(items, n_items); + + info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; + params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); + params[1] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_WRITE); + info.n_params = SPA_N_ELEMENTS(params); + info.params = params; + + spa_device_emit_info(&this->hooks, &info); + + if (spa_libcamera_is_capture(&this->dev)) { + struct spa_device_object_info oinfo; + + oinfo = SPA_DEVICE_OBJECT_INFO_INIT(); + oinfo.type = SPA_TYPE_INTERFACE_Node; + oinfo.factory_name = SPA_NAME_API_LIBCAMERA_SOURCE; + oinfo.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; + oinfo.props = &SPA_DICT_INIT(items, n_items); + + spa_device_emit_object_info(&this->hooks, 0, &oinfo); + } + + spa_libcamera_close(&this->dev); + + return 0; +} + +static int impl_add_listener(void *object, + struct spa_hook *listener, + const struct spa_device_events *events, + void *data) +{ + struct impl *this = object; + struct spa_hook_list save; + int res = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(events != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + if (events->info || events->object_info) + res = emit_info(this, true); + + spa_hook_list_join(&this->hooks, &save); + + return res; +} + +static int impl_sync(void *object, int seq) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_device_emit_result(&this->hooks, seq, 0, 0, NULL); + + return 0; +} + +static int impl_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + return -ENOTSUP; +} + +static int impl_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return -ENOTSUP; +} + +static const struct spa_device_methods impl_device = { + SPA_VERSION_DEVICE_METHODS, + .add_listener = impl_add_listener, + .sync = impl_sync, + .enum_params = impl_enum_params, + .set_param = impl_set_param, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (strcmp(type, SPA_TYPE_INTERFACE_Device) == 0) + *interface = &this->device; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + const char *str; + int res; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear, this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + + spa_hook_list_init(&this->hooks); + + this->device.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Device, + SPA_VERSION_DEVICE, + &impl_device, this); + this->dev.log = this->log; + this->dev.fd = -1; + + reset_props(&this->props); + + if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) + strncpy(this->props.device, str, 63); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Device,}, +}; + +static int impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + if (*index >= SPA_N_ELEMENTS(impl_interfaces)) + return 0; + + *info = &impl_interfaces[(*index)++]; + return 1; +} + +const struct spa_handle_factory spa_libcamera_device_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_LIBCAMERA_DEVICE, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/libcamera/libcamera-source.c b/spa/plugins/libcamera/libcamera-source.c new file mode 100644 index 000000000..5bb233c23 --- /dev/null +++ b/spa/plugins/libcamera/libcamera-source.c @@ -0,0 +1,1026 @@ +/* Spa libcamera Source + * + * Copyright (C) 2020, Collabora Ltd. + * Author: Raghavendra Rao Sidlagatta + * + * libcamera-source.c + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libcamera.h" + +#define NAME "libcamera-source" + +static const char default_device[] = "/dev/media0"; + +struct props { + char device[64]; + char device_name[128]; + int device_fd; +}; + +static void reset_props(struct props *props) +{ + strncpy(props->device, default_device, 64); +} + +#define MAX_BUFFERS 32 + +#define BUFFER_FLAG_OUTSTANDING (1<<0) +#define BUFFER_FLAG_ALLOCATED (1<<1) +#define BUFFER_FLAG_MAPPED (1<<2) + +struct buffer { + uint32_t id; + uint32_t flags; + struct spa_list link; + struct spa_buffer *outbuf; + struct spa_meta_header *h; + void *ptr; +}; + +#define MAX_CONTROLS 64 + +struct control { + uint32_t id; + uint32_t ctrl_id; + double value; +}; + +struct camera_fmt { + uint32_t width; + uint32_t height; + uint32_t pixelformat; + uint32_t bytesperline; + uint32_t numerator; + uint32_t denominator; + uint32_t sizeimage; +}; + +struct port { + struct impl *impl; + + bool export_buf; + + bool next_fmtdesc; + uint32_t fmtdesc_index; + bool next_frmsize; + + bool have_format; + struct spa_video_info current_format; + struct spa_fraction rate; + + struct spa_libcamera_device dev; + + bool have_query_ext_ctrl; + struct camera_fmt fmt; + uint32_t memtype; + + struct control controls[MAX_CONTROLS]; + uint32_t n_controls; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + struct spa_list queue; + + struct spa_source source; + + uint64_t info_all; + struct spa_port_info info; + struct spa_io_buffers *io; + struct spa_io_sequence *control; + struct spa_param_info params[8]; +}; + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + struct spa_loop *data_loop; + + uint64_t info_all; + struct spa_node_info info; + struct spa_param_info params[8]; + struct props props; + + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + struct port out_ports[1]; + + struct spa_io_position *position; + struct spa_io_clock *clock; +}; + +#define CHECK_PORT(this,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) + +#define GET_OUT_PORT(this,p) (&this->out_ports[p]) +#define GET_PORT(this,d,p) GET_OUT_PORT(this,p) + +#include "libcamera-utils.c" + +static int impl_node_enum_params(void *object, int seq, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct impl *this = object; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_result_node_params result; + uint32_t count = 0; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + + result.id = id; + result.next = start; +next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), + SPA_PROP_INFO_name, SPA_POD_String("The libcamera device"), + SPA_PROP_INFO_type, SPA_POD_String(p->device)); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), + SPA_PROP_INFO_name, SPA_POD_String("The libcamera device name"), + SPA_PROP_INFO_type, SPA_POD_String(p->device_name)); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceFd), + SPA_PROP_INFO_name, SPA_POD_String("The libcamera fd"), + SPA_PROP_INFO_type, SPA_POD_Int(p->device_fd)); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_Props: + { + struct props *p = &this->props; + + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_device, SPA_POD_String(p->device), + SPA_PROP_deviceName, SPA_POD_String(p->device_name), + SPA_PROP_deviceFd, SPA_POD_Int(p->device_fd)); + break; + default: + return 0; + } + break; + } + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int impl_node_set_param(void *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_PARAM_Props: + { + struct props *p = &this->props; + + if (param == NULL) { + reset_props(p); + return 0; + } + spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device))); + break; + } + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + switch (id) { + case SPA_IO_Clock: + this->clock = data; + break; + case SPA_IO_Position: + this->position = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_send_command(void *object, const struct spa_command *command) +{ + struct impl *this = object; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(command != NULL, -EINVAL); + + switch (SPA_NODE_COMMAND_ID(command)) { + case SPA_NODE_COMMAND_Start: + { + struct port *port = GET_OUT_PORT(this, 0); + + if (!port->have_format) + return -EIO; + if (port->n_buffers == 0) + return -EIO; + + if ((res = spa_libcamera_stream_on(this)) < 0) + return res; + break; + } + case SPA_NODE_COMMAND_Pause: + case SPA_NODE_COMMAND_Suspend: + if ((res = spa_libcamera_stream_off(this)) < 0) + return res; + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static const struct spa_dict_item info_items[] = { + { SPA_KEY_DEVICE_API, "libcamera" }, + { SPA_KEY_MEDIA_CLASS, "Video/Source" }, + { SPA_KEY_MEDIA_ROLE, "Camera" }, + { SPA_KEY_NODE_PAUSE_ON_IDLE, "false" }, + { SPA_KEY_NODE_DRIVER, "true" }, +}; + +static void emit_node_info(struct impl *this, bool full) +{ + if (full) + this->info.change_mask = this->info_all; + if (this->info.change_mask) { + this->info.props = &SPA_DICT_INIT_ARRAY(info_items); + spa_node_emit_info(&this->hooks, &this->info); + this->info.change_mask = 0; + } +} + +static void emit_port_info(struct impl *this, struct port *port, bool full) +{ + if (full) + port->info.change_mask = port->info_all; + if (port->info.change_mask) { + spa_node_emit_port_info(&this->hooks, + SPA_DIRECTION_OUTPUT, 0, &port->info); + port->info.change_mask = 0; + } +} + +static int +impl_node_add_listener(void *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + struct impl *this = object; + struct spa_hook_list save; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_hook_list_isolate(&this->hooks, &save, listener, events, data); + + emit_node_info(this, true); + emit_port_info(this, GET_OUT_PORT(this, 0), true); + + spa_hook_list_join(&this->hooks, &save); + + return 0; +} + +static int impl_node_set_callbacks(void *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); + + return 0; +} + +static int impl_node_sync(void *object, int seq) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); + + return 0; +} + +static int impl_node_add_port(void *object, + enum spa_direction direction, + uint32_t port_id, const struct spa_dict *props) +{ + return -ENOTSUP; +} + +static int impl_node_remove_port(void *object, + enum spa_direction direction, + uint32_t port_id) +{ + return -ENOTSUP; +} + +static int port_get_format(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t index, + const struct spa_pod *filter, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + struct impl *this = object; + struct port *port = GET_PORT(this, direction, port_id); + struct spa_pod_frame f; + + if (!port->have_format) + return -EIO; + if (index > 0) + return 0; + + spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format.media_type), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format.media_subtype), + 0); + + switch (port->current_format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format), + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate), + 0); + break; + case SPA_MEDIA_SUBTYPE_mjpg: + case SPA_MEDIA_SUBTYPE_jpeg: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.mjpg.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.mjpg.framerate), + 0); + break; + case SPA_MEDIA_SUBTYPE_h264: + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.h264.size), + SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.h264.framerate), + 0); + break; + default: + return -EIO; + } + + *param = spa_pod_builder_pop(builder, &f); + + return 1; +} + +static int impl_node_port_enum_params(void *object, int seq, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + + struct impl *this = object; + struct port *port; + struct spa_pod *param; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[1024]; + struct spa_result_node_params result; + uint32_t count = 0; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(num != 0, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + result.id = id; + result.next = start; +next: + result.index = result.next++; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + switch (id) { + case SPA_PARAM_PropInfo: + return spa_libcamera_enum_controls(this, seq, start, num, filter); + + case SPA_PARAM_EnumFormat: + return spa_libcamera_enum_format(this, seq, start, num, filter); + + case SPA_PARAM_Format: + if((res = port_get_format(this, direction, port_id, + result.index, filter, ¶m, &b)) <= 0) + return res; + break; + case SPA_PARAM_Buffers: + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + /* Get the number of buffers to be used from libcamera and send the same to pipewire + * so that exact number of buffers are allocated + */ + uint32_t n_buffers = libcamera_get_nbuffers(port->dev.camera); + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(n_buffers, n_buffers, n_buffers), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_size, SPA_POD_Int(port->fmt.sizeimage), + SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->fmt.bytesperline), + SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); + break; + + case SPA_PARAM_Meta: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamMeta, id, + SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), + SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); + break; + default: + return 0; + } + break; + case SPA_PARAM_IO: + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); + break; + case 1: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); + break; + case 2: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamIO, id, + SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Control), + SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence))); + break; + default: + return 0; + } + break; + default: + return -ENOENT; + } + + if (spa_pod_filter(&b, &result.param, param, filter) < 0) + goto next; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + if (++count != num) + goto next; + + return 0; +} + +static int port_set_format(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) +{ + struct impl *this = object; + struct spa_video_info info; + struct port *port = GET_PORT(this, direction, port_id); + int res; + + if (format == NULL) { + if (!port->have_format) + return 0; + + spa_libcamera_stream_off(this); + spa_libcamera_clear_buffers(this); + port->have_format = false; + port->dev.have_format = false; + + spa_libcamera_close(&port->dev); + goto done; + } else { + if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + return res; + + if (info.media_type != SPA_MEDIA_TYPE_video) { + spa_log_error(this->log, "media type must be video"); + return -EINVAL; + } + + switch (info.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + if (spa_format_video_raw_parse(format, &info.info.raw) < 0) { + spa_log_error(this->log, "can't parse video raw"); + return -EINVAL; + } + + if (port->have_format && info.media_type == port->current_format.media_type && + info.media_subtype == port->current_format.media_subtype && + info.info.raw.format == port->current_format.info.raw.format && + info.info.raw.size.width == port->current_format.info.raw.size.width && + info.info.raw.size.height == port->current_format.info.raw.size.height) + return 0; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) + return -EINVAL; + + if (port->have_format && info.media_type == port->current_format.media_type && + info.media_subtype == port->current_format.media_subtype && + info.info.mjpg.size.width == port->current_format.info.mjpg.size.width && + info.info.mjpg.size.height == port->current_format.info.mjpg.size.height) + return 0; + break; + case SPA_MEDIA_SUBTYPE_h264: + if (spa_format_video_h264_parse(format, &info.info.h264) < 0) + return -EINVAL; + + if (port->have_format && info.media_type == port->current_format.media_type && + info.media_subtype == port->current_format.media_subtype && + info.info.h264.size.width == port->current_format.info.h264.size.width && + info.info.h264.size.height == port->current_format.info.h264.size.height) + return 0; + break; + default: + return -EINVAL; + } + } + + if (port->have_format && !(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + spa_libcamera_use_buffers(this, NULL, 0); + port->have_format = false; + } + + if (spa_libcamera_set_format(this, &info, flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) < 0) + return -EINVAL; + + if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { + port->current_format = info; + port->have_format = true; + } + + done: + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + if (port->have_format) { + port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); + port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); + } else { + port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + } + emit_port_info(this, port, false); + + return 0; +} + +static int impl_node_port_set_param(void *object, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + spa_return_val_if_fail(object != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL); + + if (id == SPA_PARAM_Format) { + return port_set_format(object, direction, port_id, flags, param); + } + else + return -ENOENT; +} + +static int impl_node_port_use_buffers(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + struct impl *this = object; + struct port *port; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + if (!port->have_format) + return -EIO; + + if (port->n_buffers) { + spa_libcamera_stream_off(this); + if ((res = spa_libcamera_clear_buffers(this)) < 0) + return res; + } + if (buffers == NULL) + return 0; + + if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) { + res = spa_libcamera_alloc_buffers(this, buffers, n_buffers); + } else { + res = spa_libcamera_use_buffers(this, buffers, n_buffers); + } + return res; +} + +static int impl_node_port_set_io(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, + void *data, size_t size) +{ + struct impl *this = object; + struct port *port; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + port = GET_PORT(this, direction, port_id); + + switch (id) { + case SPA_IO_Buffers: + port->io = data; + break; + case SPA_IO_Control: + port->control = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int impl_node_port_reuse_buffer(void *object, + uint32_t port_id, + uint32_t buffer_id) +{ + struct impl *this = object; + struct port *port; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + spa_return_val_if_fail(port_id == 0, -EINVAL); + + port = GET_OUT_PORT(this, port_id); + + spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); + + res = spa_libcamera_buffer_recycle(this, buffer_id); + + return res; +} + +static void set_control(struct impl *this, struct port *port, uint32_t control_id, float value) +{ + if(libcamera_set_control(port->dev.camera, control_id, value) < 0) { + spa_log_error(this->log, "Failed to set control"); + } +} + +static int process_control(struct impl *this, struct spa_pod_sequence *control) +{ + struct spa_pod_control *c; + struct port *port; + + SPA_POD_SEQUENCE_FOREACH(control, c) { + switch (c->type) { + case SPA_CONTROL_Properties: + { + struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) &c->value; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + port = GET_OUT_PORT(this, 0); + set_control(this, port, prop->key, + SPA_POD_VALUE(struct spa_pod_float, &prop->value)); + } + break; + } + default: + break; + } + } + return 0; +} + +static int impl_node_process(void *object) +{ + struct impl *this = object; + int res; + struct spa_io_buffers *io; + struct port *port; + struct buffer *b; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + port = GET_OUT_PORT(this, 0); + io = port->io; + spa_return_val_if_fail(io != NULL, -EIO); + + if (port->control) + process_control(this, &port->control->sequence); + + spa_log_trace(this->log, NAME " %p; status %d", this, io->status); + + if (io->status == SPA_STATUS_HAVE_DATA) { + return SPA_STATUS_HAVE_DATA; + } + + if (io->buffer_id < port->n_buffers) { + if ((res = spa_libcamera_buffer_recycle(this, io->buffer_id)) < 0) { + return res; + } + + io->buffer_id = SPA_ID_INVALID; + } + + if (spa_list_is_empty(&port->queue)) { + return SPA_STATUS_OK; + } + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + + spa_log_trace(this->log, NAME " %p: dequeue buffer %d", this, b->id); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + + return SPA_STATUS_HAVE_DATA; +} + +static const struct spa_node_methods impl_node = { + SPA_VERSION_NODE_METHODS, + .add_listener = impl_node_add_listener, + .set_callbacks = impl_node_set_callbacks, + .sync = impl_node_sync, + .enum_params = impl_node_enum_params, + .set_param = impl_node_set_param, + .set_io = impl_node_set_io, + .send_command = impl_node_send_command, + .add_port = impl_node_add_port, + .remove_port = impl_node_remove_port, + .port_enum_params = impl_node_port_enum_params, + .port_set_param = impl_node_port_set_param, + .port_use_buffers = impl_node_port_use_buffers, + .port_set_io = impl_node_port_set_io, + .port_reuse_buffer = impl_node_port_reuse_buffer, + .process = impl_node_process, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + this = (struct impl *) handle; + + if (strcmp(type, SPA_TYPE_INTERFACE_Node) == 0) + *interface = &this->node; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + struct port *port; + + this = (struct impl *) handle; + port = GET_OUT_PORT(this, 0); + + if(port->dev.camera) { + deleteLibCamera(port->dev.camera); + free(port->dev.camera); + port->dev.camera = NULL; + } + + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct impl); +} + +static int +impl_init(const struct spa_handle_factory *factory, + struct spa_handle *handle, + const struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + struct impl *this; + const char *str; + struct port *port; + int res; + int err; + + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(handle != NULL, -EINVAL); + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + this = (struct impl *) handle; + + this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data_loop is needed"); + return -EINVAL; + } + + this->node.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_Node, + SPA_VERSION_NODE, + &impl_node, this); + spa_hook_list_init(&this->hooks); + + this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | + SPA_NODE_CHANGE_MASK_PROPS | + SPA_NODE_CHANGE_MASK_PARAMS; + this->info = SPA_NODE_INFO_INIT(); + this->info.max_output_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT; + this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); + this->info.params = this->params; + this->info.n_params = 2; + reset_props(&this->props); + + port = GET_OUT_PORT(this, 0); + port->impl = this; + spa_list_init(&port->queue); + port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | + SPA_PORT_CHANGE_MASK_PARAMS; + port->info = SPA_PORT_INFO_INIT(); + port->info.flags = SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL; + port->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); + port->params[1] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); + port->params[2] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); + port->params[3] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); + port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); + port->params[5] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); + port->info.params = port->params; + port->info.n_params = 6; + + port->export_buf = true; + port->have_query_ext_ctrl = true; + port->dev.log = this->log; + port->dev.fd = -1; + + if(port->dev.camera == NULL) { + port->dev.camera = (LibCamera*)newLibCamera(); + libcamera_set_log(port->dev.camera, port->dev.log); + } + + if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) { + strncpy(this->props.device, str, 63); + if ((res = spa_libcamera_open(&port->dev)) < 0) + return res; + spa_libcamera_close(&port->dev); + } + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_Node,}, +}; + +static int impl_enum_interface_info(const struct spa_handle_factory *factory, + const struct spa_interface_info **info, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(info != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + if (*index >= SPA_N_ELEMENTS(impl_interfaces)) + return 0; + + *info = &impl_interfaces[(*index)++]; + return 1; +} + +const struct spa_handle_factory spa_libcamera_source_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_API_LIBCAMERA_SOURCE, + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/libcamera/libcamera-utils.c b/spa/plugins/libcamera/libcamera-utils.c new file mode 100644 index 000000000..f6e191bf8 --- /dev/null +++ b/spa/plugins/libcamera/libcamera-utils.c @@ -0,0 +1,955 @@ +/* Spa + * + * Copyright (C) 2020, Collabora Ltd. + * Author: Raghavendra Rao Sidlagatta + * + * libcamera-utils.c + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include + +#include + +static void libcamera_on_fd_events(struct spa_source *source); + +int get_dev_fd(struct spa_libcamera_device *dev) { + if(dev->fd == -1) { + int fd = open("/dev/media0", O_RDONLY | O_NONBLOCK, 0); + return fd; + } else { + return dev->fd; + } +} + +int spa_libcamera_open(struct spa_libcamera_device *dev) +{ + int refCnt = 0; + + if(!dev) { + return -1; + } + + dev->fd = get_dev_fd(dev); + + return 0; +} + +int spa_libcamera_is_capture(struct spa_libcamera_device *dev) +{ + if(!dev) { + spa_log_error(dev->log, "Invalid argument"); + return false; + } + return true; +} + +int spa_libcamera_close(struct spa_libcamera_device *dev) +{ + int refCnt = 0; + if(!dev) { + spa_log_error(dev->log, "Invalid argument"); + return -1; + } + + if (dev->fd == -1) { + return 0; + } + + if (dev->active || dev->have_format) { + return 0; + } + + if (close(dev->fd)) { + spa_log_warn(dev->log, "close: %m"); + } + + dev->fd = -1; + return 0; +} + +static int spa_libcamera_buffer_recycle(struct impl *this, uint32_t buffer_id) +{ + struct port *port = &this->out_ports[0]; + struct buffer *b = &port->buffers[buffer_id]; + struct spa_libcamera_device *dev = &port->dev; + int err; + + if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) + return 0; + + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUTSTANDING); + return 0; +} + +static int spa_libcamera_clear_buffers(struct impl *this) +{ + struct port *port = &this->out_ports[0]; + uint32_t i; + + if (port->n_buffers == 0) + return 0; + + for (i = 0; i < port->n_buffers; i++) { + struct buffer *b; + struct spa_data *d; + + b = &port->buffers[i]; + d = b->outbuf->datas; + + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) { + spa_log_debug(this->log, "libcamera: queueing outstanding buffer %p", b); + spa_libcamera_buffer_recycle(this, i); + } + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { + munmap(SPA_MEMBER(b->ptr, -d[0].mapoffset, void), + d[0].maxsize - d[0].mapoffset); + } + if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) { + close(d[0].fd); + } + d[0].type = SPA_ID_INVALID; + } + + port->n_buffers = 0; + + return 0; +} + +struct format_info { + char fourcc[32]; + uint32_t format; + uint32_t media_type; + uint32_t media_subtype; +}; + +#define VIDEO SPA_MEDIA_TYPE_video +#define IMAGE SPA_MEDIA_TYPE_image + +#define RAW SPA_MEDIA_SUBTYPE_raw + +#define BAYER SPA_MEDIA_SUBTYPE_bayer +#define MJPG SPA_MEDIA_SUBTYPE_mjpg +#define JPEG SPA_MEDIA_SUBTYPE_jpeg +#define DV SPA_MEDIA_SUBTYPE_dv +#define MPEGTS SPA_MEDIA_SUBTYPE_mpegts +#define H264 SPA_MEDIA_SUBTYPE_h264 +#define H263 SPA_MEDIA_SUBTYPE_h263 +#define MPEG1 SPA_MEDIA_SUBTYPE_mpeg1 +#define MPEG2 SPA_MEDIA_SUBTYPE_mpeg2 +#define MPEG4 SPA_MEDIA_SUBTYPE_mpeg4 +#define XVID SPA_MEDIA_SUBTYPE_xvid +#define VC1 SPA_MEDIA_SUBTYPE_vc1 +#define VP8 SPA_MEDIA_SUBTYPE_vp8 + +#define FORMAT_UNKNOWN SPA_VIDEO_FORMAT_UNKNOWN +#define FORMAT_ENCODED SPA_VIDEO_FORMAT_ENCODED +#define FORMAT_RGB15 SPA_VIDEO_FORMAT_RGB15 +#define FORMAT_BGR15 SPA_VIDEO_FORMAT_BGR15 +#define FORMAT_RGB16 SPA_VIDEO_FORMAT_RGB16 +#define FORMAT_BGR SPA_VIDEO_FORMAT_BGR +#define FORMAT_RGB SPA_VIDEO_FORMAT_RGB +#define FORMAT_BGRA SPA_VIDEO_FORMAT_BGRA +#define FORMAT_BGRx SPA_VIDEO_FORMAT_BGRx +#define FORMAT_ARGB SPA_VIDEO_FORMAT_ARGB +#define FORMAT_xRGB SPA_VIDEO_FORMAT_xRGB +#define FORMAT_GRAY8 SPA_VIDEO_FORMAT_GRAY8 +#define FORMAT_GRAY16_LE SPA_VIDEO_FORMAT_GRAY16_LE +#define FORMAT_GRAY16_BE SPA_VIDEO_FORMAT_GRAY16_BE +#define FORMAT_YVU9 SPA_VIDEO_FORMAT_YVU9 +#define FORMAT_YV12 SPA_VIDEO_FORMAT_YV12 +#define FORMAT_YUY2 SPA_VIDEO_FORMAT_YUY2 +#define FORMAT_YVYU SPA_VIDEO_FORMAT_YVYU +#define FORMAT_UYVY SPA_VIDEO_FORMAT_UYVY +#define FORMAT_Y42B SPA_VIDEO_FORMAT_Y42B +#define FORMAT_Y41B SPA_VIDEO_FORMAT_Y41B +#define FORMAT_YUV9 SPA_VIDEO_FORMAT_YUV9 +#define FORMAT_I420 SPA_VIDEO_FORMAT_I420 +#define FORMAT_NV12 SPA_VIDEO_FORMAT_NV12 +#define FORMAT_NV12_64Z32 SPA_VIDEO_FORMAT_NV12_64Z32 +#define FORMAT_NV21 SPA_VIDEO_FORMAT_NV21 +#define FORMAT_NV16 SPA_VIDEO_FORMAT_NV16 +#define FORMAT_NV61 SPA_VIDEO_FORMAT_NV61 +#define FORMAT_NV24 SPA_VIDEO_FORMAT_NV24 + +static const struct format_info format_info[] = { + /* RGB formats */ + {{"RGB332"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"ARGB555"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"XRGB555"}, FORMAT_RGB15, VIDEO, RAW}, + {{"ARGB555X"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"XRGB555X"}, FORMAT_BGR15, VIDEO, RAW}, + {{"RGB565"}, FORMAT_RGB16, VIDEO, RAW}, + {{"RGB565X"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"BGR666"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"BGR24"}, FORMAT_BGR, VIDEO, RAW}, + {{"RGB24"}, FORMAT_RGB, VIDEO, RAW}, + {{"ABGR32"}, FORMAT_BGRA, VIDEO, RAW}, + {{"XBGR32"}, FORMAT_BGRx, VIDEO, RAW}, + {{"ARGB32"}, FORMAT_ARGB, VIDEO, RAW}, + {{"XRGB32"}, FORMAT_xRGB, VIDEO, RAW}, + + /* Deprecated Packed RGB Image Formats (alpha ambiguity) */ + {{"RGB444"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"RGB555"}, FORMAT_RGB15, VIDEO, RAW}, + {{"RGB555X"}, FORMAT_BGR15, VIDEO, RAW}, + {{"BGR32"}, FORMAT_BGRx, VIDEO, RAW}, + {{"RGB32"}, FORMAT_xRGB, VIDEO, RAW}, + + /* Grey formats */ + {{"GREY"}, FORMAT_GRAY8, VIDEO, RAW}, + {{"Y4"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"Y6"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"Y10"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"Y12"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"Y16"}, FORMAT_GRAY16_LE, VIDEO, RAW}, + {{"Y16_BE"}, FORMAT_GRAY16_BE, VIDEO, RAW}, + {{"Y10BPACK"}, FORMAT_UNKNOWN, VIDEO, RAW}, + + /* Palette formats */ + {{"PAL8"}, FORMAT_UNKNOWN, VIDEO, RAW}, + + /* Chrominance formats */ + {{"UV8"}, FORMAT_UNKNOWN, VIDEO, RAW}, + + /* Luminance+Chrominance formats */ + {{"YVU410"}, FORMAT_YVU9, VIDEO, RAW}, + {{"YVU420"}, FORMAT_YV12, VIDEO, RAW}, + {{"YVU420M"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YUYV"}, FORMAT_YUY2, VIDEO, RAW}, + {{"YYUV"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YVYU"}, FORMAT_YVYU, VIDEO, RAW}, + {{"UYVY"}, FORMAT_UYVY, VIDEO, RAW}, + {{"VYUY"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YUV422P"}, FORMAT_Y42B, VIDEO, RAW}, + {{"YUV411P"}, FORMAT_Y41B, VIDEO, RAW}, + {{"Y41P"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YUV444"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YUV555"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YUV565"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YUV32"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"YUV410"}, FORMAT_YUV9, VIDEO, RAW}, + {{"YUV420"}, FORMAT_I420, VIDEO, RAW}, + {{"YUV420M"}, FORMAT_I420, VIDEO, RAW}, + {{"HI240"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"HM12"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"M420"}, FORMAT_UNKNOWN, VIDEO, RAW}, + + /* two planes -- one Y, one Cr + Cb interleaved */ + {{"NV12"}, FORMAT_NV12, VIDEO, RAW}, + {{"NV12M"}, FORMAT_NV12, VIDEO, RAW}, + {{"NV12MT"}, FORMAT_NV12_64Z32, VIDEO, RAW}, + {{"NV12MT_16X16"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"NV21"}, FORMAT_NV21, VIDEO, RAW}, + {{"NV21M"}, FORMAT_NV21, VIDEO, RAW}, + {{"NV16"}, FORMAT_NV16, VIDEO, RAW}, + {{"NV16M"}, FORMAT_NV16, VIDEO, RAW}, + {{"NV61"}, FORMAT_NV61, VIDEO, RAW}, + {{"NV61M"}, FORMAT_NV61, VIDEO, RAW}, + {{"NV24"}, FORMAT_NV24, VIDEO, RAW}, + {{"NV42"}, FORMAT_UNKNOWN, VIDEO, RAW}, + + /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */ + {{"SBGGR8"}, FORMAT_UNKNOWN, VIDEO, BAYER}, + {{"SGBRG8"}, FORMAT_UNKNOWN, VIDEO, BAYER}, + {{"SGRBG8"}, FORMAT_UNKNOWN, VIDEO, BAYER}, + {{"SRGGB8"}, FORMAT_UNKNOWN, VIDEO, BAYER}, + + /* compressed formats */ + {{"MJPEG"}, FORMAT_ENCODED, VIDEO, MJPG}, + {{"JPEG"}, FORMAT_ENCODED, VIDEO, MJPG}, + {{"PJPG"}, FORMAT_ENCODED, VIDEO, MJPG}, + {{"DV"}, FORMAT_ENCODED, VIDEO, DV}, + {{"MPEG"}, FORMAT_ENCODED, VIDEO, MPEGTS}, + {{"H264"}, FORMAT_ENCODED, VIDEO, H264}, + {{"H264_NO_SC"}, FORMAT_ENCODED, VIDEO, H264}, + {{"H264_MVC"}, FORMAT_ENCODED, VIDEO, H264}, + {{"H263"}, FORMAT_ENCODED, VIDEO, H263}, + {{"MPEG1"}, FORMAT_ENCODED, VIDEO, MPEG1}, + {{"MPEG2"}, FORMAT_ENCODED, VIDEO, MPEG2}, + {{"MPEG4"}, FORMAT_ENCODED, VIDEO, MPEG4}, + {{"XVID"}, FORMAT_ENCODED, VIDEO, XVID}, + {{"VC1_ANNEX_G"}, FORMAT_ENCODED, VIDEO, VC1}, + {{"VC1_ANNEX_L"}, FORMAT_ENCODED, VIDEO, VC1}, + {{"VP8"}, FORMAT_ENCODED, VIDEO, VP8}, + + /* Vendor-specific formats */ + {{"WNVA"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"SN9C10X"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"PWC1"}, FORMAT_UNKNOWN, VIDEO, RAW}, + {{"PWC2"}, FORMAT_UNKNOWN, VIDEO, RAW}, +}; + +static const struct format_info *video_format_to_info(uint32_t fmt) { + size_t i; + + for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { + if (format_info[i].format == fmt) + return &format_info[i]; + } + return NULL; +} + +static const struct format_info *find_format_info_by_media_type(uint32_t type, + uint32_t subtype, + uint32_t format, + int startidx) +{ + size_t i; + + for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) { + if ((format_info[i].media_type == type) && + (format_info[i].media_subtype == subtype) && + (format == 0 || format_info[i].format == format)) + return &format_info[i]; + } + return NULL; +} + +static uint32_t +enum_filter_format(uint32_t media_type, int32_t media_subtype, + const struct spa_pod *filter, uint32_t index) +{ + uint32_t video_format = 0; + + switch (media_type) { + case SPA_MEDIA_TYPE_video: + case SPA_MEDIA_TYPE_image: + if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { + const struct spa_pod_prop *p; + const struct spa_pod *val; + uint32_t n_values, choice; + const uint32_t *values; + + if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_format))) + return SPA_VIDEO_FORMAT_UNKNOWN; + + val = spa_pod_get_values(&p->value, &n_values, &choice); + + if (val->type != SPA_TYPE_Id) + return SPA_VIDEO_FORMAT_UNKNOWN; + + values = SPA_POD_BODY(val); + + if (choice == SPA_CHOICE_None) { + if (index == 0) + video_format = values[0]; + } else { + if (index + 1 < n_values) + video_format = values[index + 1]; + } + } else { + if (index == 0) + video_format = SPA_VIDEO_FORMAT_ENCODED; + } + } + return video_format; +} + +#define FOURCC_ARGS(f) (f)&0x7f,((f)>>8)&0x7f,((f)>>16)&0x7f,((f)>>24)&0x7f + +static int +spa_libcamera_enum_format(struct impl *this, int seq, + uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + struct port *port = &this->out_ports[0]; + int res, n_fractions; + const struct format_info *info; + struct spa_pod_choice *choice; + uint32_t filter_media_type, filter_media_subtype, video_format; + struct spa_libcamera_device *dev = &port->dev; + uint8_t buffer[1024]; + struct spa_pod_builder b = { 0 }; + struct spa_pod_frame f[2]; + struct spa_result_node_params result; + uint32_t count = 0; + uint32_t width = 0, height = 0; + + if ((res = spa_libcamera_open(dev)) < 0) { + spa_log_error(dev->log, "failed to open libcamera device"); + return res; + } + + result.id = SPA_PARAM_EnumFormat; + result.next = start; + + if (result.next == 0) { + port->fmtdesc_index = 0; + spa_zero(port->fmt); + } + +next_fmtdesc: + port->fmtdesc_index++; + +next: + result.index = result.next++; + + /* Enumerate all the video formats supported by libcamera */ + video_format = libcamera_drm_to_video_format( + libcamera_enum_streamcfgpixel_format(dev->camera, port->fmtdesc_index)); + if(UINT32_MAX == video_format) { + goto enum_end; + } + port->fmt.pixelformat = video_format; + port->fmt.width = libcamera_get_streamcfg_width(dev->camera); + port->fmt.height = libcamera_get_streamcfg_height(dev->camera); + + if (!(info = video_format_to_info(video_format))) { + goto next_fmtdesc; + } + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(&b, + SPA_FORMAT_mediaType, SPA_POD_Id(info->media_type), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(info->media_subtype), + 0); + + if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { + spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_format, 0); + spa_pod_builder_id(&b, info->format); + } + +have_size: + spa_log_info(this->log, "%s:: In have_size: Got width = %u height = %u\n", __FUNCTION__, width, height); + + spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_size, 0); + spa_pod_builder_rectangle(&b, port->fmt.width, port->fmt.height); + +have_framerate: + spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_framerate, 0); + + spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b, &f[1]); + + /* Below framerates are hardcoded until framerates are queried from libcamera */ + port->fmt.denominator = 30; + port->fmt.numerator = 1; + + spa_pod_builder_fraction(&b, + port->fmt.denominator, + port->fmt.numerator); + + spa_pod_builder_pop(&b, &f[1]); + result.param = spa_pod_builder_pop(&b, &f[0]); + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + + goto next_fmtdesc; + +enum_end: + res = 0; +exit: + spa_libcamera_close(dev); + return res; +} + +static int spa_libcamera_set_format(struct impl *this, struct spa_video_info *format, bool try_only) +{ + struct port *port = &this->out_ports[0]; + struct spa_libcamera_device *dev = &port->dev; + int res, cmd; + struct camera_fmt fmt; + const struct format_info *info = NULL; + uint32_t video_format; + struct spa_rectangle *size = NULL; + struct spa_fraction *framerate = NULL; + + spa_zero(fmt); + + switch (format->media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + video_format = format->info.raw.format; + size = &format->info.raw.size; + framerate = &format->info.raw.framerate; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + case SPA_MEDIA_SUBTYPE_jpeg: + video_format = SPA_VIDEO_FORMAT_ENCODED; + size = &format->info.mjpg.size; + framerate = &format->info.mjpg.framerate; + break; + case SPA_MEDIA_SUBTYPE_h264: + video_format = SPA_VIDEO_FORMAT_ENCODED; + size = &format->info.h264.size; + framerate = &format->info.h264.framerate; + break; + default: + video_format = SPA_VIDEO_FORMAT_ENCODED; + break; + } + + info = find_format_info_by_media_type(format->media_type, + format->media_subtype, video_format, 0); + if (info == NULL || size == NULL || framerate == NULL) { + spa_log_error(this->log, "libcamera: unknown media type %d %d %d", format->media_type, + format->media_subtype, video_format); + return -EINVAL; + } + + fmt.pixelformat = video_format; + fmt.width = size->width; + fmt.height = size->height; + fmt.sizeimage = libcamera_get_max_size(dev->camera); + fmt.bytesperline = libcamera_get_stride(dev->camera); + fmt.numerator = framerate->denom; + fmt.denominator = framerate->num; + + if ((res = spa_libcamera_open(dev)) < 0) + return res; + + /* stop the camera first. It might have opened with different configuration*/ + libcamera_stop_capture(dev->camera); + + spa_log_info(dev->log, "libcamera: set %s %dx%d %d/%d\n", (char *)&info->fourcc, + fmt.width, fmt.height, + fmt.denominator, fmt.numerator); + + libcamera_set_streamcfgpixel_format(dev->camera, libcamera_video_format_to_drm(video_format)); + libcamera_set_streamcfg_width(dev->camera, size->width); + libcamera_set_streamcfg_height(dev->camera, size->height); + + /* start the camera now with the configured params */ + libcamera_start_capture(dev->camera); + + dev->have_format = true; + size->width = libcamera_get_streamcfg_width(dev->camera); + size->height = libcamera_get_streamcfg_height(dev->camera); + port->rate.denom = framerate->num = fmt.denominator; + port->rate.num = framerate->denom = fmt.numerator; + + port->fmt = fmt; + port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE; + port->info.flags = (port->export_buf ? SPA_PORT_FLAG_CAN_ALLOC_BUFFERS : 0) | + SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL; + port->info.rate = SPA_FRACTION(port->rate.num, port->rate.denom); + + spa_log_info(dev->log, " got format. width = %d height = %d and fmt = %s. bytesperline = %u sizeimage = %u\n", + fmt.width, fmt.height, + (char *)&info->fourcc, fmt.bytesperline, fmt.sizeimage); + + return 0; +} + +static int +spa_libcamera_enum_controls(struct impl *this, int seq, + uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + return -ENOTSUP; +} + +static int mmap_read(struct impl *this) +{ + struct port *port = &this->out_ports[0]; + struct spa_libcamera_device *dev = &port->dev; + struct buffer *b; + struct spa_data *d; + unsigned int sequence; + struct timeval timestamp; + int64_t pts; + struct OutBuf *pOut = NULL; + struct CamData *pDatas = NULL; + uint32_t bytesused; + + if(dev->camera) { + if(!libcamera_is_data_available(dev->camera)) { + return -1; + } + + pOut = (struct OutBuf *)libcamera_get_ring_buffer_data(dev->camera); + if(!pOut) { + spa_log_debug(this->log, "Exiting %s as pOut is NULL\n", __FUNCTION__); + return -1; + } + /* update the read index of the ring buffer */ + libcamera_ringbuffer_read_update(dev->camera); + + pDatas = pOut->datas; + if(NULL == pDatas) { + spa_log_debug(this->log, "Exiting %s on NULL pointer\n", __FUNCTION__); + goto end; + } + + b = &port->buffers[pOut->bufIdx]; + b->outbuf->n_datas = pOut->n_datas; + + if(NULL == b->outbuf->datas) { + spa_log_debug(this->log, "Exiting %s as b->outbuf->datas is NULL\n", __FUNCTION__); + goto end; + } + + for(unsigned int i = 0; i < pOut->n_datas; ++i) { + struct CamData *pData = &pDatas[i]; + if(NULL == pData) { + spa_log_debug(this->log, "Exiting %s on NULL pointer\n", __FUNCTION__); + goto end; + } + b->outbuf->datas[i].flags = SPA_DATA_FLAG_READABLE; + if(port->memtype == SPA_DATA_DmaBuf) { + b->outbuf->datas[i].fd = pData->fd; + } + bytesused = b->outbuf->datas[i].chunk->size = pData->size; + timestamp = pData->timestamp; + sequence = pData->sequence; + + b->outbuf->datas[i].mapoffset = 0; + b->outbuf->datas[i].chunk->offset = 0; + b->outbuf->datas[i].chunk->flags = 0; + //b->outbuf->datas[i].chunk->stride = pData->sstride; /* FIXME:: This needs to be appropriately filled */ + b->outbuf->datas[i].maxsize = pData->maxsize; + + spa_log_trace(this->log,"Spa libcamera Source::%s:: got bufIdx = %d and ndatas = %d\t", + __FUNCTION__, pOut->bufIdx, pOut->n_datas); + spa_log_trace(this->log," data[%d] --> fd = %ld bytesused = %d sequence = %d\n", + i, b->outbuf->datas[i].fd, bytesused, sequence); + } + } + + pts = SPA_TIMEVAL_TO_NSEC(×tamp); + + if (this->clock) { + this->clock->nsec = pts; + this->clock->rate = port->rate; + this->clock->position = sequence; + this->clock->duration = 1; + this->clock->delay = 0; + this->clock->rate_diff = 1.0; + this->clock->next_nsec = pts + 1000000000LL / port->rate.denom; + } + + if (b->h) { + b->h->flags = 0; + b->h->offset = 0; + b->h->seq = sequence; + b->h->pts = pts; + b->h->dts_offset = 0; + } + + d = b->outbuf->datas; + d[0].chunk->offset = 0; + d[0].chunk->size = bytesused; + d[0].chunk->flags = 0; + d[0].data = b->ptr; + spa_log_trace(this->log,"%s:: b->ptr = %p d[0].data = %p\n", + __FUNCTION__, b->ptr, d[0].data); + spa_list_append(&port->queue, &b->link); +end: + libcamera_free_CamData(dev->camera, pDatas); + libcamera_free_OutBuf(dev->camera, pOut); + return 0; +} + +static void libcamera_on_fd_events(struct spa_source *source) +{ + struct impl *this = source->data; + struct spa_io_buffers *io; + struct port *port = &this->out_ports[0]; + struct buffer *b; + + if (source->rmask & SPA_IO_ERR) { + struct port *port = &this->out_ports[0]; + spa_log_error(this->log, "libcamera %p: error %08x", this, source->rmask); + if (port->source.loop) + spa_loop_remove_source(this->data_loop, &port->source); + return; + } + + if (!(source->rmask & SPA_IO_IN)) { + spa_log_warn(this->log, "libcamera %p: spurious wakeup %d", this, source->rmask); + return; + } + + if (mmap_read(this) < 0) { + spa_log_debug(this->log, "%s:: mmap_read failure\n", __FUNCTION__); + return; + } + + if (spa_list_is_empty(&port->queue)) { + spa_log_debug(this->log, "Exiting %s as spa list is empty\n", __FUNCTION__); + return; + } + + io = port->io; + if (io != NULL && io->status != SPA_STATUS_HAVE_DATA) { + if (io->buffer_id < port->n_buffers) + spa_libcamera_buffer_recycle(this, io->buffer_id); + + b = spa_list_first(&port->queue, struct buffer, link); + spa_list_remove(&b->link); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); + + io->buffer_id = b->id; + io->status = SPA_STATUS_HAVE_DATA; + spa_log_trace(this->log, "libcamera %p: now queued %d", this, b->id); + } + spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); +} + +static int spa_libcamera_use_buffers(struct impl *this, struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct port *port = &this->out_ports[0]; + unsigned int i, j; + struct spa_data *d; + + n_buffers = libcamera_get_nbuffers(port->dev.camera); + if (n_buffers > 0) { + d = buffers[0]->datas; + + port->memtype = SPA_DATA_DmaBuf; + } + + for (i = 0; i < n_buffers; i++) { + struct buffer *b; + int64_t fd; + + b = &port->buffers[i]; + b->id = i; + b->outbuf = buffers[i]; + b->flags = BUFFER_FLAG_OUTSTANDING; + b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); + + spa_log_debug(this->log, "libcamera: import buffer %p", buffers[i]); + + if (buffers[i]->n_datas < 1) { + spa_log_error(this->log, "libcamera: invalid memory on buffer %p", buffers[i]); + return -EINVAL; + } + + d = buffers[i]->datas; + for(j = 0; j < buffers[i]->n_datas; ++j) { + d[j].mapoffset = 0; + d[j].maxsize = libcamera_get_max_size(port->dev.camera); + + if (port->memtype == SPA_DATA_MemPtr) { + if (d[j].data == NULL) { + d[j].fd = -1; + d[j].data = mmap(NULL, + d[j].maxsize + d[j].mapoffset, + PROT_READ, MAP_SHARED, + libcamera_get_fd(port->dev.camera, i, j), + 0); + if (d[j].data == MAP_FAILED) { + return -errno; + } + + b->ptr = d[j].data; + spa_log_debug(this->log, "libcamera: In spa_libcamera_use_buffers(). mmap ptr:%p for fd = %ld buffer: #%d", + d[j].data, d[j].fd, i); + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + } else { + b->ptr = d[j].data; + spa_log_debug(this->log, "libcamera: In spa_libcamera_use_buffers(). b->ptr = %p d[j].maxsize = %d for buffer: #%d", + d[j].data, d[j].maxsize, i); + } + spa_log_debug(this->log, "libcamera: In spa_libcamera_use_buffers(). setting b->ptr = %p for buffer: #%d on libcamera", + b->ptr, i); + } + else if (port->memtype == SPA_DATA_DmaBuf) { + d[j].fd = libcamera_get_fd(port->dev.camera, i, j); + spa_log_debug(this->log, "libcamera: Got fd = %ld for buffer: #%d", d[j].fd, i); + } + else { + spa_log_error(this->log, "libcamera: Exiting spa_libcamera_use_buffers() with -EIO"); + return -EIO; + } + } + + spa_libcamera_buffer_recycle(this, i); + } + port->n_buffers = n_buffers; + + return 0; +} + +static int +mmap_init(struct impl *this, + struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct port *port = &this->out_ports[0]; + unsigned int i, j; + + spa_log_info(this->log, "libcamera: In mmap_init()"); + + port->memtype = SPA_DATA_DmaBuf; + + /* get n_buffers from libcamera */ + uint32_t libcamera_nbuffers = libcamera_get_nbuffers(port->dev.camera); + + for (i = 0; i < libcamera_nbuffers; i++) { + struct buffer *b; + struct spa_data *d; + + if (buffers[i]->n_datas < 1) { + spa_log_error(this->log, "libcamera: invalid buffer data"); + return -EINVAL; + } + + b = &port->buffers[i]; + b->id = i; + b->outbuf = buffers[i]; + b->flags = BUFFER_FLAG_OUTSTANDING; + b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); + + d = buffers[i]->datas; + for(j = 0; j < buffers[i]->n_datas; ++j) { + d[j].type = SPA_DATA_DmaBuf; + d[j].flags = SPA_DATA_FLAG_READABLE; + d[j].mapoffset = 0; + d[j].maxsize = libcamera_get_max_size(port->dev.camera); + d[j].chunk->offset = 0; + d[j].chunk->size = 0; + d[j].chunk->stride = port->fmt.bytesperline; /* FIXME:: This needs to be appropriately filled */ + d[j].chunk->flags = 0; + + d[j].flags = SPA_DATA_FLAG_READABLE; + + if(port->memtype == SPA_DATA_DmaBuf) { + d[j].fd = libcamera_get_fd(port->dev.camera, i, j); + spa_log_info(this->log, "libcamera: Got fd = %ld for buffer: #%d\n", d[j].fd, i); + d[j].data = NULL; + SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED); + } + else if(port->memtype == SPA_DATA_MemPtr) { + d[j].fd = -1; + d[j].data = mmap(NULL, + d[j].maxsize + d[j].mapoffset, + PROT_READ, MAP_SHARED, + libcamera_get_fd(port->dev.camera, i, j), + 0); + if (d[j].data == MAP_FAILED) { + spa_log_error(this->log, "mmap: %m"); + continue; + } + b->ptr = d[j].data; + SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); + spa_log_info(this->log, "libcamera: mmap ptr:%p", d[j].data); + } else { + spa_log_error(this->log, "libcamera: invalid buffer type"); + return -EIO; + } + } + + spa_libcamera_buffer_recycle(this, i); + } + port->n_buffers = libcamera_nbuffers; + return 0; +} + +static int userptr_init(struct impl *this) +{ + return -ENOTSUP; +} + +static int read_init(struct impl *this) +{ + return -ENOTSUP; +} + +static int +spa_libcamera_alloc_buffers(struct impl *this, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + int res; + struct port *port = &this->out_ports[0]; + struct spa_libcamera_device *dev = &port->dev; + + if (port->n_buffers > 0) + return -EIO; + + if ((res = mmap_init(this, buffers, n_buffers)) < 0) { + return -EIO; + } + + return 0; +} + +static int spa_libcamera_stream_on(struct impl *this) +{ + struct port *port = &this->out_ports[0]; + struct spa_libcamera_device *dev = &port->dev; + + if (!dev->have_format) { + spa_log_error(this->log, "Exting %s with -EIO\n", __FUNCTION__); + return -EIO; + } + + if (dev->active) { + return 0; + } + + spa_log_info(this->log, "connecting camera"); + + libcamera_connect(dev->camera); + + port->source.func = libcamera_on_fd_events; + port->source.data = this; + port->source.fd = get_dev_fd(dev); + port->source.mask = SPA_IO_IN | SPA_IO_ERR; + port->source.rmask = 0; + spa_loop_add_source(this->data_loop, &port->source); + + dev->active = true; + + return 0; +} + +static int do_remove_source(struct spa_loop *loop, + bool async, + uint32_t seq, + const void *data, + size_t size, + void *user_data) +{ + struct port *port = user_data; + if (port->source.loop) + spa_loop_remove_source(loop, &port->source); + return 0; +} + +static int spa_libcamera_stream_off(struct impl *this) +{ + struct port *port = &this->out_ports[0]; + struct spa_libcamera_device *dev = &port->dev; + + if (!dev->active) + return 0; + + spa_log_info(this->log, "stopping camera"); + + libcamera_stop_capture(dev->camera); + + spa_loop_invoke(this->data_loop, do_remove_source, 0, NULL, 0, true, port); + + spa_list_init(&port->queue); + dev->active = false; + + return 0; +} diff --git a/spa/plugins/libcamera/libcamera.c b/spa/plugins/libcamera/libcamera.c new file mode 100644 index 000000000..3be36fee3 --- /dev/null +++ b/spa/plugins/libcamera/libcamera.c @@ -0,0 +1,55 @@ +/* Spa libcamera support + * + * Copyright © 2020 collabora + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include + +extern const struct spa_handle_factory spa_libcamera_source_factory; +extern const struct spa_handle_factory spa_libcamera_client_factory; +extern const struct spa_handle_factory spa_libcamera_device_factory; + +SPA_EXPORT +int spa_handle_factory_enum(const struct spa_handle_factory **factory, + uint32_t *index) +{ + spa_return_val_if_fail(factory != NULL, -EINVAL); + spa_return_val_if_fail(index != NULL, -EINVAL); + + switch (*index) { + case 0: + *factory = &spa_libcamera_source_factory; + break; + case 1: + *factory = &spa_libcamera_client_factory; + break; + case 2: + *factory = &spa_libcamera_device_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/libcamera/libcamera.h b/spa/plugins/libcamera/libcamera.h new file mode 100644 index 000000000..ac6e3af33 --- /dev/null +++ b/spa/plugins/libcamera/libcamera.h @@ -0,0 +1,43 @@ +/* Spa libcamera support + * + * Copyright © 2020 collabora + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include + +#include "libcamera_wrapper.h" + +struct spa_libcamera_device { + struct spa_log *log; + int fd; + struct media_device_info dev_info; + unsigned int active:1; + unsigned int have_format:1; + LibCamera *camera; +}; + +int spa_libcamera_open(struct spa_libcamera_device *dev); +int spa_libcamera_close(struct spa_libcamera_device *dev); +int spa_libcamera_is_capture(struct spa_libcamera_device *dev); +int get_dev_fd(struct spa_libcamera_device *dev); diff --git a/spa/plugins/libcamera/libcamera_wrapper.cpp b/spa/plugins/libcamera/libcamera_wrapper.cpp new file mode 100644 index 000000000..fdadda2d7 --- /dev/null +++ b/spa/plugins/libcamera/libcamera_wrapper.cpp @@ -0,0 +1,945 @@ +/* Spa libcamera support + * + * Copyright (C) 2020, Collabora Ltd. + * Author: Raghavendra Rao Sidlagatta + * + * libcamera_wrapper.cpp + * + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace libcamera; +using namespace controls; + +#include "libcamera_wrapper.h" + +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_PIXEL_FMT DRM_FORMAT_YUYV + +extern "C" { + + static struct { + spa_video_format video_format; + unsigned int drm_fourcc; + } format_map[] = { + { SPA_VIDEO_FORMAT_ENCODED, DRM_FORMAT_MJPEG }, + { SPA_VIDEO_FORMAT_RGB, DRM_FORMAT_BGR888 }, + { SPA_VIDEO_FORMAT_BGR, DRM_FORMAT_RGB888 }, + { SPA_VIDEO_FORMAT_ARGB, DRM_FORMAT_BGRA8888 }, + { SPA_VIDEO_FORMAT_NV12, DRM_FORMAT_NV12 }, + { SPA_VIDEO_FORMAT_NV21, DRM_FORMAT_NV21 }, + { SPA_VIDEO_FORMAT_NV16, DRM_FORMAT_NV16 }, + { SPA_VIDEO_FORMAT_NV61, DRM_FORMAT_NV61 }, + { SPA_VIDEO_FORMAT_NV24, DRM_FORMAT_NV24 }, + { SPA_VIDEO_FORMAT_UYVY, DRM_FORMAT_UYVY }, + { SPA_VIDEO_FORMAT_VYUY, DRM_FORMAT_VYUY }, + { SPA_VIDEO_FORMAT_YUY2, DRM_FORMAT_YUYV }, + { SPA_VIDEO_FORMAT_YVYU, DRM_FORMAT_YVYU }, + /* \todo NV42 is used in libcamera but is not mapped in here yet. */ + }; + + typedef struct ring_buf { + uint32_t read_index; + uint32_t write_index; + }ring_buf; + + typedef struct LibCamera { + std::unique_ptr cm_; + std::shared_ptr cam_; + std::unique_ptr config_; + FrameBufferAllocator *allocator_; + std::map streamName_; + + uint32_t nbuffers_; + uint32_t nplanes_; + uint32_t bufIdx_; + int64_t **fd_; + uint32_t maxSize_; + bool isAvail_; + uint32_t width_; + uint32_t height_; + uint32_t pixelFormat_; + uint32_t stride_; + + struct ring_buf ringbuf_; + void *ringbuf_data_[MAX_NUM_BUFFERS] = {}; + struct spa_log *log_; + pthread_mutex_t lock; + + /* Methods */ + int32_t listProperties(); + void requestComplete(Request *request); + void item_free_fn(); + void ring_buffer_init(); + void *ring_buffer_read(); + void ring_buffer_write(void *p); + void empty_data(); + void fill_data(); + bool open(); + void close(); + int request_capture(); + int start(); + void stop(); + void connect(); + void disconnect(); + bool set_config(); + + std::shared_ptr get_camera(); + std::string choose_camera(); + + /* Mutators */ + void set_streamcfg_width(uint32_t w); + void set_streamcfg_height(uint32_t h); + void set_streamcfgpixel_format(uint32_t fmt); + void set_max_size(uint32_t s); + void set_nbuffers(uint32_t n); + void set_nplanes(uint32_t n); + void set_stride(uint32_t s); + void set_fd(Stream *stream); + void ring_buffer_set_read_index(uint32_t idx); + void ring_buffer_set_write_index(uint32_t idx); + void ring_buffer_update_read_index(); + void ring_buffer_update_write_index(); + void reset_ring_buffer_data(); + int32_t set_control(ControlList &controls, uint32_t control_id, float value); + + /* Accessors */ + uint32_t get_streamcfg_width(); + uint32_t get_streamcfg_height(); + uint32_t get_streamcfgpixel_format(); + uint32_t get_max_size(); + uint32_t get_nbuffers(); + uint32_t get_nplanes(); + uint32_t get_stride(); + uint32_t ring_buffer_get_read_index(); + uint32_t ring_buffer_get_write_index(); + bool is_data_available(); + }LibCamera; + + bool LibCamera::is_data_available() { + return this->isAvail_; + } + + uint32_t LibCamera::get_max_size() { + return this->maxSize_; + } + + void LibCamera::set_max_size(uint32_t s) { + this->maxSize_ = s; + } + + uint32_t LibCamera::get_nbuffers() { + return this->nbuffers_; + } + + void LibCamera::set_nbuffers(uint32_t n) { + this->nbuffers_ = n; + } + + void LibCamera::set_nplanes(uint32_t n) { + this->nplanes_ = n; + } + + void LibCamera::set_stride(uint32_t s) { + this->stride_ = s; + } + + uint32_t LibCamera::get_stride() { + return this->stride_; + } + + void LibCamera::set_fd(Stream *stream) { + this->fd_ = new int64_t*[this->nbuffers_]; + + uint32_t bufIdx = 0; + for (const std::unique_ptr &buffer : this->allocator_->buffers(stream)) { + uint32_t nplanes = buffer->planes().size(); + this->fd_[bufIdx] = new int64_t[this->nplanes_]; + for(uint32_t planeIdx = 0; planeIdx < nplanes; ++planeIdx) { + const FrameBuffer::Plane &plane = buffer->planes().front(); + this->fd_[bufIdx][planeIdx] = plane.fd.fd(); + } + bufIdx++; + } + } + + uint32_t LibCamera::get_nplanes() { + return this->nplanes_; + } + + void LibCamera::ring_buffer_init() { + this->ringbuf_.read_index = 0; + this->ringbuf_.write_index = 0; + } + + uint32_t LibCamera::ring_buffer_get_read_index() { + uint32_t idx; + idx = __atomic_load_n(&this->ringbuf_.read_index, __ATOMIC_RELAXED); + + return idx; + } + + uint32_t LibCamera::ring_buffer_get_write_index() { + uint32_t idx; + idx = __atomic_load_n(&this->ringbuf_.write_index, __ATOMIC_RELAXED); + + return idx; + } + + void LibCamera::ring_buffer_set_read_index(uint32_t idx) { + __atomic_store_n(&this->ringbuf_.read_index, idx, __ATOMIC_RELEASE); + } + + void LibCamera::ring_buffer_set_write_index(uint32_t idx) { + __atomic_store_n(&this->ringbuf_.write_index, idx, __ATOMIC_RELEASE); + } + + void LibCamera::ring_buffer_update_read_index() { + uint32_t idx; + + idx = this->ring_buffer_get_read_index(); + this->ringbuf_data_[idx] = nullptr; + ++idx; + if(idx == MAX_NUM_BUFFERS) { + idx = 0; + } + this->ring_buffer_set_read_index(idx); + } + + void LibCamera::ring_buffer_update_write_index() { + uint32_t idx; + + idx = this->ring_buffer_get_write_index(); + ++idx; + if(idx == MAX_NUM_BUFFERS) { + idx = 0; + } + this->ring_buffer_set_write_index(idx); + } + + void LibCamera::ring_buffer_write(void *p) + { + uint32_t idx; + + idx = this->ring_buffer_get_write_index(); + pthread_mutex_lock(&this->lock); + ringbuf_data_[idx] = p; + pthread_mutex_unlock(&this->lock); + } + + void *LibCamera::ring_buffer_read() + { + uint32_t idx; + void *p; + + idx = this->ring_buffer_get_read_index(); + pthread_mutex_lock(&this->lock); + p = (void *)this->ringbuf_data_[idx]; + pthread_mutex_unlock(&this->lock); + + return p; + } + + void LibCamera::empty_data() { + pthread_mutex_lock(&this->lock); + this->isAvail_ = true; + pthread_mutex_unlock(&this->lock); + } + + void LibCamera::fill_data() { + pthread_mutex_lock(&this->lock); + this->isAvail_ = false; + pthread_mutex_unlock(&this->lock); + } + + void LibCamera::item_free_fn() { + uint32_t ringbuf_read_index; + struct OutBuf *pOut = NULL; + struct CamData *pDatas = NULL; + + ringbuf_read_index = this->ring_buffer_get_read_index(); + for(int i = 0; i < MAX_NUM_BUFFERS; i++) { + pOut = (struct OutBuf *)ringbuf_data_[ringbuf_read_index]; + if(pOut) { + pDatas = pOut->datas; + if(pDatas) { + libcamera_free_CamData(this, pDatas); + } + libcamera_free_OutBuf(this, pOut); + } + ++ringbuf_read_index; + if(ringbuf_read_index == MAX_NUM_BUFFERS) { + ringbuf_read_index = 0; + } + } + } + + std::string LibCamera::choose_camera() { + if (!this->cm_) { + return std::string(); + } + + if (this->cm_->cameras().empty()) { + return std::string(); + } + /* If only one camera is available, use it automatically. */ + else if (this->cm_->cameras().size() == 1) { + return this->cm_->cameras()[0]->name(); + } + /* TODO:: + * 1. Allow the user to provide a camera name to select. * + * 2. Select the camera based on the camera name provided by User * + */ + /* For time being, return the first camera if more than 1 camera devices are available */ + else { + return this->cm_->cameras()[0]->name(); + } + } + + std::shared_ptr LibCamera::get_camera() { + std::string camName = this->choose_camera(); + std::shared_ptr cam; + + if (camName == "") { + return nullptr; + } + + cam = this->cm_->get(camName); + if (!cam) { + return nullptr; + } + + /* Sanity check that the camera has streams. */ + if (cam->streams().empty()) { + return nullptr; + } + + return cam; + } + + uint32_t LibCamera::get_streamcfg_width() { + return this->width_; + } + + uint32_t LibCamera::get_streamcfg_height() { + return this->height_; + } + + uint32_t LibCamera::get_streamcfgpixel_format() { + return this->pixelFormat_; + } + + void LibCamera::set_streamcfg_width(uint32_t w) { + this->width_ = w; + } + + void LibCamera::set_streamcfg_height(uint32_t h) { + this->height_ = h; + } + + void LibCamera::set_streamcfgpixel_format(uint32_t fmt) { + this->pixelFormat_ = fmt; + } + + bool LibCamera::set_config() { + if(!this->cam_) { + return false; + } + + this->config_ = this->cam_->generateConfiguration({ StreamRole::VideoRecording }); + if (!this->config_ || this->config_->size() != 1) { + return false; + } + + StreamConfiguration &cfg = this->config_->at(0); + + cfg.size.width = this->get_streamcfg_width(); + cfg.size.height = this->get_streamcfg_height(); + cfg.pixelFormat = PixelFormat(this->get_streamcfgpixel_format()); + + /* Validate the configuration. */ + if (this->config_->validate() == CameraConfiguration::Invalid) { + return false; + } + + if (this->cam_->configure(this->config_.get())) { + return false; + } + + this->listProperties(); + + this->allocator_ = new FrameBufferAllocator(this->cam_); + uint32_t nbuffers = UINT_MAX, nplanes = 0; + + Stream *stream = cfg.stream(); + int ret = this->allocator_->allocate(stream); + if (ret < 0) { + return -ENOMEM; + } + + uint32_t allocated = this->allocator_->buffers(cfg.stream()).size(); + nbuffers = std::min(nbuffers, allocated); + + this->set_nbuffers(nbuffers); + + int id = 0; + uint32_t max_size = 0; + for (const std::unique_ptr &buffer : this->allocator_->buffers(stream)) { + nplanes = buffer->planes().size(); + const FrameBuffer::Plane &plane = buffer->planes().front(); + max_size = std::max(max_size, plane.length); + ++id; + } + this->set_max_size(max_size); + this->set_nplanes(nplanes); + this->set_fd(stream); + this->set_stride(cfg.stride); + + return true; + } + + int LibCamera::request_capture() { + int ret = 0; + + StreamConfiguration &cfg = this->config_->at(0); + Stream *stream = cfg.stream(); + + std::vector requests; + + for (const std::unique_ptr &buffer : this->allocator_->buffers(stream)) { + Request *request = this->cam_->createRequest(); + if (!request) { + spa_log_error(this->log_, "Cannot create request"); + return -ENOMEM; + } + + if (request->addBuffer(stream, buffer.get())) { + spa_log_error(this->log_, "Failed to associating buffer with request"); + return -ENOMEM; + } + + requests.push_back(request); + } + + for (Request *request : requests) { + ret = this->cam_->queueRequest(request); + if (ret < 0) { + spa_log_error(this->log_, "Cannot create request"); + return ret; + } + } + + return ret; + } + + bool LibCamera::open() { + std::shared_ptr cam; + int err; + int ret = 0; + + cam = this->get_camera(); + if(!cam) { + return false; + } + + ret = cam->acquire(); + if (ret) { + err = errno; + return false; + } + + this->cam_ = cam; + + if(!this->set_config()) { + return false; + } + + return true; + } + + int LibCamera::start() { + if(!this->set_config()) { + return -1; + } + + this->streamName_.clear(); + for (unsigned int index = 0; index < this->config_->size(); ++index) { + StreamConfiguration &cfg = this->config_->at(index); + this->streamName_[cfg.stream()] = "stream" + std::to_string(index); + } + + spa_log_info(this->log_, "Starting camera ..."); + + /* start the camera now */ + if (this->cam_->start()) { + spa_log_error(this->log_, "failed to start camera"); + return -1; + } + + this->ring_buffer_init(); + + if(this->request_capture()) { + spa_log_error(this->log_, "failed to create request"); + return -1; + } + return 0; + } + + void LibCamera::stop() { + this->disconnect(); + + spa_log_info(this->log_, "Stopping camera ..."); + this->cam_->stop(); + if(this->allocator_) { + delete this->allocator_; + this->allocator_ = nullptr; + } + + if(this->fd_) { + for(uint32_t i = 0; i < this->nplanes_; i++) { + delete this->fd_[i]; + this->fd_[i] = nullptr; + } + delete this->fd_; + this->fd_ = nullptr; + } + + this->item_free_fn(); + } + + void LibCamera::close() { + this->stop(); + this->cam_->release(); + } + + void LibCamera::connect() + { + this->cam_->requestCompleted.connect(this, &LibCamera::requestComplete); + } + + void LibCamera::disconnect() + { + this->cam_->requestCompleted.disconnect(this, &LibCamera::requestComplete); + } + + uint32_t libcamera_get_streamcfg_width(LibCamera *camera) { + return camera->get_streamcfg_width(); + } + + uint32_t libcamera_get_streamcfg_height(LibCamera *camera) { + return camera->get_streamcfg_height(); + } + + uint32_t libcamera_get_streamcfgpixel_format(LibCamera *camera) { + return camera->get_streamcfgpixel_format(); + } + + void libcamera_set_streamcfg_width(LibCamera *camera, uint32_t w) { + camera->set_streamcfg_width(w); + } + + void libcamera_set_streamcfg_height(LibCamera *camera, uint32_t h) { + camera->set_streamcfg_height(h); + } + + void libcamera_set_streamcfgpixel_format(LibCamera *camera, uint32_t fmt) { + camera->set_streamcfgpixel_format(fmt); + } + + void libcamera_ringbuffer_read_update(LibCamera *camera) { + camera->fill_data(); + camera->ring_buffer_update_read_index(); + } + + void *libcamera_get_ring_buffer_data(LibCamera *camera) { + return camera->ring_buffer_read(); + } + + void libcamera_free_OutBuf(LibCamera *camera, OutBuf *p) { + pthread_mutex_lock(&camera->lock); + if(p != nullptr) { + delete p; + p = nullptr; + } + pthread_mutex_unlock(&camera->lock); + } + + void libcamera_free_CamData(LibCamera *camera, CamData *p) { + pthread_mutex_lock(&camera->lock); + if(p != nullptr) { + delete p; + p = nullptr; + } + pthread_mutex_unlock(&camera->lock); + } + + void libcamera_set_log(LibCamera *camera, struct spa_log *log) { + camera->log_ = log; + } + + bool libcamera_is_data_available(LibCamera *camera) { + return camera->is_data_available(); + } + + spa_video_format libcamera_map_drm_fourcc_format(unsigned int fourcc) { + for (const auto &item : format_map) { + if (item.drm_fourcc == fourcc) { + return item.video_format; + } + } + return (spa_video_format)UINT32_MAX; + } + + uint32_t libcamera_drm_to_video_format(unsigned int drm) { + return libcamera_map_drm_fourcc_format(drm); + } + + uint32_t libcamera_video_format_to_drm(uint32_t format) + { + if (format == SPA_VIDEO_FORMAT_ENCODED) { + return DRM_FORMAT_INVALID; + } + + for (const auto &item : format_map) { + if (item.video_format == format) { + return item.drm_fourcc; + } + } + + return DRM_FORMAT_INVALID; + } + + uint32_t libcamera_enum_streamcfgpixel_format(LibCamera *camera, uint32_t idx) { + if(!camera) { + return -1; + } + if (!camera->config_) { + spa_log_error(camera->log_, "Cannot get stream information without a camera"); + return -EINVAL; + } + + uint32_t index = 0; + for (const StreamConfiguration &cfg : *camera->config_) { + uint32_t index = 0; + const StreamFormats &formats = cfg.formats(); + for (PixelFormat pixelformat : formats.pixelformats()) { + if(index == idx) { + return pixelformat.fourcc(); + } + ++index; + } + } + /* We shouldn't be here */ + return UINT32_MAX; + } + + void libcamera_get_streamcfg_size(LibCamera *camera, uint32_t idx, uint32_t *width, uint32_t *height) { + if(!camera) { + return; + } + if (!camera->config_) { + spa_log_error(camera->log_, "Cannot get stream information without a camera");; + return; + } + + for (const StreamConfiguration &cfg : *camera->config_) { + uint32_t index = 0; + const StreamFormats &formats = cfg.formats(); + for (PixelFormat pixelformat : formats.pixelformats()) { + uint32_t index = 0; + for (const Size &size : formats.sizes(pixelformat)) { + if(index == idx) { + *width = size.width; + *height = size.height; + return; + } + ++index; + } + } + } + /* We shouldn't be here */ + *width = *height = UINT32_MAX; + } + + int LibCamera::listProperties() + { + if (!cam_) { + spa_log_error(log_, "Cannot list properties without a camera");; + return -EINVAL; + } + + spa_log_info(log_, "listing properties"); + for (const auto &prop : cam_->properties()) { + const ControlId *id = properties::properties.at(prop.first); + const ControlValue &value = prop.second; + + spa_log_info(log_, "Property: %s = %s",id->name().c_str(), value.toString().c_str()); + } + + return 0; + } + + int64_t libcamera_get_fd(LibCamera *camera, int bufIdx, int planeIdx) { + if((bufIdx >= (int)camera->nbuffers_) || (planeIdx >= (int)camera->nplanes_)){ + return -1; + } else { + return camera->fd_[bufIdx][planeIdx]; + } + } + + int libcamera_get_max_size(LibCamera *camera) { + return camera->get_max_size(); + } + + void libcamera_connect(LibCamera *camera) { + if(!camera || !camera->cam_) { + return; + } + camera->connect(); + } + + uint32_t libcamera_get_nbuffers(LibCamera *camera) { + return camera->get_nbuffers(); + } + + uint32_t libcamera_get_nplanes(LibCamera *camera) { + return camera->get_nplanes(); + } + + uint32_t libcamera_get_stride(LibCamera *camera) { + return camera->get_stride(); + } + + int libcamera_start_capture(LibCamera *camera) { + if (!camera || !camera->cm_ || !camera->cam_) { + return -1; + } + + return camera->start(); + } + + void libcamera_disconnect(LibCamera *camera) { + if(!camera || !camera->cam_) { + return; + } + camera->disconnect(); + } + + void libcamera_stop_capture(LibCamera *camera) { + if(!camera || !camera->cm_ || !camera->cam_) { + return; + } + + camera->stop(); + } + + LibCamera* newLibCamera() { + int err; + int ret = 0; + pthread_mutexattr_t attr; + std::unique_ptr cm = std::make_unique(); + LibCamera* camera = new LibCamera(); + + ret = cm->start(); + if (ret) { + err = errno; + return nullptr; + } + + camera->cm_ = std::move(cm); + + camera->bufIdx_ = 0; + + camera->set_streamcfg_width(DEFAULT_WIDTH); + camera->set_streamcfg_height(DEFAULT_HEIGHT); + camera->set_streamcfgpixel_format(DEFAULT_PIXEL_FMT); + + if(!camera->open()) { + deleteLibCamera(camera); + return nullptr; + } + + pthread_mutexattr_init(&attr); + pthread_mutex_init(&camera->lock, &attr); + + camera->ring_buffer_init(); + + return camera; + } + + void deleteLibCamera(LibCamera *camera) { + if(camera == nullptr) { + return; + } + + pthread_mutex_destroy(&camera->lock); + + camera->close(); + + if(camera->cm_) + camera->cm_->stop(); + + delete camera; + camera = nullptr; + } + + void LibCamera::requestComplete(Request *request) { + if (request->status() == Request::RequestCancelled) { + return; + } + + ++bufIdx_; + if(bufIdx_ >= nbuffers_) { + bufIdx_ = 0; + } + + StreamConfiguration &cfg = config_->at(0); + const std::map &buffers = request->buffers(); + + unsigned int idx = 0; + for (auto it = buffers.begin(); it != buffers.end(); ++it) { + Stream *stream = it->first; + FrameBuffer *buffer = it->second; + const std::string &name = streamName_[stream]; + unsigned int nplanes = buffer->planes().size(); + OutBuf *pBuf = new OutBuf(); + uint32_t ringbuf_write_index; + + pBuf->bufIdx = bufIdx_; + pBuf->n_datas = nplanes; + pBuf->datas = new CamData[pBuf->n_datas]; + + unsigned int planeIdx = 0; + const std::vector &planes = buffer->planes(); + const FrameMetadata &metadata = buffer->metadata(); + for (const FrameMetadata::Plane &plane : metadata.planes) { + pBuf->datas[planeIdx].idx = planeIdx; + pBuf->datas[planeIdx].type = 3; /*SPA_DATA_DmaBuf;*/ + pBuf->datas[planeIdx].fd = planes[planeIdx].fd.fd(); + pBuf->datas[planeIdx].size = plane.bytesused; + pBuf->datas[planeIdx].maxsize = buffer->planes()[planeIdx].length; + pBuf->datas[planeIdx].sequence = metadata.sequence; + pBuf->datas[planeIdx].timestamp.tv_sec = metadata.timestamp / 1000000000; + pBuf->datas[planeIdx].timestamp.tv_usec = (metadata.timestamp / 1000) % 1000000; + ++planeIdx; + } + + /* Push the buffer to ring buffer */ + if(pBuf && pBuf->datas) { + this->ring_buffer_write(pBuf); + spa_log_trace(log_, "%s::Pushing buffer %p at index: %d\n", __FUNCTION__, pBuf, ringbuf_write_index); + /* Now update the write index of the ring buffer */ + this->ring_buffer_update_write_index(); + this->empty_data(); + } + } + + /* + * Create a new request and populate it with one buffer for each + * stream. + */ + request = cam_->createRequest(); + if (!request) { + spa_log_error(log_, "Cannot create request"); + return; + } + + for (auto it = buffers.begin(); it != buffers.end(); ++it) { + Stream *stream = it->first; + FrameBuffer *buffer = it->second; + + request->addBuffer(stream, buffer); + } + + cam_->queueRequest(request); + } + + int32_t LibCamera::set_control(ControlList &controls, uint32_t control_id, float value) { + switch(control_id) { + case SPA_PROP_brightness: + controls.set(controls::Brightness, value); + break; + + case SPA_PROP_contrast: + controls.set(controls::Contrast, value); + break; + + case SPA_PROP_saturation: + controls.set(controls::Saturation, value); + break; + + case SPA_PROP_exposure: + controls.set(controls::ExposureValue, value); + break; + + case SPA_PROP_gain: + controls.set(controls::AnalogueGain, value); + break; + + default: + return -1; + } + return 0; + } + + int32_t libcamera_set_control(LibCamera *camera, uint32_t control_id, float value) { + int32_t res; + + if(!camera || !camera->cm_ || !camera->cam_) + return -1; + + Request *request = camera->cam_->createRequest(); + ControlList &controls = request->controls(); + res = camera->set_control(controls, control_id, value); + camera->cam_->queueRequest(request); + + return res; + } +} diff --git a/spa/plugins/libcamera/libcamera_wrapper.h b/spa/plugins/libcamera/libcamera_wrapper.h new file mode 100644 index 000000000..ab78c6ce9 --- /dev/null +++ b/spa/plugins/libcamera/libcamera_wrapper.h @@ -0,0 +1,128 @@ +/* Spa libcamera support + * + * Copyright (C) 2020, Collabora Ltd. + * Author: Raghavendra Rao Sidlagatta + * + * libcamera_wrapper.h + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef __LIBCAMERA_WRAPPER_H +#define __LIBCAMERA_WRAPPER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_NUM_BUFFERS 16 + +typedef struct CamData { + uint32_t idx; + uint32_t type; + int64_t fd; + uint32_t maxsize; /**< max size of data */ + uint32_t size; /**< size of valid data. Should be clamped to + * maxsize. */ + struct timeval timestamp; + uint32_t sequence; + void *data; +}CamData; + +typedef struct OutBuf { + uint32_t bufIdx; + uint32_t n_datas; /**< number of data members */ + struct CamData *datas; /**< array of data members */ +}OutBuf; + +typedef struct LibCamera LibCamera; + +LibCamera *newLibCamera(); + +void deleteLibCamera(LibCamera *camera); + +void libcamera_set_log(LibCamera *camera, struct spa_log *log); + +bool libcamera_open(LibCamera *camera); + +void libcamera_close(LibCamera *camera); + +void libcamera_connect(LibCamera *camera); + +void libcamera_disconnect(LibCamera *camera); + +int libcamera_isCapturing(LibCamera *camera); + +int libcamera_start_capture(LibCamera *camera); + +void libcamera_stop_capture(LibCamera *camera); + +int libcamera_get_refcnt(LibCamera *camera); + +uint32_t libcamera_get_streamcfg_width(LibCamera *camera); + +uint32_t libcamera_get_streamcfg_height(LibCamera *camera); + +uint32_t libcamera_get_streamcfgpixel_format(LibCamera *camera); + +uint32_t libcamera_enum_streamcfgpixel_format(LibCamera *camera, uint32_t idx); + +uint32_t libcamera_video_format_to_drm(uint32_t fmt); + +uint32_t libcamera_drm_to_video_format(unsigned int drm); + +uint32_t libcamera_get_nbuffers(LibCamera *camera); + +uint32_t libcamera_get_nplanes(LibCamera *camera); + +int64_t libcamera_get_fd(LibCamera *camera, int bufIdx, int planeIdx); + +int32_t libcamera_get_max_size(LibCamera *camera); + +int32_t libcamera_set_control(LibCamera *camera, uint32_t control_id, float value); + +void libcamera_set_streamcfg_width(LibCamera *camera, uint32_t w); + +void libcamera_set_streamcfg_height(LibCamera *camera, uint32_t w); + +void libcamera_set_streamcfgpixel_format(LibCamera *camera, uint32_t fmt); + +void libcamera_get_streamcfg_size(LibCamera *camera, uint32_t idx, uint32_t *width, uint32_t *height); + +uint32_t libcamera_get_stride(LibCamera *camera); + +void *libcamera_get_ring_buffer_data(LibCamera *camera); + +void libcamera_reset_ring_buffer_data(LibCamera *camera); + +void libcamera_ringbuffer_read_update(LibCamera *camera); + +bool libcamera_is_data_available(LibCamera *camera); + +void libcamera_consume_data(LibCamera *camera); + +void libcamera_free_CamData(LibCamera *camera, CamData *p); + +void libcamera_free_OutBuf(LibCamera *camera, OutBuf *p); + +#ifdef __cplusplus +} +#endif /* extern "C" */ +#endif /* __LIBCAMERA_WRAPPER_H */ \ No newline at end of file diff --git a/spa/plugins/libcamera/meson.build b/spa/plugins/libcamera/meson.build new file mode 100644 index 000000000..75d378cec --- /dev/null +++ b/spa/plugins/libcamera/meson.build @@ -0,0 +1,12 @@ +libcamera_sources = ['libcamera.c', + 'libcamera-device.c', + 'libcamera-client.c', + 'libcamera-source.c', + 'libcamera_wrapper.cpp'] + +libcameralib = shared_library('spa-libcamera', + libcamera_sources, + include_directories : [ spa_inc ], + dependencies : [ libudev_dep, libcamera_dep, pthread_lib ], + install : true, + install_dir : join_paths(spa_plugindir, 'libcamera')) diff --git a/spa/plugins/meson.build b/spa/plugins/meson.build index 5be1d2ac4..bcff8e937 100644 --- a/spa/plugins/meson.build +++ b/spa/plugins/meson.build @@ -43,3 +43,6 @@ endif if get_option('v4l2') subdir('v4l2') endif +if get_option('libcamera') + subdir('libcamera') +endif diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index b659d4601..36e3e8164 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -29,6 +29,7 @@ set-prop link.max-buffers 16 # version < 3 clients can't handle more add-spa-lib audio.convert* audioconvert/libspa-audioconvert add-spa-lib api.alsa.* alsa/libspa-alsa add-spa-lib api.v4l2.* v4l2/libspa-v4l2 +add-spa-lib api.libcamera.* libcamera/libspa-libcamera add-spa-lib api.bluez5.* bluez5/libspa-bluez5 add-spa-lib api.vulkan.* vulkan/libspa-vulkan add-spa-lib api.jack.* jack/libspa-jack @@ -41,7 +42,7 @@ add-spa-lib support.* support/libspa-support # Loads a module with the given parameters. Normally failure is # fatal if the module is not found, unless -ifexists is given. # -load-module libpipewire-module-rtkit # rt.prio=20 rt.time.soft=200000 rt.time.hard=200000 +#load-module libpipewire-module-rtkit # rt.prio=20 rt.time.soft=200000 rt.time.hard=200000 load-module libpipewire-module-protocol-native load-module libpipewire-module-profiler load-module libpipewire-module-metadata diff --git a/src/examples/media-session/libcamera-monitor.c b/src/examples/media-session/libcamera-monitor.c new file mode 100644 index 000000000..1a7962089 --- /dev/null +++ b/src/examples/media-session/libcamera-monitor.c @@ -0,0 +1,498 @@ +/* PipeWire + * + * Copyright © 2019 Wim Taymans + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pipewire/pipewire.h" + +#include "media-session.h" + +struct device; + +struct node { + struct impl *impl; + struct device *device; + struct spa_list link; + uint32_t id; + + struct pw_properties *props; + + struct pw_proxy *proxy; + struct spa_node *node; +}; + +struct device { + struct impl *impl; + struct spa_list link; + uint32_t id; + uint32_t device_id; + + int priority; + int profile; + + struct pw_properties *props; + + struct spa_handle *handle; + struct spa_device *device; + struct spa_hook device_listener; + + struct sm_device *sdevice; + struct spa_hook listener; + + unsigned int appeared:1; + struct spa_list node_list; +}; + +struct impl { + struct sm_media_session *session; + struct spa_hook session_listener; + + struct spa_handle *handle; + struct spa_device *monitor; + struct spa_hook listener; + + struct spa_list device_list; +}; + +static struct node *libcamera_find_node(struct device *dev, uint32_t id) +{ + struct node *node; + + spa_list_for_each(node, &dev->node_list, link) { + if (node->id == id) + return node; + } + return NULL; +} + +static void libcamera_update_node(struct device *dev, struct node *node, + const struct spa_device_object_info *info) +{ + pw_log_debug("update node %u", node->id); + + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_dict(0, info->props); + + pw_properties_update(node->props, info->props); +} + +static struct node *libcamera_create_node(struct device *dev, uint32_t id, + const struct spa_device_object_info *info) +{ + struct node *node; + struct impl *impl = dev->impl; + int res; + const char *str; + + pw_log_debug("new node %u", id); + + if (strcmp(info->type, SPA_TYPE_INTERFACE_Node) != 0) { + errno = EINVAL; + return NULL; + } + node = calloc(1, sizeof(*node)); + if (node == NULL) { + res = -errno; + goto exit; + } + + node->props = pw_properties_new_dict(info->props); + + pw_properties_setf(node->props, PW_KEY_DEVICE_ID, "%d", dev->device_id); + + str = pw_properties_get(dev->props, SPA_KEY_DEVICE_NAME); + if (str == NULL) + str = pw_properties_get(dev->props, SPA_KEY_DEVICE_NICK); + if (str == NULL) + str = pw_properties_get(dev->props, SPA_KEY_DEVICE_ALIAS); + if (str == NULL) + str = "libcamera-device"; + pw_properties_setf(node->props, PW_KEY_NODE_NAME, "%s.%s", info->factory_name, str); + + str = pw_properties_get(dev->props, SPA_KEY_DEVICE_DESCRIPTION); + if (str == NULL) + str = "libcamera-device"; + pw_properties_set(node->props, PW_KEY_NODE_DESCRIPTION, str); + + pw_properties_set(node->props, PW_KEY_FACTORY_NAME, info->factory_name); + + node->impl = impl; + node->device = dev; + node->id = id; + node->proxy = sm_media_session_create_object(impl->session, + "spa-node-factory", + PW_TYPE_INTERFACE_Node, + PW_VERSION_NODE, + &node->props->dict, + 0); + if (node->proxy == NULL) { + res = -errno; + goto clean_node; + } + + spa_list_append(&dev->node_list, &node->link); + + return node; + +clean_node: + pw_properties_free(node->props); + free(node); +exit: + errno = -res; + return NULL; +} + +static void libcamera_remove_node(struct device *dev, struct node *node) +{ + pw_log_debug("remove node %u", node->id); + spa_list_remove(&node->link); + pw_proxy_destroy(node->proxy); + pw_properties_free(node->props); + free(node); +} + +static void libcamera_device_info(void *data, const struct spa_device_info *info) +{ + struct device *dev = data; + + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_dict(0, info->props); + + pw_properties_update(dev->props, info->props); +} + +static void libcamera_device_object_info(void *data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct device *dev = data; + struct node *node; + + node = libcamera_find_node(dev, id); + + if (info == NULL) { + if (node == NULL) { + pw_log_warn("device %p: unknown node %u", dev, id); + return; + } + libcamera_remove_node(dev, node); + } else if (node == NULL) { + libcamera_create_node(dev, id, info); + } else { + libcamera_update_node(dev, node, info); + } +} + +static const struct spa_device_events libcamera_device_events = { + SPA_VERSION_DEVICE_EVENTS, + .info = libcamera_device_info, + .object_info = libcamera_device_object_info +}; + +static struct device *libcamera_find_device(struct impl *impl, uint32_t id) +{ + struct device *dev; + + pw_log_info("In %s:: Invoking spa_list_for_each for id = %d", __FUNCTION__, id); + spa_list_for_each(dev, &impl->device_list, link) { + if (dev->id == id) + return dev; + } + pw_log_info("In %s:: spa_list_for_each returns NULL for id = %d", __FUNCTION__, id); + return NULL; +} + +static void libcamera_update_device(struct impl *impl, struct device *dev, + const struct spa_device_object_info *info) +{ + pw_log_debug("update device %u", dev->id); + + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_dict(0, info->props); + + pw_properties_update(dev->props, info->props); +} + +static int libcamera_update_device_props(struct device *dev) +{ + struct pw_properties *p = dev->props; + const char *s, *d; + char temp[32]; + + if ((s = pw_properties_get(p, SPA_KEY_DEVICE_NAME)) == NULL) { + if ((s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_ID)) == NULL) { + if ((s = pw_properties_get(p, SPA_KEY_DEVICE_BUS_PATH)) == NULL) { + snprintf(temp, sizeof(temp), "%d", dev->id); + s = temp; + } + } + } + pw_properties_setf(p, PW_KEY_DEVICE_NAME, "libcamera_device.%s", s); + + if (pw_properties_get(p, PW_KEY_DEVICE_DESCRIPTION) == NULL) { + d = pw_properties_get(p, PW_KEY_DEVICE_PRODUCT_NAME); + if (!d) + d = "Unknown device"; + + pw_properties_set(p, PW_KEY_DEVICE_DESCRIPTION, d); + } + return 0; +} + +static void set_profile(struct device *device, int index) +{ + char buf[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); + + pw_log_debug("%p: set profile %d id:%d", device, index, device->device_id); + + device->profile = index; + if (device->device_id != 0) { + spa_device_set_param(device->device, + SPA_PARAM_Profile, 0, + spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile, + SPA_PARAM_PROFILE_index, SPA_POD_Int(index))); + } +} + +static void device_destroy(void *data) +{ + struct device *device = data; + struct node *node; + + pw_log_debug("device %p destroy", device); + + spa_list_consume(node, &device->node_list, link) + libcamera_remove_node(device, node); +} + +static void device_update(void *data) +{ + struct device *device = data; + + pw_log_debug("device %p appeared %d %d", device, device->appeared, device->profile); + + if (device->appeared) + return; + + device->device_id = device->sdevice->obj.id; + device->appeared = true; + + spa_device_add_listener(device->device, + &device->device_listener, + &libcamera_device_events, device); + + set_profile(device, 1); + sm_object_sync_update(&device->sdevice->obj); +} + +static const struct sm_object_events device_events = { + SM_VERSION_OBJECT_EVENTS, + .destroy = device_destroy, + .update = device_update, +}; + + +static struct device *libcamera_create_device(struct impl *impl, uint32_t id, + const struct spa_device_object_info *info) +{ + struct pw_context *context = impl->session->context; + struct device *dev; + struct spa_handle *handle; + int res; + void *iface; + + pw_log_debug("new device %u", id); + + if (strcmp(info->type, SPA_TYPE_INTERFACE_Device) != 0) { + errno = EINVAL; + return NULL; + } + + handle = pw_context_load_spa_handle(context, + info->factory_name, + info->props); + if (handle == NULL) { + res = -errno; + pw_log_error("can't make factory instance: %m"); + goto exit; + } + + if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { + pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res)); + goto unload_handle; + } + + dev = calloc(1, sizeof(*dev)); + if (dev == NULL) { + res = -errno; + goto unload_handle; + } + + dev->impl = impl; + dev->id = id; + dev->handle = handle; + dev->device = iface; + dev->props = pw_properties_new_dict(info->props); + libcamera_update_device_props(dev); + + dev->sdevice = sm_media_session_export_device(impl->session, + &dev->props->dict, dev->device); + + if (dev->sdevice == NULL) { + res = -errno; + goto clean_device; + } + + pw_log_debug("got object %p", &dev->sdevice->obj); + + sm_object_add_listener(&dev->sdevice->obj, + &dev->listener, + &device_events, dev); + + spa_list_init(&dev->node_list); + spa_list_append(&impl->device_list, &dev->link); + + return dev; + +clean_device: + free(dev); +unload_handle: + pw_unload_spa_handle(handle); +exit: + errno = -res; + return NULL; +} + +static void libcamera_remove_device(struct impl *impl, struct device *dev) +{ + pw_log_debug("remove device %u", dev->id); + spa_list_remove(&dev->link); + if (dev->appeared) + spa_hook_remove(&dev->device_listener); + if (dev->sdevice) + sm_object_destroy(&dev->sdevice->obj); + spa_hook_remove(&dev->listener); + pw_unload_spa_handle(dev->handle); + pw_properties_free(dev->props); + free(dev); +} + +static void libcamera_udev_object_info(void *data, uint32_t id, + const struct spa_device_object_info *info) +{ + struct impl *impl = data; + struct device *dev = NULL; + + pw_log_info("In %s:: Invoking libcamera_find_device for id = %d", __FUNCTION__, id); + dev = libcamera_find_device(impl, id); + + if (info == NULL) { + if (dev == NULL) + return; + libcamera_remove_device(impl, dev); + } else if (dev == NULL) { + if (libcamera_create_device(impl, id, info) == NULL) + return; + } else { + libcamera_update_device(impl, dev, info); + } +} + +static const struct spa_device_events libcamera_udev_callbacks = +{ + SPA_VERSION_DEVICE_EVENTS, + .object_info = libcamera_udev_object_info, +}; + +static void session_destroy(void *data) +{ + struct impl *impl = data; + spa_hook_remove(&impl->session_listener); + spa_hook_remove(&impl->listener); + pw_unload_spa_handle(impl->handle); + free(impl); +} + +static const struct sm_media_session_events session_events = { + SM_VERSION_MEDIA_SESSION_EVENTS, + .destroy = session_destroy, +}; + +int sm_libcamera_monitor_start(struct sm_media_session *sess) +{ + struct pw_context *context = sess->context; + struct impl *impl; + int res; + void *iface; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return -errno; + + impl->session = sess; + pw_log_info("%s:: Loading spa handle: %s", __FUNCTION__, SPA_NAME_API_LIBCAMERA_DEVICE); + + impl->handle = pw_context_load_spa_handle(context, SPA_NAME_API_LIBCAMERA_ENUM_CLIENT, NULL); + if (impl->handle == NULL) { + res = -errno; + goto out_free; + } + + if ((res = spa_handle_get_interface(impl->handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) { + pw_log_error("can't get MONITOR interface: %d", res); + goto out_unload; + } + + impl->monitor = iface; + spa_list_init(&impl->device_list); + + spa_device_add_listener(impl->monitor, &impl->listener, + &libcamera_udev_callbacks, impl); + + sm_media_session_add_listener(sess, &impl->session_listener, &session_events, impl); + + return 0; + +out_unload: + pw_unload_spa_handle(impl->handle); +out_free: + free(impl); + return res; +} diff --git a/src/examples/media-session/media-session.c b/src/examples/media-session/media-session.c index bc08a6c4b..73313ef89 100644 --- a/src/examples/media-session/media-session.c +++ b/src/examples/media-session/media-session.c @@ -66,6 +66,7 @@ int sm_metadata_start(struct sm_media_session *sess); int sm_alsa_midi_start(struct sm_media_session *sess); int sm_v4l2_monitor_start(struct sm_media_session *sess); +int sm_libcamera_monitor_start(struct sm_media_session *sess); int sm_bluez5_monitor_start(struct sm_media_session *sess); int sm_alsa_monitor_start(struct sm_media_session *sess); int sm_suspend_node_start(struct sm_media_session *sess); @@ -1715,8 +1716,7 @@ static void do_quit(void *data, int signal_number) pw_main_loop_quit(impl->loop); } - -#define DEFAULT_ENABLED "alsa-pcm,alsa-seq,v4l2,bluez5,metadata,suspend-node,policy-node" +#define DEFAULT_ENABLED "alsa-pcm,alsa-seq,v4l2,libcamera,bluez5,metadata,suspend-node,policy-node" #define DEFAULT_DISABLED "" static const struct { @@ -1728,6 +1728,7 @@ static const struct { { "alsa-seq", "alsa seq midi support", sm_alsa_midi_start }, { "alsa-pcm", "alsa pcm udev detection", sm_alsa_monitor_start }, { "v4l2", "video for linux udev detection", sm_v4l2_monitor_start }, + { "libcamera", "libcamera udev detection", sm_libcamera_monitor_start }, { "bluez5", "bluetooth support", sm_bluez5_monitor_start }, { "metadata", "export metadata API", sm_metadata_start }, { "suspend-node", "suspend inactive nodes", sm_suspend_node_start }, @@ -1836,6 +1837,7 @@ int main(int argc, char *argv[]) pw_context_add_spa_lib(impl.this.context, "api.bluez5.*", "bluez5/libspa-bluez5"); pw_context_add_spa_lib(impl.this.context, "api.alsa.*", "alsa/libspa-alsa"); pw_context_add_spa_lib(impl.this.context, "api.v4l2.*", "v4l2/libspa-v4l2"); + pw_context_add_spa_lib(impl.this.context, "api.libcamera.*", "libcamera/libspa-libcamera"); pw_context_set_object(impl.this.context, SM_TYPE_MEDIA_SESSION, &impl); @@ -1865,7 +1867,7 @@ int main(int argc, char *argv[]) const char *name = modules[i].name; if (opt_contains(opt_enabled, name) && !opt_contains(opt_disabled, name)) { - pw_log_info("enable: %s", name); + pw_log_info("enable: %s. Starting module.", name); modules[i].start(&impl.this); } } diff --git a/src/examples/media-session/v4l2-endpoint.c b/src/examples/media-session/v4l2-endpoint.c index b7447ef35..0e70177dd 100644 --- a/src/examples/media-session/v4l2-endpoint.c +++ b/src/examples/media-session/v4l2-endpoint.c @@ -141,6 +141,7 @@ static int client_endpoint_create_link(void *object, const struct spa_dict *prop struct pw_properties *p; int res; + pw_log_info(NAME " %s. ", __FUNCTION__); pw_log_debug(NAME" %p: endpoint %p", impl, endpoint); if (props == NULL) @@ -160,11 +161,15 @@ static int client_endpoint_create_link(void *object, const struct spa_dict *prop res = -EINVAL; goto exit; } + pw_log_info(NAME " %s. Got %s from PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT", __FUNCTION__, str); + obj = sm_media_session_find_object(impl->session, atoi(str)); if (obj == NULL || strcmp(obj->type, PW_TYPE_INTERFACE_Endpoint) != 0) { pw_log_warn(NAME" %p: could not find endpoint %s (%p)", impl, str, obj); res = -EINVAL; goto exit; + } else { + pw_log_info(NAME " %s: comparing obj->type[%s] with %s.", __FUNCTION__, obj->type, PW_TYPE_INTERFACE_Endpoint); } pw_properties_setf(p, PW_KEY_LINK_OUTPUT_NODE, "%d", endpoint->node->node->info->id); @@ -198,6 +203,7 @@ static struct stream *endpoint_add_stream(struct endpoint *endpoint) struct stream *s; const char *str; + pw_log_info(NAME " %s. ", __FUNCTION__); s = calloc(1, sizeof(*s)); if (s == NULL) return NULL; @@ -222,7 +228,7 @@ static struct stream *endpoint_add_stream(struct endpoint *endpoint) s->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS; s->info.props = &s->props->dict; - pw_log_debug("stream %d", s->info.id); + pw_log_info("%s:: stream %s %d", __FUNCTION__, s->info.name, s->info.id); pw_client_endpoint_stream_update(endpoint->client_endpoint, s->info.id, PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO, @@ -239,6 +245,7 @@ static void destroy_stream(struct stream *stream) { struct endpoint *endpoint = stream->endpoint; + pw_log_info(NAME " %s. ", __FUNCTION__); pw_client_endpoint_stream_update(endpoint->client_endpoint, stream->info.id, PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED, @@ -260,6 +267,7 @@ static void update_params(void *data) struct sm_node *node = endpoint->node->node; struct sm_param *p; + pw_log_info(NAME " %s. ", __FUNCTION__); pw_log_debug(NAME" %p: endpoint", endpoint); params = alloca(sizeof(struct spa_pod *) * node->n_params); @@ -307,6 +315,7 @@ static void complete_endpoint(void *data) struct stream *stream; struct sm_param *p; + pw_log_info(NAME " %s. ", __FUNCTION__); pw_log_debug("endpoint %p: complete", endpoint); spa_list_for_each(p, &endpoint->node->node->param_list, link) { @@ -346,6 +355,7 @@ static void proxy_destroy(void *data) struct endpoint *endpoint = data; struct stream *s; + pw_log_info(NAME " %s. ", __FUNCTION__); pw_log_debug("endpoint %p: destroy", endpoint); spa_list_consume(s, &endpoint->stream_list, link) @@ -361,6 +371,7 @@ static void proxy_destroy(void *data) static void proxy_bound(void *data, uint32_t id) { struct endpoint *endpoint = data; + pw_log_info(NAME " %s. ", __FUNCTION__); endpoint->info.id = id; } @@ -382,6 +393,7 @@ static struct endpoint *create_endpoint(struct node *node) struct pw_properties *pr = node->node->obj.props; enum pw_direction direction; + pw_log_info(NAME " %s. ", __FUNCTION__); if (pr == NULL) { errno = EINVAL; return NULL; @@ -415,6 +427,7 @@ static struct endpoint *create_endpoint(struct node *node) if ((str = pw_properties_get(pr, PW_KEY_DEVICE_ICON_NAME)) != NULL) pw_properties_set(props, PW_KEY_ENDPOINT_ICON_NAME, str); + pw_log_info(NAME " %s. Invoking sm_media_session_create_object", __FUNCTION__); proxy = sm_media_session_create_object(impl->session, "client-endpoint", PW_TYPE_INTERFACE_ClientEndpoint, @@ -449,7 +462,7 @@ static struct endpoint *create_endpoint(struct node *node) endpoint->info.n_params = 2; spa_list_init(&endpoint->stream_list); - pw_log_debug(NAME" %p: new endpoint %p for v4l2 node %p", impl, endpoint, node); + pw_log_info(NAME" %p: new endpoint %p %s for v4l2 node %p", impl, endpoint, endpoint->info.name, node); pw_proxy_add_listener(proxy, &endpoint->proxy_listener, &proxy_events, endpoint); @@ -489,6 +502,7 @@ static int setup_v4l2_endpoint(struct device *device) struct sm_node *n; struct sm_device *d = device->device; + pw_log_info(NAME " %s. ", __FUNCTION__); pw_log_debug(NAME" %p: device %p setup", impl, d); spa_list_for_each(n, &d->node_list, link) { @@ -500,6 +514,7 @@ static int setup_v4l2_endpoint(struct device *device) node->device = device; node->node = n; node->impl = impl; + pw_log_info(NAME " %s. Invoking create_endpoint", __FUNCTION__); node->endpoint = create_endpoint(node); if (node->endpoint == NULL) return -errno; @@ -509,6 +524,8 @@ static int setup_v4l2_endpoint(struct device *device) static int activate_device(struct device *device) { + pw_log_info(NAME " In %s. Activating....", __FUNCTION__); + pw_log_info(NAME " In %s. Invoking setup_libcamera_endpoint", __FUNCTION__); return setup_v4l2_endpoint(device); } @@ -517,6 +534,7 @@ static int deactivate_device(struct device *device) struct impl *impl = device->impl; struct endpoint *e; + pw_log_info(NAME " %s. ", __FUNCTION__); pw_log_debug(NAME" %p: device %p deactivate", impl, device->device); spa_list_consume(e, &device->endpoint_list, link) destroy_endpoint(impl, e); @@ -529,7 +547,8 @@ static void device_update(void *data) struct device *device = data; struct impl *impl = device->impl; - pw_log_debug(NAME" %p: device %p %08x %08x", impl, device, + pw_log_info(NAME " %s. ", __FUNCTION__); + pw_log_info(NAME" %p: device %p %08x %08x", impl, device, device->device->obj.avail, device->device->obj.changed); if (!SPA_FLAG_IS_SET(device->device->obj.avail, @@ -554,13 +573,14 @@ handle_device(struct impl *impl, struct sm_object *obj) const char *media_class, *str; struct device *device; + pw_log_info(NAME " In %s. ", __FUNCTION__); if (obj->props == NULL) return 0; media_class = pw_properties_get(obj->props, PW_KEY_MEDIA_CLASS); str = pw_properties_get(obj->props, PW_KEY_DEVICE_API); - pw_log_debug(NAME" %p: device "PW_KEY_MEDIA_CLASS":%s api:%s", impl, media_class, str); + pw_log_info(NAME" %p: device "PW_KEY_MEDIA_CLASS":%s api:%s", impl, media_class, str); if (strstr(media_class, "Video/") != media_class) return 0; @@ -572,7 +592,7 @@ handle_device(struct impl *impl, struct sm_object *obj) device->id = obj->id; device->device = (struct sm_device*)obj; spa_list_init(&device->endpoint_list); - pw_log_debug(NAME" %p: found v4l2 device %d media_class %s", impl, obj->id, media_class); + pw_log_info(NAME" %p: found v4l2 device %d media_class %s", impl, obj->id, media_class); sm_object_add_listener(obj, &device->listener, &device_events, device); @@ -581,6 +601,7 @@ handle_device(struct impl *impl, struct sm_object *obj) static void destroy_device(struct impl *impl, struct device *device) { + pw_log_info(NAME " In %s. Deactivating...", __FUNCTION__); deactivate_device(device); spa_hook_remove(&device->listener); sm_object_remove_data((struct sm_object*)device->device, SESSION_KEY); @@ -591,6 +612,7 @@ static void session_create(void *data, struct sm_object *object) struct impl *impl = data; int res; + pw_log_info(NAME " In %s. comparing object->type[%s] with %s", __FUNCTION__, object->type, PW_TYPE_INTERFACE_Device); if (strcmp(object->type, PW_TYPE_INTERFACE_Device) == 0) res = handle_device(impl, object); else @@ -606,6 +628,7 @@ static void session_remove(void *data, struct sm_object *object) { struct impl *impl = data; + pw_log_info(NAME " In %s. ", __FUNCTION__); if (strcmp(object->type, PW_TYPE_INTERFACE_Device) == 0) { struct device *device; if ((device = sm_object_get_data(object, SESSION_KEY)) != NULL) @@ -631,11 +654,13 @@ int sm_v4l2_endpoint_start(struct sm_media_session *session) { struct impl *impl; + pw_log_info(NAME " In %s. ", __FUNCTION__); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; impl->session = session; + pw_log_info(NAME " In %s. Invoking sm_media_session_add_listener", __FUNCTION__); sm_media_session_add_listener(session, &impl->listener, &session_events, impl); return 0; diff --git a/src/examples/meson.build b/src/examples/meson.build index 2b47033ab..dbb91c623 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -65,6 +65,7 @@ if alsa_dep.found() 'media-session/policy-node.c', 'media-session/v4l2-monitor.c', 'media-session/v4l2-endpoint.c', + 'media-session/libcamera-monitor.c', 'media-session/suspend-node.c', c_args : [ '-D_GNU_SOURCE' ], install: true, @@ -99,6 +100,7 @@ if sdl_dep.found() install: false, dependencies : [pipewire_dep, sdl_dep], ) + executable('export-sink', 'export-sink.c', c_args : [ '-D_GNU_SOURCE' ], diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index 5228394f3..2c2195740 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -239,7 +239,7 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, struct spa_pod **params, *param; uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - uint32_t i, offset, n_params; + uint32_t i, offset, n_params, n_datas = 1; uint32_t max_buffers; size_t minsize, stride, align; uint32_t data_sizes[1]; @@ -288,7 +288,7 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, if (param) { uint32_t qmax_buffers = max_buffers, qminsize = minsize, qstride = stride, qalign = align; - uint32_t qtypes = types; + uint32_t qtypes = types, qn_datas = n_datas; spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamBuffers, NULL, @@ -296,7 +296,8 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, SPA_PARAM_BUFFERS_size, SPA_POD_OPT_Int(&qminsize), SPA_PARAM_BUFFERS_stride, SPA_POD_OPT_Int(&qstride), SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&qalign), - SPA_PARAM_BUFFERS_dataType, SPA_POD_OPT_Int(&qtypes)); + SPA_PARAM_BUFFERS_dataType, SPA_POD_OPT_Int(&qtypes), + SPA_PARAM_BUFFERS_datas, SPA_POD_OPT_Int(&qn_datas)); max_buffers = qmax_buffers == 0 ? max_buffers : SPA_MIN(qmax_buffers, @@ -305,6 +306,7 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, stride = SPA_MAX(stride, qstride); align = SPA_MAX(align, qalign); types = qtypes; + n_datas = SPA_MAX(n_datas, qn_datas); pw_log_debug(NAME" %p: %d %d %d %d %d -> %zd %zd %d %zd %d", result, qminsize, qstride, qmax_buffers, qalign, qtypes, @@ -327,7 +329,7 @@ int pw_buffers_negotiate(struct pw_context *context, uint32_t flags, max_buffers, n_params, params, - 1, + n_datas, data_sizes, data_strides, data_aligns, data_types, flags,