diff options
author | Wim Taymans <wtaymans@redhat.com> | 2017-07-11 15:57:20 +0200 |
---|---|---|
committer | Wim Taymans <wtaymans@redhat.com> | 2017-07-11 16:08:22 +0200 |
commit | d1655196c3a54de9dad08cde7ecde17d9c0acf8b (patch) | |
tree | fa3c48546d7c400882b18aba81a35d6fa4c0e85f /src | |
parent | 847cef83b665fcdeb7d50fe02ea51df460c857c4 (diff) |
move things around
Diffstat (limited to 'src')
123 files changed, 30677 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/.gitignore diff --git a/src/daemon/.gitignore b/src/daemon/.gitignore new file mode 100644 index 00000000..5347568e --- /dev/null +++ b/src/daemon/.gitignore @@ -0,0 +1 @@ +pipewire.desktop diff --git a/src/daemon/daemon-config.c b/src/daemon/daemon-config.c new file mode 100644 index 00000000..290639a3 --- /dev/null +++ b/src/daemon/daemon-config.c @@ -0,0 +1,200 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <errno.h> + +#include <pipewire/pipewire.h> +#include <pipewire/command.h> + +#include "daemon/daemon-config.h" + +#define DEFAULT_CONFIG_FILE PIPEWIRE_CONFIG_DIR "/pipewire.conf" + +static bool +parse_line(struct pw_daemon_config *config, + const char *filename, char *line, unsigned int lineno, char **err) +{ + struct pw_command *command = NULL; + char *p; + bool ret = true; + char *local_err = NULL; + + /* search for comments */ + if ((p = strchr(line, '#'))) + *p = '\0'; + + /* remove whitespaces */ + pw_strip(line, "\n\r \t"); + + if (*line == '\0') /* empty line */ + return true; + + if ((command = pw_command_parse(line, &local_err)) == NULL) { + asprintf(err, "%s:%u: %s", filename, lineno, local_err); + free(local_err); + ret = false; + } else { + spa_list_insert(config->commands.prev, &command->link); + } + + return ret; +} + +/** + * pw_daemon_config_new: + * + * Returns a new empty #struct pw_daemon_config. + */ +struct pw_daemon_config *pw_daemon_config_new(void) +{ + struct pw_daemon_config *config; + + config = calloc(1, sizeof(struct pw_daemon_config)); + spa_list_init(&config->commands); + + return config; +} + +/** + * pw_daemon_config_free: + * @config: A #struct pw_daemon_config + * + * Free all resources associated to @config. + */ +void pw_daemon_config_free(struct pw_daemon_config *config) +{ + struct pw_command *cmd, *tmp; + + spa_list_for_each_safe(cmd, tmp, &config->commands, link) + pw_command_free(cmd); + + free(config); +} + +/** + * pw_daemon_config_load_file: + * @config: A #struct pw_daemon_config + * @filename: A filename + * @err: Return location for an error string + * + * Loads PipeWire config from @filename. + * + * Returns: %true on success, otherwise %false and @err is set. + */ +bool pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err) +{ + unsigned int line; + FILE *f; + char buf[4096]; + + pw_log_debug("deamon-config %p: loading configuration file '%s'", config, filename); + + if ((f = fopen(filename, "r")) == NULL) { + asprintf(err, "failed to open configuration file '%s': %s", filename, + strerror(errno)); + goto open_error; + } + + line = 0; + + while (!feof(f)) { + if (!fgets(buf, sizeof(buf), f)) { + if (feof(f)) + break; + + asprintf(err, "failed to read configuration file '%s': %s", + filename, strerror(errno)); + goto read_error; + } + + line++; + + if (!parse_line(config, filename, buf, line, err)) + goto parse_failed; + } + fclose(f); + + return true; + + parse_failed: + read_error: + fclose(f); + open_error: + return false; +} + +/** + * pw_daemon_config_load: + * @config: A #struct pw_daemon_config + * @err: Return location for a #GError, or %NULL + * + * Loads the default config file for PipeWire. The filename can be overridden with + * an evironment variable PIPEWIRE_CONFIG_FILE. + * + * Return: %true on success, otherwise %false and @err is set. + */ +bool pw_daemon_config_load(struct pw_daemon_config *config, char **err) +{ + const char *filename; + + filename = getenv("PIPEWIRE_CONFIG_FILE"); + if (filename != NULL && *filename != '\0') { + pw_log_debug("PIPEWIRE_CONFIG_FILE set to: %s", filename); + } else { + filename = DEFAULT_CONFIG_FILE; + } + return pw_daemon_config_load_file(config, filename, err); +} + +/** + * pw_daemon_config_run_commands: + * @config: A #struct pw_daemon_config + * @core: A #struct pw_core + * + * Run all commands that have been parsed. The list of commands will be cleared + * when this function has been called. + * + * Returns: %true if all commands where executed with success, otherwise %false. + */ +bool pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_core *core) +{ + char *err = NULL; + bool ret = true; + struct pw_command *command, *tmp; + + spa_list_for_each(command, &config->commands, link) { + if (!pw_command_run(command, core, &err)) { + pw_log_warn("could not run command %s: %s", command->name, err); + free(err); + ret = false; + } + } + + spa_list_for_each_safe(command, tmp, &config->commands, link) { + pw_command_free(command); + } + + return ret; +} diff --git a/src/daemon/daemon-config.h b/src/daemon/daemon-config.h new file mode 100644 index 00000000..c5f7bdc2 --- /dev/null +++ b/src/daemon/daemon-config.h @@ -0,0 +1,53 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_DAEMON_CONFIG_H__ +#define __PIPEWIRE_DAEMON_CONFIG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/core.h> + +struct pw_daemon_config { + struct spa_list commands; +}; + +struct pw_daemon_config * +pw_daemon_config_new(void); + +void +pw_daemon_config_free(struct pw_daemon_config *config); + +bool +pw_daemon_config_load_file(struct pw_daemon_config *config, const char *filename, char **err); + +bool +pw_daemon_config_load(struct pw_daemon_config *config, char **err); + +bool +pw_daemon_config_run_commands(struct pw_daemon_config *config, struct pw_core *core); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_DAEMON_CONFIG_H__ */ diff --git a/src/daemon/main.c b/src/daemon/main.c new file mode 100644 index 00000000..d8c566ce --- /dev/null +++ b/src/daemon/main.c @@ -0,0 +1,56 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <pipewire/pipewire.h> +#include <pipewire/core.h> +#include <pipewire/module.h> + +#include "daemon-config.h" + +int main(int argc, char *argv[]) +{ + struct pw_core *core; + struct pw_main_loop *loop; + struct pw_daemon_config *config; + char *err = NULL; + + pw_init(&argc, &argv); + + /* parse configuration */ + config = pw_daemon_config_new(); + if (!pw_daemon_config_load(config, &err)) { + pw_log_error("failed to parse config: %s", err); + free(err); + return -1; + } + + loop = pw_main_loop_new(); + + core = pw_core_new(loop->loop, NULL); + + pw_daemon_config_run_commands(config, core); + + pw_main_loop_run(loop); + + pw_main_loop_destroy(loop); + + pw_core_destroy(core); + + return 0; +} diff --git a/src/daemon/meson.build b/src/daemon/meson.build new file mode 100644 index 00000000..6bd4f31c --- /dev/null +++ b/src/daemon/meson.build @@ -0,0 +1,30 @@ +pipewire_sources = [ + 'main.c', + 'daemon-config.c', +] + +pipewire_headers = [ + 'daemon-config.h', +] + +pipewire_c_args = [ + '-DHAVE_CONFIG_H', + '-D_GNU_SOURCE', + '-DG_LOG_DOMAIN=g_log_domain_pipewire', +] + +conf_config = configuration_data() +conf_install_dir = '@0@/pipewire'.format(get_option('sysconfdir')) + +configure_file(input : 'pipewire.conf.in', + output : 'pipewire.conf', + configuration : conf_config, + install_dir : conf_install_dir) + +executable('pipewire', + pipewire_sources, + install: true, + c_args : pipewire_c_args, + include_directories : [configinc, spa_inc], + dependencies : [pipewire_dep], +) diff --git a/src/daemon/pipewire-system.conf b/src/daemon/pipewire-system.conf new file mode 100644 index 00000000..9ea2adec --- /dev/null +++ b/src/daemon/pipewire-system.conf @@ -0,0 +1,31 @@ +<?xml version="1.0"?><!--*-nxml-*--> +<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" + "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> + +<!-- +This file is part of PipeWire. + +PipeWire is free software; you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation; either version 2.1 of the +License, or (at your option) any later version. + +PipeWire is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with PipeWire; if not, see <http://www.gnu.org/licenses/>. +--> + +<busconfig> + + <!-- System-wide PipeWire runs as 'pipewire' user. This fragment is + not necessary for user PipeWire instances. --> + + <policy user="pipewire"> + <allow own="org.pipewire"/> + </policy> + +</busconfig> diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in new file mode 100644 index 00000000..dd547514 --- /dev/null +++ b/src/daemon/pipewire.conf.in @@ -0,0 +1,11 @@ +#load-module libpipewire-module-protocol-dbus +load-module libpipewire-module-protocol-native +load-module libpipewire-module-suspend-on-idle +#load-module libpipewire-module-spa-monitor alsa/libspa-alsa alsa-monitor alsa +load-module libpipewire-module-spa-monitor v4l2/libspa-v4l2 v4l2-monitor v4l2 +#load-module libpipewire-module-spa-node videotestsrc/libspa-videotestsrc videotestsrc videotestsrc media.class=Video/Source Spa:POD:Object:Props:patternType=Spa:POD:Object:Props:patternType:snow +load-module libpipewire-module-autolink +#load-module libpipewire-module-mixer +load-module libpipewire-module-client-node +load-module libpipewire-module-flatpak +#load-module libpipewire-module-jack diff --git a/src/daemon/pipewire.desktop.in b/src/daemon/pipewire.desktop.in new file mode 100644 index 00000000..7ff72c55 --- /dev/null +++ b/src/daemon/pipewire.desktop.in @@ -0,0 +1,11 @@ +[Desktop Entry] +Version=1.0 +_Name=PipeWire Media System +_Comment=Start the PipeWire Media System +Exec=pipewire +Terminal=false +Type=Application +Categories= +GenericName= +X-GNOME-Autostart-Phase=Initialization +X-KDE-autostart-phase=1 diff --git a/src/examples/local-v4l2.c b/src/examples/local-v4l2.c new file mode 100644 index 00000000..5dc3569c --- /dev/null +++ b/src/examples/local-v4l2.c @@ -0,0 +1,531 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> +#include <sys/mman.h> + +#include <SDL2/SDL.h> + +#include <spa/type-map.h> +#include <spa/format-utils.h> +#include <spa/video/format-utils.h> +#include <spa/format-builder.h> +#include <spa/props.h> +#include <spa/lib/debug.h> + +#include <pipewire/pipewire.h> +#include <pipewire/sig.h> +#include <pipewire/module.h> +#include <pipewire/node-factory.h> + +struct type { + uint32_t format; + uint32_t props; + struct spa_type_meta meta; + struct spa_type_data data; + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; +}; + +static inline void init_type(struct type *type, struct spa_type_map *map) +{ + type->format = spa_type_map_get_id(map, SPA_TYPE__Format); + type->props = spa_type_map_get_id(map, SPA_TYPE__Props); + spa_type_meta_map(map, &type->meta); + spa_type_data_map(map, &type->data); + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_video_map(map, &type->format_video); + spa_type_video_format_map(map, &type->video_format); +} + +#define WIDTH 640 +#define HEIGHT 480 +#define BPP 3 + +struct data { + struct type type; + + const char *path; + + SDL_Renderer *renderer; + SDL_Window *window; + SDL_Texture *texture; + + bool running; + struct pw_loop *loop; + struct spa_source *timer; + + struct pw_core *core; + struct pw_node *node; + struct pw_port *port; + struct spa_port_info port_info; + + struct pw_node *v4l2; + + struct pw_link *link; + + uint8_t buffer[1024]; + + struct spa_video_info_raw format; + int32_t stride; + + uint8_t params_buffer[1024]; + struct spa_param *params[2]; + + struct spa_buffer *buffers[32]; + int n_buffers; +}; + +static void handle_events(struct data *data) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + exit(0); + break; + } + } +} + +static struct { + Uint32 format; + uint32_t id; +} video_formats[] = { + { + SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX1MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX4LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX4MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX8, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB332, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGR555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ARGB4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGBA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ABGR4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGRA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ARGB1555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGBA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ABGR1555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGRA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB565, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGR565, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB24, offsetof(struct spa_type_video_format, RGB),}, { + SDL_PIXELFORMAT_RGB888, offsetof(struct spa_type_video_format, RGB),}, { + SDL_PIXELFORMAT_RGBX8888, offsetof(struct spa_type_video_format, RGBx),}, { + SDL_PIXELFORMAT_BGR24, offsetof(struct spa_type_video_format, BGR),}, { + SDL_PIXELFORMAT_BGR888, offsetof(struct spa_type_video_format, BGR),}, { + SDL_PIXELFORMAT_BGRX8888, offsetof(struct spa_type_video_format, BGRx),}, { + SDL_PIXELFORMAT_ARGB2101010, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGBA8888, offsetof(struct spa_type_video_format, RGBA),}, { + SDL_PIXELFORMAT_ARGB8888, offsetof(struct spa_type_video_format, ARGB),}, { + SDL_PIXELFORMAT_BGRA8888, offsetof(struct spa_type_video_format, BGRA),}, { + SDL_PIXELFORMAT_ABGR8888, offsetof(struct spa_type_video_format, ABGR),}, { + SDL_PIXELFORMAT_YV12, offsetof(struct spa_type_video_format, YV12),}, { + SDL_PIXELFORMAT_IYUV, offsetof(struct spa_type_video_format, I420),}, { + SDL_PIXELFORMAT_YUY2, offsetof(struct spa_type_video_format, YUY2),}, { + SDL_PIXELFORMAT_UYVY, offsetof(struct spa_type_video_format, UYVY),}, { + SDL_PIXELFORMAT_YVYU, offsetof(struct spa_type_video_format, YVYU),}, { + SDL_PIXELFORMAT_NV12, offsetof(struct spa_type_video_format, NV12),}, { +SDL_PIXELFORMAT_NV21, offsetof(struct spa_type_video_format, NV21),}}; + +static uint32_t sdl_format_to_id(struct data *data, Uint32 format) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) { + if (video_formats[i].format == format) + return *SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t); + } + return data->type.video_format.UNKNOWN; +} + +static Uint32 id_to_sdl_format(struct data *data, uint32_t id) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) { + if (*SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t) == id) + return video_formats[i].format; + } + return SDL_PIXELFORMAT_UNKNOWN; +} + +#define PROP(f,key,type,...) \ + SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__) +#define PROP_U_MM(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \ + SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__) + +static int impl_port_enum_formats(struct pw_port *port, + struct spa_format **format, + const struct spa_format *filter, + int32_t index) +{ + struct data *data = port->user_data; + const struct spa_format *formats[1]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(data->buffer, sizeof(data->buffer)); + struct spa_pod_frame f[2]; + SDL_RendererInfo info; + int i, c; + + if (index != 0) + return SPA_RESULT_ENUM_END; + + SDL_GetRendererInfo(data->renderer, &info); + + spa_pod_builder_push_format(&b, &f[0], data->type.format, + data->type.media_type.video, + data->type.media_subtype.raw); + + spa_pod_builder_push_prop(&b, &f[1], data->type.format_video.format, + SPA_POD_PROP_FLAG_UNSET | + SPA_POD_PROP_RANGE_ENUM); + for (i = 0, c = 0; i < info.num_texture_formats; i++) { + uint32_t id = sdl_format_to_id(data, info.texture_formats[i]); + if (id == 0) + continue; + if (c++ == 0) + spa_pod_builder_id(&b, id); + spa_pod_builder_id(&b, id); + } + for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) { + uint32_t id = + *SPA_MEMBER(&data->type.video_format, video_formats[i].id, + uint32_t); + if (id != data->type.video_format.UNKNOWN) + spa_pod_builder_id(&b, id); + } + spa_pod_builder_pop(&b, &f[1]); + spa_pod_builder_add(&b, + PROP_U_MM(&f[1], data->type.format_video.size, SPA_POD_TYPE_RECTANGLE, + WIDTH, HEIGHT, + 1, 1, info.max_texture_width, info.max_texture_height), + PROP_U_MM(&f[1], data->type.format_video.framerate, SPA_POD_TYPE_FRACTION, + 25, 1, + 0, 1, 30, 1), + 0); + spa_pod_builder_pop(&b, &f[0]); + formats[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_format); + + spa_debug_format(formats[0]); + + *format = (struct spa_format *)formats[0]; + + return SPA_RESULT_OK; +} + +static int impl_port_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format) +{ + struct data *data = port->user_data; + struct pw_core *core = data->core; + struct spa_pod_builder b = { NULL }; + struct spa_pod_frame f[2]; + Uint32 sdl_format; + void *d; + + if (format == NULL) { + return SPA_RESULT_OK; + } + + spa_debug_format(format); + + spa_format_video_raw_parse(format, &data->format, &data->type.format_video); + + sdl_format = id_to_sdl_format(data, data->format.format); + if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) + return SPA_RESULT_ERROR; + + data->texture = SDL_CreateTexture(data->renderer, + sdl_format, + SDL_TEXTUREACCESS_STREAMING, + data->format.size.width, + data->format.size.height); + SDL_LockTexture(data->texture, NULL, &d, &data->stride); + SDL_UnlockTexture(data->texture); + + spa_pod_builder_init(&b, data->params_buffer, sizeof(data->params_buffer)); + spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_buffers.Buffers, + PROP(&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, + data->stride * data->format.size.height), + PROP(&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, + data->stride), + PROP_U_MM(&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, + 32, + 2, 32), + PROP(&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, + 16)); + data->params[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param); + + spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable, + PROP(&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, + core->type.meta.Header), + PROP(&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, + sizeof(struct spa_meta_header))); + data->params[1] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param); + + return SPA_RESULT_OK; +} + +static int impl_port_get_format(struct pw_port *port, const struct spa_format **format) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int impl_port_get_info(struct pw_port *port, const struct spa_port_info **info) +{ + struct data *data = port->user_data; + + data->port_info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + data->port_info.rate = 0; + data->port_info.props = NULL; + + *info = &data->port_info; + + return SPA_RESULT_OK; +} + +static int impl_port_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param) +{ + struct data *data = port->user_data; + + if (index >= 2) + return SPA_RESULT_ENUM_END; + + *param = data->params[index]; + + return SPA_RESULT_OK; +} + +static int impl_port_set_param(struct pw_port *port, struct spa_param *param) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int impl_port_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct data *data = port->user_data; + int i; + for (i = 0; i < n_buffers; i++) + data->buffers[i] = buffers[i]; + data->n_buffers = n_buffers; + return SPA_RESULT_OK; +} + +static int impl_port_alloc_buffers(struct pw_port *port, + struct spa_param **params, uint32_t n_params, + struct spa_buffer **buffers, uint32_t *n_buffers) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int impl_port_reuse_buffer(struct pw_port *port, uint32_t buffer_id) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int impl_port_send_command(struct pw_port *port, struct spa_command *command) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static const struct pw_port_implementation impl_port = { + PW_VERSION_PORT_IMPLEMENTATION, + impl_port_enum_formats, + impl_port_set_format, + impl_port_get_format, + impl_port_get_info, + impl_port_enum_params, + impl_port_set_param, + impl_port_use_buffers, + impl_port_alloc_buffers, + impl_port_reuse_buffer, + impl_port_send_command, +}; + +static int impl_node_get_props(struct pw_node *node, struct spa_props **props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int impl_node_set_props(struct pw_node *node, const struct spa_props *props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int impl_node_send_command(struct pw_node *node, + struct spa_command *command) +{ + return SPA_RESULT_OK; +} + +static struct pw_port* impl_node_add_port(struct pw_node *node, + enum pw_direction direction, + uint32_t port_id) +{ + return NULL; +} + +static int impl_node_process_input(struct pw_node *node) +{ + struct data *data = node->user_data; + struct pw_port *port = data->port; + struct spa_buffer *buf; + uint8_t *map; + void *sdata, *ddata; + int sstride, dstride, ostride; + int i; + uint8_t *src, *dst; + + buf = port->buffers[port->io.buffer_id]; + + if (buf->datas[0].type == data->type.data.MemFd || + buf->datas[0].type == data->type.data.DmaBuf) { + map = mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, PROT_READ, + MAP_PRIVATE, buf->datas[0].fd, 0); + sdata = SPA_MEMBER(map, buf->datas[0].mapoffset, uint8_t); + } else if (buf->datas[0].type == data->type.data.MemPtr) { + map = NULL; + sdata = buf->datas[0].data; + } else + return SPA_RESULT_ERROR; + + if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return SPA_RESULT_ERROR; + } + sstride = buf->datas[0].chunk->stride; + ostride = SPA_MIN(sstride, dstride); + + src = sdata; + dst = ddata; + for (i = 0; i < data->format.size.height; i++) { + memcpy(dst, src, ostride); + src += sstride; + dst += 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, buf->datas[0].maxsize); + + handle_events(data); + + port->io.status = SPA_RESULT_NEED_BUFFER; + + return SPA_RESULT_NEED_BUFFER; +} + +static int impl_node_process_output(struct pw_node *node) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static const struct pw_node_implementation impl_node = { + PW_VERSION_NODE_IMPLEMENTATION, + impl_node_get_props, + impl_node_set_props, + impl_node_send_command, + impl_node_add_port, + impl_node_process_input, + impl_node_process_output, +}; + +static void make_nodes(struct data *data) +{ + struct pw_node_factory *factory; + struct pw_properties *props; + + data->node = pw_node_new(data->core, NULL, "SDL-sink", NULL, 0); + data->node->user_data = data; + data->node->implementation = &impl_node; + + data->port = pw_port_new(PW_DIRECTION_INPUT, 0, 0); + data->port->user_data = data; + data->port->implementation = &impl_port; + pw_port_add(data->port, data->node); + pw_node_export(data->node); + + factory = pw_core_find_node_factory(data->core, "spa-node-factory"); + props = pw_properties_new("spa.library.name", "v4l2/libspa-v4l2", + "spa.factory.name", "v4l2-source", NULL); + data->v4l2 = pw_node_factory_create_node(factory, NULL, "v4l2-source", props); + + data->link = pw_link_new(data->core, + pw_node_get_free_port(data->v4l2, PW_DIRECTION_OUTPUT), + data->port, + NULL, + NULL, + NULL); + pw_link_activate(data->link); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + char *err; + + pw_init(&argc, &argv); + + data.loop = pw_loop_new(); + data.running = true; + data.core = pw_core_new(data.loop, NULL); + data.path = argc > 1 ? argv[1] : NULL; + + pw_module_load(data.core, "libpipewire-module-spa-node-factory", NULL, &err); + + init_type(&data.type, data.core->type.map); + + spa_debug_set_type_map(data.core->type.map); + + 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; + } + + make_nodes(&data); + + pw_loop_enter(data.loop); + while (data.running) { + pw_loop_iterate(data.loop, -1); + } + pw_loop_leave(data.loop); + + pw_loop_destroy(data.loop); + + return 0; +} diff --git a/src/examples/meson.build b/src/examples/meson.build new file mode 100644 index 00000000..e087f98d --- /dev/null +++ b/src/examples/meson.build @@ -0,0 +1,18 @@ +executable('video-src', + 'video-src.c', + install: false, + dependencies : [pipewire_dep], +) + +if sdl_dep.found() + executable('video-play', + 'video-play.c', + install: false, + dependencies : [pipewire_dep, sdl_dep], + ) + executable('local-v4l2', + 'local-v4l2.c', + install: false, + dependencies : [pipewire_dep, sdl_dep], + ) +endif diff --git a/src/examples/video-play.c b/src/examples/video-play.c new file mode 100644 index 00000000..e47d8986 --- /dev/null +++ b/src/examples/video-play.c @@ -0,0 +1,415 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> +#include <sys/mman.h> + +#include <SDL2/SDL.h> + +#include <spa/type-map.h> +#include <spa/format-utils.h> +#include <spa/video/format-utils.h> +#include <spa/format-builder.h> +#include <spa/props.h> +#include <spa/lib/debug.h> + +#include <pipewire/pipewire.h> +#include <pipewire/sig.h> + +struct type { + uint32_t format; + uint32_t props; + struct spa_type_meta meta; + struct spa_type_data data; + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; +}; + +static inline void init_type(struct type *type, struct spa_type_map *map) +{ + type->format = spa_type_map_get_id(map, SPA_TYPE__Format); + type->props = spa_type_map_get_id(map, SPA_TYPE__Props); + spa_type_meta_map(map, &type->meta); + spa_type_data_map(map, &type->data); + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_video_map(map, &type->format_video); + spa_type_video_format_map(map, &type->video_format); +} + +#define WIDTH 640 +#define HEIGHT 480 +#define BPP 3 + +struct data { + struct type type; + + const char *path; + + SDL_Renderer *renderer; + SDL_Window *window; + SDL_Texture *texture; + + bool running; + struct pw_loop *loop; + struct spa_source *timer; + + struct pw_core *core; + struct pw_remote *remote; + struct pw_listener on_state_changed; + + struct pw_stream *stream; + struct pw_listener on_stream_state_changed; + struct pw_listener on_stream_format_changed; + struct pw_listener on_stream_new_buffer; + + struct spa_video_info_raw format; + int32_t stride; + + uint8_t params_buffer[1024]; + int counter; +}; + +static void handle_events(struct data *data) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + exit(0); + break; + } + } +} + +static void +on_stream_new_buffer(struct pw_listener *listener, struct pw_stream *stream, uint32_t id) +{ + struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_new_buffer); + struct spa_buffer *buf; + uint8_t *map; + void *sdata, *ddata; + int sstride, dstride, ostride; + int i; + uint8_t *src, *dst; + + buf = pw_stream_peek_buffer(data->stream, id); + + if (buf->datas[0].type == data->type.data.MemFd) { + map = mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, PROT_READ, + MAP_PRIVATE, buf->datas[0].fd, 0); + sdata = SPA_MEMBER(map, buf->datas[0].mapoffset, uint8_t); + } else if (buf->datas[0].type == data->type.data.MemPtr) { + map = NULL; + sdata = buf->datas[0].data; + } else + return; + + if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return; + } + sstride = buf->datas[0].chunk->stride; + ostride = SPA_MIN(sstride, dstride); + + src = sdata; + dst = ddata; + for (i = 0; i < data->format.size.height; i++) { + memcpy(dst, src, ostride); + src += sstride; + dst += 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, buf->datas[0].maxsize); + + pw_stream_recycle_buffer(data->stream, id); + + handle_events(data); +} + +static void on_stream_state_changed(struct pw_listener *listener, struct pw_stream *stream) +{ + printf("stream state: \"%s\"\n", pw_stream_state_as_string(stream->state)); +} + +static struct { + Uint32 format; + uint32_t id; +} video_formats[] = { + { + SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_UNKNOWN, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX1LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX1MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX4LSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX4MSB, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_INDEX8, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB332, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGR555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ARGB4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGBA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ABGR4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGRA4444, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ARGB1555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGBA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_ABGR1555, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGRA5551, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB565, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_BGR565, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGB24, offsetof(struct spa_type_video_format, RGB),}, { + SDL_PIXELFORMAT_RGB888, offsetof(struct spa_type_video_format, RGB),}, { + SDL_PIXELFORMAT_RGBX8888, offsetof(struct spa_type_video_format, RGBx),}, { + SDL_PIXELFORMAT_BGR24, offsetof(struct spa_type_video_format, BGR),}, { + SDL_PIXELFORMAT_BGR888, offsetof(struct spa_type_video_format, BGR),}, { + SDL_PIXELFORMAT_BGRX8888, offsetof(struct spa_type_video_format, BGRx),}, { + SDL_PIXELFORMAT_ARGB2101010, offsetof(struct spa_type_video_format, UNKNOWN),}, { + SDL_PIXELFORMAT_RGBA8888, offsetof(struct spa_type_video_format, RGBA),}, { + SDL_PIXELFORMAT_ARGB8888, offsetof(struct spa_type_video_format, ARGB),}, { + SDL_PIXELFORMAT_BGRA8888, offsetof(struct spa_type_video_format, BGRA),}, { + SDL_PIXELFORMAT_ABGR8888, offsetof(struct spa_type_video_format, ABGR),}, { + SDL_PIXELFORMAT_YV12, offsetof(struct spa_type_video_format, YV12),}, { + SDL_PIXELFORMAT_IYUV, offsetof(struct spa_type_video_format, I420),}, { + SDL_PIXELFORMAT_YUY2, offsetof(struct spa_type_video_format, YUY2),}, { + SDL_PIXELFORMAT_UYVY, offsetof(struct spa_type_video_format, UYVY),}, { + SDL_PIXELFORMAT_YVYU, offsetof(struct spa_type_video_format, YVYU),}, { + SDL_PIXELFORMAT_NV12, offsetof(struct spa_type_video_format, NV12),}, { +SDL_PIXELFORMAT_NV21, offsetof(struct spa_type_video_format, NV21),}}; + +static uint32_t sdl_format_to_id(struct data *data, Uint32 format) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) { + if (video_formats[i].format == format) + return *SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t); + } + return data->type.video_format.UNKNOWN; +} + +static Uint32 id_to_sdl_format(struct data *data, uint32_t id) +{ + int i; + + for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) { + if (*SPA_MEMBER(&data->type.video_format, video_formats[i].id, uint32_t) == id) + return video_formats[i].format; + } + return SDL_PIXELFORMAT_UNKNOWN; +} + +#define PROP(f,key,type,...) \ + SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__) +#define PROP_U_MM(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \ + SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__) + +static void +on_stream_format_changed(struct pw_listener *listener, + struct pw_stream *stream, struct spa_format *format) +{ + struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_format_changed); + struct pw_remote *remote = stream->remote; + struct pw_core *core = remote->core; + struct spa_pod_builder b = { NULL }; + struct spa_pod_frame f[2]; + struct spa_param *params[2]; + Uint32 sdl_format; + void *d; + + if (format == NULL) { + pw_stream_finish_format(stream, SPA_RESULT_OK, NULL, 0); + return; + } + + spa_debug_format(format); + + spa_format_video_raw_parse(format, &data->format, &data->type.format_video); + + sdl_format = id_to_sdl_format(data, data->format.format); + if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { + pw_stream_finish_format(stream, SPA_RESULT_ERROR, NULL, 0); + return; + } + + data->texture = SDL_CreateTexture(data->renderer, + sdl_format, + SDL_TEXTUREACCESS_STREAMING, + data->format.size.width, + data->format.size.height); + SDL_LockTexture(data->texture, NULL, &d, &data->stride); + SDL_UnlockTexture(data->texture); + + spa_pod_builder_init(&b, data->params_buffer, sizeof(data->params_buffer)); + spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_buffers.Buffers, + PROP(&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, + data->stride * data->format.size.height), + PROP(&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, + data->stride), + PROP_U_MM(&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, + 32, + 2, 32), + PROP(&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, + 16)); + params[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param); + + spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable, + PROP(&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, + core->type.meta.Header), + PROP(&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, + sizeof(struct spa_meta_header))); + params[1] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param); + + pw_stream_finish_format(stream, SPA_RESULT_OK, params, 2); +} + +static void on_state_changed(struct pw_listener *listener, struct pw_remote *remote) +{ + struct data *data = SPA_CONTAINER_OF(listener, struct data, on_state_changed); + + switch (remote->state) { + case PW_REMOTE_STATE_ERROR: + printf("remote error: %s\n", remote->error); + data->running = false; + break; + + case PW_REMOTE_STATE_CONNECTED: + { + const struct spa_format *formats[1]; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct spa_pod_frame f[2]; + SDL_RendererInfo info; + int i, c; + + printf("remote state: \"%s\"\n", + pw_remote_state_as_string(remote->state)); + + data->stream = pw_stream_new(remote, "video-play", NULL); + + SDL_GetRendererInfo(data->renderer, &info); + + spa_pod_builder_push_format(&b, &f[0], data->type.format, + data->type.media_type.video, + data->type.media_subtype.raw); + + spa_pod_builder_push_prop(&b, &f[1], data->type.format_video.format, + SPA_POD_PROP_FLAG_UNSET | + SPA_POD_PROP_RANGE_ENUM); + for (i = 0, c = 0; i < info.num_texture_formats; i++) { + uint32_t id = sdl_format_to_id(data, info.texture_formats[i]); + if (id == 0) + continue; + if (c++ == 0) + spa_pod_builder_id(&b, id); + spa_pod_builder_id(&b, id); + } + for (i = 0; i < SPA_N_ELEMENTS(video_formats); i++) { + uint32_t id = + *SPA_MEMBER(&data->type.video_format, video_formats[i].id, + uint32_t); + if (id != data->type.video_format.UNKNOWN) + spa_pod_builder_id(&b, id); + } + spa_pod_builder_pop(&b, &f[1]); + spa_pod_builder_add(&b, + PROP_U_MM(&f[1], data->type.format_video.size, SPA_POD_TYPE_RECTANGLE, + WIDTH, HEIGHT, + 1, 1, info.max_texture_width, info.max_texture_height), + PROP_U_MM(&f[1], data->type.format_video.framerate, SPA_POD_TYPE_FRACTION, + 25, 1, + 0, 1, 30, 1), + 0); + spa_pod_builder_pop(&b, &f[0]); + formats[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_format); + + printf("supported formats:\n"); + spa_debug_format(formats[0]); + + pw_signal_add(&data->stream->state_changed, + &data->on_stream_state_changed, on_stream_state_changed); + pw_signal_add(&data->stream->format_changed, + &data->on_stream_format_changed, on_stream_format_changed); + pw_signal_add(&data->stream->new_buffer, + &data->on_stream_new_buffer, on_stream_new_buffer); + + pw_stream_connect(data->stream, + PW_DIRECTION_INPUT, + PW_STREAM_MODE_BUFFER, + data->path, PW_STREAM_FLAG_AUTOCONNECT, 1, formats); + break; + } + default: + printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state)); + break; + } +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + + pw_init(&argc, &argv); + + data.loop = pw_loop_new(); + data.running = true; + data.core = pw_core_new(data.loop, NULL); + data.remote = pw_remote_new(data.core, NULL); + data.path = argc > 1 ? argv[1] : NULL; + + init_type(&data.type, data.core->type.map); + + spa_debug_set_type_map(data.core->type.map); + + 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; + } + + pw_signal_add(&data.remote->state_changed, &data.on_state_changed, on_state_changed); + + pw_remote_connect(data.remote); + + pw_loop_enter(data.loop); + while (data.running) { + pw_loop_iterate(data.loop, -1); + } + pw_loop_leave(data.loop); + + pw_remote_destroy(data.remote); + pw_loop_destroy(data.loop); + + return 0; +} diff --git a/src/examples/video-src.c b/src/examples/video-src.c new file mode 100644 index 00000000..a4d7234c --- /dev/null +++ b/src/examples/video-src.c @@ -0,0 +1,296 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> +#include <errno.h> +#include <time.h> +#include <sys/mman.h> + +#include <spa/type-map.h> +#include <spa/format-utils.h> +#include <spa/video/format-utils.h> +#include <spa/format-builder.h> +#include <spa/props.h> +#include <spa/lib/debug.h> + +#include <pipewire/pipewire.h> +#include <pipewire/sig.h> + +struct type { + uint32_t format; + uint32_t props; + struct spa_type_meta meta; + struct spa_type_data data; + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; +}; + +static inline void init_type(struct type *type, struct spa_type_map *map) +{ + type->format = spa_type_map_get_id(map, SPA_TYPE__Format); + type->props = spa_type_map_get_id(map, SPA_TYPE__Props); + spa_type_meta_map(map, &type->meta); + spa_type_data_map(map, &type->data); + spa_type_media_type_map(map, &type->media_type); + spa_type_media_subtype_map(map, &type->media_subtype); + spa_type_format_video_map(map, &type->format_video); + spa_type_video_format_map(map, &type->video_format); +} + +#define BPP 3 + +struct data { + struct type type; + + bool running; + struct pw_loop *loop; + struct spa_source *timer; + + struct pw_core *core; + struct pw_remote *remote; + struct pw_listener on_state_changed; + + struct pw_stream *stream; + struct pw_listener on_stream_state_changed; + struct pw_listener on_stream_format_changed; + + struct spa_video_info_raw format; + int32_t stride; + + uint8_t params_buffer[1024]; + int counter; + uint32_t seq; +}; + +static void on_timeout(struct spa_loop_utils *utils, struct spa_source *source, void *userdata) +{ + struct data *data = userdata; + uint32_t id; + struct spa_buffer *buf; + int i, j; + uint8_t *p, *map; + struct spa_meta_header *h; + + id = pw_stream_get_empty_buffer(data->stream); + if (id == SPA_ID_INVALID) + return; + + buf = pw_stream_peek_buffer(data->stream, id); + + if (buf->datas[0].type == data->type.data.MemFd) { + map = + mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, + PROT_READ | PROT_WRITE, MAP_SHARED, buf->datas[0].fd, 0); + if (map == MAP_FAILED) { + printf("failed to mmap: %s\n", strerror(errno)); + return; + } + p = SPA_MEMBER(map, buf->datas[0].mapoffset, uint8_t); + } else if (buf->datas[0].type == data->type.data.MemPtr) { + map = NULL; + p = buf->datas[0].data; + } else + return; + + if ((h = spa_buffer_find_meta(buf, data->type.meta.Header))) { +#if 0 + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + h->pts = SPA_TIMESPEC_TO_TIME(&now); +#else + h->pts = -1; +#endif + h->flags = 0; + h->seq = data->seq++; + h->dts_offset = 0; + } + + for (i = 0; i < data->format.size.height; i++) { + for (j = 0; j < data->format.size.width * BPP; j++) { + p[j] = data->counter + j * i; + } + p += buf->datas[0].chunk->stride; + data->counter += 13; + } + + if (map) + munmap(map, buf->datas[0].maxsize + buf->datas[0].mapoffset); + + pw_stream_send_buffer(data->stream, id); +} + +static void on_stream_state_changed(struct pw_listener *listener, struct pw_stream *stream) +{ + struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_state_changed); + + printf("stream state: \"%s\"\n", pw_stream_state_as_string(stream->state)); + + switch (stream->state) { + case PW_STREAM_STATE_PAUSED: + pw_loop_update_timer(data->loop, data->timer, NULL, NULL, false); + break; + + case PW_STREAM_STATE_STREAMING: + { + struct timespec timeout, interval; + + timeout.tv_sec = 0; + timeout.tv_nsec = 1; + interval.tv_sec = 0; + interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; + + pw_loop_update_timer(data->loop, data->timer, &timeout, &interval, false); + break; + } + default: + break; + } +} + +#define PROP(f,key,type,...) \ + SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__) +#define PROP_U_MM(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \ + SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__) + +static void +on_stream_format_changed(struct pw_listener *listener, + struct pw_stream *stream, struct spa_format *format) +{ + struct data *data = SPA_CONTAINER_OF(listener, struct data, on_stream_format_changed); + struct pw_remote *remote = stream->remote; + struct pw_core *core = remote->core; + struct spa_pod_builder b = { NULL }; + struct spa_pod_frame f[2]; + struct spa_param *params[2]; + + if (format == NULL) { + pw_stream_finish_format(stream, SPA_RESULT_OK, NULL, 0); + return; + } + spa_format_video_raw_parse(format, &data->format, &data->type.format_video); + + data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); + + spa_pod_builder_init(&b, data->params_buffer, sizeof(data->params_buffer)); + spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_buffers.Buffers, + PROP(&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, + data->stride * data->format.size.height), + PROP(&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, + data->stride), + PROP_U_MM(&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, + 32, + 2, 32), + PROP(&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, + 16)); + params[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param); + + spa_pod_builder_object(&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable, + PROP(&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, + core->type.meta.Header), + PROP(&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, + sizeof(struct spa_meta_header))); + params[1] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_param); + + pw_stream_finish_format(stream, SPA_RESULT_OK, params, 2); +} + +static void on_state_changed(struct pw_listener *listener, struct pw_remote *remote) +{ + struct data *data = SPA_CONTAINER_OF(listener, struct data, on_state_changed); + + switch (remote->state) { + case PW_REMOTE_STATE_ERROR: + printf("remote error: %s\n", remote->error); + data->running = false; + break; + + case PW_REMOTE_STATE_CONNECTED: + { + const struct spa_format *formats[1]; + uint8_t buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + struct spa_pod_frame f[2]; + + printf("remote state: \"%s\"\n", + pw_remote_state_as_string(remote->state)); + + data->stream = pw_stream_new(remote, "video-src", NULL); + + spa_pod_builder_format(&b, &f[0], data->type.format, + data->type.media_type.video, + data->type.media_subtype.raw, + PROP(&f[1], data->type.format_video.format, SPA_POD_TYPE_ID, + data->type.video_format.RGB), + PROP_U_MM(&f[1], data->type.format_video.size, SPA_POD_TYPE_RECTANGLE, + 320, 240, + 1, 1, 4096, 4096), + PROP(&f[1], data->type.format_video.framerate, SPA_POD_TYPE_FRACTION, + 25, 1)); + formats[0] = SPA_POD_BUILDER_DEREF(&b, f[0].ref, struct spa_format); + + pw_signal_add(&data->stream->state_changed, + &data->on_stream_state_changed, on_stream_state_changed); + pw_signal_add(&data->stream->format_changed, + &data->on_stream_format_changed, on_stream_format_changed); + + pw_stream_connect(data->stream, + PW_DIRECTION_OUTPUT, + PW_STREAM_MODE_BUFFER, + NULL, PW_STREAM_FLAG_NONE, 1, formats); + break; + } + default: + printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state)); + break; + } +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + + pw_init(&argc, &argv); + + data.loop = pw_loop_new(); + data.running = true; + data.core = pw_core_new(data.loop, NULL); + data.remote = pw_remote_new(data.core, NULL); + + init_type(&data.type, data.core->type.map); + + data.timer = pw_loop_add_timer(data.loop, on_timeout, &data); + + pw_signal_add(&data.remote->state_changed, &data.on_state_changed, on_state_changed); + + pw_remote_connect(data.remote); + + pw_loop_enter(data.loop); + while (data.running) { + pw_loop_iterate(data.loop, -1); + } + pw_loop_leave(data.loop); + + pw_remote_destroy(data.remote); + pw_loop_destroy(data.loop); + + return 0; +} diff --git a/src/extensions/client-node.h b/src/extensions/client-node.h new file mode 100644 index 00000000..445d9de3 --- /dev/null +++ b/src/extensions/client-node.h @@ -0,0 +1,299 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_EXT_CLIENT_NODE_H__ +#define __PIPEWIRE_EXT_CLIENT_NODE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/defs.h> +#include <spa/props.h> +#include <spa/format.h> +#include <spa/param-alloc.h> +#include <spa/node.h> + +#define PIPEWIRE_TYPE__ClientNode PIPEWIRE_TYPE_NODE_BASE "Client" +#define PIPEWIRE_TYPE_CLIENT_NODE_BASE PIPEWIRE_TYPE__ClientNode ":" + +/** information about a buffer */ +struct pw_client_node_buffer { + uint32_t mem_id; /**< the memory id for the metadata */ + uint32_t offset; /**< offset in memory */ + uint32_t size; /**< size in memory */ + struct spa_buffer *buffer; /**< buffer describing metadata and buffer memory */ +}; + +#define PW_VERSION_CLIENT_NODE 0 + +#define PW_CLIENT_NODE_METHOD_DONE 0 +#define PW_CLIENT_NODE_METHOD_UPDATE 1 +#define PW_CLIENT_NODE_METHOD_PORT_UPDATE 2 +#define PW_CLIENT_NODE_METHOD_EVENT 3 +#define PW_CLIENT_NODE_METHOD_DESTROY 4 +#define PW_CLIENT_NODE_METHOD_NUM 5 + +/** \ref pw_client_node methods */ +struct pw_client_node_methods { + /** Complete an async operation */ + void (*done) (void *object, int seq, int res); + + /** + * Update the node ports and properties + * + * Update the maximum number of ports and the properties of the + * client node. + * \param change_mask bitfield with changed parameters + * \param max_input_ports new max input ports + * \param max_output_ports new max output ports + * \param props new properties + */ + void (*update) (void *object, +#define PW_CLIENT_NODE_UPDATE_MAX_INPUTS (1 << 0) +#define PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS (1 << 1) +#define PW_CLIENT_NODE_UPDATE_PROPS (1 << 2) + uint32_t change_mask, + uint32_t max_input_ports, + uint32_t max_output_ports, + const struct spa_props *props); + + /** + * Update a node port + * + * Update the information of one port of a node. + * \param direction the direction of the port + * \param port_id the port id to update + * \param change_mask a bitfield of changed items + * \param n_possible_formats number of possible formats + * \param possible_formats array of possible formats on the port + * \param format the current format on the port + * \param n_params number of port parameters + * \param params array of port parameters + * \param info port information + */ + void (*port_update) (void *object, + enum spa_direction direction, + uint32_t port_id, +#define PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS (1 << 0) +#define PW_CLIENT_NODE_PORT_UPDATE_FORMAT (1 << 1) +#define PW_CLIENT_NODE_PORT_UPDATE_PARAMS (1 << 2) +#define PW_CLIENT_NODE_PORT_UPDATE_INFO (1 << 3) + uint32_t change_mask, + uint32_t n_possible_formats, + const struct spa_format **possible_formats, + const struct spa_format *format, + uint32_t n_params, + const struct spa_param **params, + const struct spa_port_info *info); + /** + * Send an event to the node + * \param event the event to send + */ + void (*event) (void *object, struct spa_event *event); + /** + * Destroy the client_node + */ + void (*destroy) (void *object); +}; + +#define pw_client_node_do_done(p,...) pw_proxy_do(p,struct pw_client_node_methods,done,__VA_ARGS__) +#define pw_client_node_do_update(p,...) pw_proxy_do(p,struct pw_client_node_methods,update,__VA_ARGS__) +#define pw_client_node_do_port_update(p,...) pw_proxy_do(p,struct pw_client_node_methods,port_update,__VA_ARGS__) +#define pw_client_node_do_event(p,...) pw_proxy_do(p,struct pw_client_node_methods,event,__VA_ARGS__) +#define pw_client_node_do_destroy(p) pw_proxy_do_na(p,struct pw_client_node_methods,destroy) + +#define PW_CLIENT_NODE_EVENT_TRANSPORT 0 +#define PW_CLIENT_NODE_EVENT_SET_PROPS 1 +#define PW_CLIENT_NODE_EVENT_EVENT 2 +#define PW_CLIENT_NODE_EVENT_ADD_PORT 3 +#define PW_CLIENT_NODE_EVENT_REMOVE_PORT 4 +#define PW_CLIENT_NODE_EVENT_SET_FORMAT 5 +#define PW_CLIENT_NODE_EVENT_SET_PARAM 6 +#define PW_CLIENT_NODE_EVENT_ADD_MEM 7 +#define PW_CLIENT_NODE_EVENT_USE_BUFFERS 8 +#define PW_CLIENT_NODE_EVENT_NODE_COMMAND 9 +#define PW_CLIENT_NODE_EVENT_PORT_COMMAND 10 +#define PW_CLIENT_NODE_EVENT_NUM 11 + +/** \ref pw_client_node events */ +struct pw_client_node_events { + /** + * Notify of a new transport area + * + * The transport area is used to exchange real-time commands between + * the client and the server. + * + * \param node_id the node id created for this client node + * \param readfd fd for signal data can be read + * \param writefd fd for signal data can be written + * \param memfd the memory fd of the area + * \param offset the offset to map + * \param size the size to map + */ + void (*transport) (void *object, + uint32_t node_id, + int readfd, + int writefd, + int memfd, + uint32_t offset, + uint32_t size); + /** + * Notify of a property change + * + * When the server configures the properties on the node + * this event is sent + * + * \param seq a sequence number + * \param props the props to set + */ + void (*set_props) (void *object, + uint32_t seq, + const struct spa_props *props); + /** + * Receive an event from the client node + * \param event the received event */ + void (*event) (void *object, const struct spa_event *event); + /** + * A new port was added to the node + * + * The server can at any time add a port to the node when there + * are free ports available. + * + * \param seq a sequence number + * \param direction the direction of the port + * \param port_id the new port id + */ + void (*add_port) (void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id); + /** + * A port was removed from the node + * + * \param seq a sequence number + * \param direction a port direction + * \param port_id the remove port id + */ + void (*remove_port) (void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id); + /** + * A format was configured on the port + * + * \param seq a sequence number + * \param direction a port direction + * \param port_id the port id + * \param flags flags used when setting the format + * \param format the new format + */ + void (*set_format) (void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_format *format); + /** + * A parameter was configured on the port + * + * \param seq a sequence number + * \param direction a port direction + * \param port_id the port id + * \param param the new param + */ + void (*set_param) (void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + const struct spa_param *param); + /** + * Memory was added for a port + * + * \param direction a port direction + * \param port_id the port id + * \param mem_id the id of the memory + * \param type the memory type + * \param memfd the fd of the memory + * \param flags flags for the \a memfd + * \param offset valid offset of mapped memory from \a memfd + * \param size valid size of mapped memory from \a memfd + */ + void (*add_mem) (void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t mem_id, + uint32_t type, + int memfd, + uint32_t flags, + uint32_t offset, + uint32_t size); + /** + * Notify the port of buffers + * + * \param seq a sequence number + * \param direction a port direction + * \param port_id the port id + * \param n_buffer the number of buffers + * \param buffers and array of buffer descriptions + */ + void (*use_buffers) (void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + uint32_t n_buffers, + struct pw_client_node_buffer *buffers); + /** + * Notify of a new node command + * + * \param seq a sequence number + * \param command the command + */ + void (*node_command) (void *object, uint32_t seq, const struct spa_command *command); + /** + * Notify of a new port command + * + * \param direction a port direction + * \param port_id the port id + * \param command the command + */ + void (*port_command) (void *object, + enum spa_direction direction, + uint32_t port_id, + const struct spa_command *command); + +}; + +#define pw_client_node_notify_transport(r,...) pw_resource_notify(r,struct pw_client_node_events,transport,__VA_ARGS__) +#define pw_client_node_notify_set_props(r,...) pw_resource_notify(r,struct pw_client_node_events,props,__VA_ARGS__) +#define pw_client_node_notify_event(r,...) pw_resource_notify(r,struct pw_client_node_events,event,__VA_ARGS__) +#define pw_client_node_notify_add_port(r,...) pw_resource_notify(r,struct pw_client_node_events,add_port,__VA_ARGS__) +#define pw_client_node_notify_remove_port(r,...) pw_resource_notify(r,struct pw_client_node_events,remove_port,__VA_ARGS__) +#define pw_client_node_notify_set_format(r,...) pw_resource_notify(r,struct pw_client_node_events,set_format,__VA_ARGS__) +#define pw_client_node_notify_set_param(r,...) pw_resource_notify(r,struct pw_client_node_events,set_param,__VA_ARGS__) +#define pw_client_node_notify_add_mem(r,...) pw_resource_notify(r,struct pw_client_node_events,add_mem,__VA_ARGS__) +#define pw_client_node_notify_use_buffers(r,...) pw_resource_notify(r,struct pw_client_node_events,use_buffers,__VA_ARGS__) +#define pw_client_node_notify_node_command(r,...) pw_resource_notify(r,struct pw_client_node_events,node_command,__VA_ARGS__) +#define pw_client_node_notify_port_command(r,...) pw_resource_notify(r,struct pw_client_node_events,port_command,__VA_ARGS__) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_EXT_CLIENT_NODE_H__ */ diff --git a/src/extensions/meson.build b/src/extensions/meson.build new file mode 100644 index 00000000..ca784be2 --- /dev/null +++ b/src/extensions/meson.build @@ -0,0 +1,5 @@ +pipewire_ext_headers = [ + 'client-node.h', +] + +install_headers(pipewire_ext_headers, subdir : 'pipewire/extensions') diff --git a/src/gst/gstpipewire.c b/src/gst/gstpipewire.c new file mode 100644 index 00000000..b77640fd --- /dev/null +++ b/src/gst/gstpipewire.c @@ -0,0 +1,64 @@ +/* GStreamer + * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-pipewiresrc + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch -v pipewiresrc ! ximagesink + * ]| Shows PipeWire output in an X window. + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstpipewiresrc.h" +#include "gstpipewiresink.h" +#include "gstpipewiredeviceprovider.h" + +GST_DEBUG_CATEGORY (pipewire_debug); + +static gboolean +plugin_init (GstPlugin *plugin) +{ + pw_init (NULL, NULL); + + gst_element_register (plugin, "pipewiresrc", GST_RANK_PRIMARY + 1, + GST_TYPE_PIPEWIRE_SRC); + gst_element_register (plugin, "pipewiresink", GST_RANK_NONE, + GST_TYPE_PIPEWIRE_SINK); + + if (!gst_device_provider_register (plugin, "pipewiredeviceprovider", + GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_DEVICE_PROVIDER)) + return FALSE; + + GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWirie elements"); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + pipewire, + "Uses PipeWire to handle media streams", + plugin_init, VERSION, "LGPL", "pipewire", "pipewire.org") diff --git a/src/gst/gstpipewireclock.c b/src/gst/gstpipewireclock.c new file mode 100644 index 00000000..a82631ef --- /dev/null +++ b/src/gst/gstpipewireclock.c @@ -0,0 +1,92 @@ +/* GStreamer + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> + +#include "gstpipewireclock.h" + +GST_DEBUG_CATEGORY_STATIC (gst_pipewire_clock_debug_category); +#define GST_CAT_DEFAULT gst_pipewire_clock_debug_category + +G_DEFINE_TYPE (GstPipeWireClock, gst_pipewire_clock, GST_TYPE_SYSTEM_CLOCK); + +GstClock * +gst_pipewire_clock_new (struct pw_stream *stream) +{ + GstPipeWireClock *clock; + + clock = g_object_new (GST_TYPE_PIPEWIRE_CLOCK, NULL); + clock->stream = stream; + + return GST_CLOCK_CAST (clock); +} + +static GstClockTime +gst_pipewire_clock_get_internal_time (GstClock * clock) +{ + GstPipeWireClock *pclock = (GstPipeWireClock *) clock; + GstClockTime result; + struct pw_time t; + + pw_stream_get_time (pclock->stream, &t); + + if (t.rate) + result = gst_util_uint64_scale_int (t.ticks, GST_SECOND, t.rate); + else + result = GST_CLOCK_TIME_NONE; + + GST_DEBUG ("%"PRId64", %d %"PRId64, t.ticks, t.rate, result); + + return result; +} + + +static void +gst_pipewire_clock_finalize (GObject * object) +{ + GstPipeWireClock *clock = GST_PIPEWIRE_CLOCK (object); + + GST_DEBUG_OBJECT (clock, "finalize"); + + G_OBJECT_CLASS (gst_pipewire_clock_parent_class)->finalize (object); +} + +static void +gst_pipewire_clock_class_init (GstPipeWireClockClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstClockClass *gstclock_class = GST_CLOCK_CLASS (klass); + + gobject_class->finalize = gst_pipewire_clock_finalize; + + gstclock_class->get_internal_time = gst_pipewire_clock_get_internal_time; + + GST_DEBUG_CATEGORY_INIT (gst_pipewire_clock_debug_category, "pipewireclock", 0, + "debug category for pipewireclock object"); +} + +static void +gst_pipewire_clock_init (GstPipeWireClock * clock) +{ + GST_OBJECT_FLAG_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER); +} diff --git a/src/gst/gstpipewireclock.h b/src/gst/gstpipewireclock.h new file mode 100644 index 00000000..3020c83c --- /dev/null +++ b/src/gst/gstpipewireclock.h @@ -0,0 +1,62 @@ +/* GStreamer + * Copyright (C) <2016> Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_PIPEWIRE_CLOCK_H__ +#define __GST_PIPEWIRE_CLOCK_H__ + +#include <gst/gst.h> + +#include <pipewire/pipewire.h> + +G_BEGIN_DECLS + +#define GST_TYPE_PIPEWIRE_CLOCK \ + (gst_pipewire_clock_get_type()) +#define GST_PIPEWIRE_CLOCK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClock)) +#define GST_PIPEWIRE_CLOCK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClockClass)) +#define GST_IS_PIPEWIRE_CLOCK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_CLOCK)) +#define GST_IS_PIPEWIRE_CLOCK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_CLOCK)) +#define GST_PIPEWIRE_CLOCK_GET_CLASS(klass) \ + (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_CLOCK, GstPipeWireClockClass)) + +typedef struct _GstPipeWireClock GstPipeWireClock; +typedef struct _GstPipeWireClockClass GstPipeWireClockClass; + +struct _GstPipeWireClock { + GstSystemClock parent; + + struct pw_stream *stream; +}; + +struct _GstPipeWireClockClass { + GstSystemClockClass parent_class; +}; + +GType gst_pipewire_clock_get_type (void); + +GstClock * gst_pipewire_clock_new (struct pw_stream *stream); + + +G_END_DECLS + +#endif /* __GST_PIPEWIRE_CLOCK_H__ */ diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c new file mode 100644 index 00000000..1af78463 --- /dev/null +++ b/src/gst/gstpipewiredeviceprovider.c @@ -0,0 +1,647 @@ +/* GStreamer + * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com> + * (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * pipewiredeviceprovider.c: PipeWire device probing and monitoring + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> + +#include <gst/gst.h> + +#include "gstpipewireformat.h" +#include "gstpipewiredeviceprovider.h" +#include "gstpipewiresrc.h" +#include "gstpipewiresink.h" + +GST_DEBUG_CATEGORY_EXTERN (pipewire_debug); +#define GST_CAT_DEFAULT pipewire_debug + +G_DEFINE_TYPE (GstPipeWireDevice, gst_pipewire_device, GST_TYPE_DEVICE); + +enum +{ + PROP_ID = 1, +}; + +static GstDevice * +gst_pipewire_device_new (uint32_t id, const gchar * device_name, + GstCaps * caps, const gchar *klass, + GstPipeWireDeviceType type, GstStructure *props) +{ + GstPipeWireDevice *gstdev; + const gchar *element = NULL; + + g_return_val_if_fail (device_name, NULL); + g_return_val_if_fail (caps, NULL); + + switch (type) { + case GST_PIPEWIRE_DEVICE_TYPE_SOURCE: + element = "pipewiresrc"; + break; + case GST_PIPEWIRE_DEVICE_TYPE_SINK: + element = "pipewiresink"; + break; + default: + g_assert_not_reached (); + break; + } + + gstdev = g_object_new (GST_TYPE_PIPEWIRE_DEVICE, + "display-name", device_name, "caps", caps, "device-class", klass, + "id", id, "properties", props, NULL); + + gstdev->id = id; + gstdev->type = type; + gstdev->element = element; + + return GST_DEVICE (gstdev); +} + +static GstElement * +gst_pipewire_device_create_element (GstDevice * device, const gchar * name) +{ + GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device); + GstElement *elem; + gchar *str; + + elem = gst_element_factory_make (pipewire_dev->element, name); + str = g_strdup_printf ("%u", pipewire_dev->id); + g_object_set (elem, "path", str, NULL); + g_free (str); + + return elem; +} + +static gboolean +gst_pipewire_device_reconfigure_element (GstDevice * device, GstElement * element) +{ + GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device); + gchar *str; + + if (!strcmp (pipewire_dev->element, "pipewiresrc")) { + if (!GST_IS_PIPEWIRE_SRC (element)) + return FALSE; + } else if (!strcmp (pipewire_dev->element, "pipewiresink")) { + if (!GST_IS_PIPEWIRE_SINK (element)) + return FALSE; + } else { + g_assert_not_reached (); + } + + str = g_strdup_printf ("%u", pipewire_dev->id); + g_object_set (element, "path", str, NULL); + g_free (str); + + return TRUE; +} + + +static void +gst_pipewire_device_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstPipeWireDevice *device; + + device = GST_PIPEWIRE_DEVICE_CAST (object); + + switch (prop_id) { + case PROP_ID: + g_value_set_uint (value, device->id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPipeWireDevice *device; + + device = GST_PIPEWIRE_DEVICE_CAST (object); + + switch (prop_id) { + case PROP_ID: + device->id = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_finalize (GObject * object) +{ + G_OBJECT_CLASS (gst_pipewire_device_parent_class)->finalize (object); +} + +static void +gst_pipewire_device_class_init (GstPipeWireDeviceClass * klass) +{ + GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + dev_class->create_element = gst_pipewire_device_create_element; + dev_class->reconfigure_element = gst_pipewire_device_reconfigure_element; + + object_class->get_property = gst_pipewire_device_get_property; + object_class->set_property = gst_pipewire_device_set_property; + object_class->finalize = gst_pipewire_device_finalize; + + g_object_class_install_property (object_class, PROP_ID, + g_param_spec_uint ("id", "Id", + "The internal id of the PipeWire device", 0, G_MAXUINT32, SPA_ID_INVALID, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gst_pipewire_device_init (GstPipeWireDevice * device) +{ +} + +G_DEFINE_TYPE (GstPipeWireDeviceProvider, gst_pipewire_device_provider, + GST_TYPE_DEVICE_PROVIDER); + +enum +{ + PROP_0, + PROP_CLIENT_NAME, + PROP_LAST +}; + +static GstDevice * +new_node (GstPipeWireDeviceProvider *self, const struct pw_node_info *info) +{ + GstCaps *caps = NULL; + GstStructure *props; + const gchar *klass = NULL; + struct spa_dict_item *item; + GstPipeWireDeviceType type; + int i; + + caps = gst_caps_new_empty (); + if (info->max_input_ports > 0 && info->max_output_ports == 0) { + type = GST_PIPEWIRE_DEVICE_TYPE_SINK; + + for (i = 0; i < info->n_input_formats; i++) { + GstCaps *c1 = gst_caps_from_format (info->input_formats[i], self->core->type.map); + if (c1) + gst_caps_append (caps, c1); + } + } + else if (info->max_output_ports > 0 && info->max_input_ports == 0) { + type = GST_PIPEWIRE_DEVICE_TYPE_SOURCE; + for (i = 0; i < info->n_output_formats; i++) { + GstCaps *c1 = gst_caps_from_format (info->output_formats[i], self->core->type.map); + if (c1) + gst_caps_append (caps, c1); + } + } else { + gst_caps_unref(caps); + return NULL; + } + + props = gst_structure_new_empty ("pipewire-proplist"); + if (info->props) { + spa_dict_for_each (item, info->props) + gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL); + + klass = spa_dict_lookup (info->props, "media.class"); + } + if (klass == NULL) + klass = "unknown/unknown"; + + return gst_pipewire_device_new (info->id, + info->name, + caps, + klass, + type, + props); +} + +static GstPipeWireDevice * +find_device (GstDeviceProvider *provider, uint32_t id) +{ + GList *item; + GstPipeWireDevice *dev = NULL; + + GST_OBJECT_LOCK (provider); + for (item = provider->devices; item; item = item->next) { + dev = item->data; + if (dev->id == id) { + gst_object_ref (dev); + break; + } + dev = NULL; + } + GST_OBJECT_UNLOCK (provider); + + return dev; +} + +static void +get_core_info (struct pw_remote *remote, + void *user_data) +{ + GstDeviceProvider *provider = user_data; + struct pw_core_info *info = remote->info; + const gchar *value; + + if (info == NULL || info->props == NULL) + return; + + value = spa_dict_lookup (info->props, "monitors"); + if (value) { + gchar **monitors = g_strsplit (value, ",", -1); + gint i; + + GST_DEBUG_OBJECT (provider, "have hidden providers: %s", value); + + for (i = 0; monitors[i]; i++) { + if (strcmp (monitors[i], "v4l2") == 0) + gst_device_provider_hide_provider (provider, "v4l2deviceprovider"); + else if (strcmp (monitors[i], "alsa") == 0) + gst_device_provider_hide_provider (provider, "pulsedeviceprovider"); + } + g_strfreev (monitors); + } +} + +static void +on_sync_reply (struct pw_listener *listener, struct pw_remote *remote, uint32_t seq) +{ + GstPipeWireDeviceProvider *self = SPA_CONTAINER_OF (listener, GstPipeWireDeviceProvider, on_sync_reply); + if (seq == 1) + pw_core_do_sync(self->registry->remote->core_proxy, 2); + else if (seq == 2) + self->end = true; +} + +static void node_event_info(void *object, struct pw_node_info *info) +{ + struct pw_proxy *proxy = object; + GstPipeWireDeviceProvider *self = proxy->object; + GstDevice *dev; + + dev = new_node (self, info); + if (dev) { + if(self->list_only) + *self->devices = g_list_prepend (*self->devices, gst_object_ref_sink (dev)); + else + gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), dev); + } +} + +static const struct pw_node_events node_events = { + &node_event_info +}; + +static void registry_event_global(void *object, uint32_t id, const char *type, uint32_t version) +{ + struct pw_proxy *registry = object; + GstPipeWireDeviceProvider *self = registry->object; + struct pw_remote *remote = registry->remote; + struct pw_core *core = remote->core; + struct pw_proxy *proxy = NULL; + + if (strcmp(type, PIPEWIRE_TYPE__Node)) + return; + + proxy = pw_proxy_new(remote, SPA_ID_INVALID, core->type.node, 0); + if (proxy == NULL) + goto no_mem; + + pw_proxy_set_implementation(proxy, self, PW_VERSION_NODE, &node_events, NULL); + pw_registry_do_bind(registry, id, version, proxy->id); + return; + +no_mem: + GST_ERROR_OBJECT(self, "failed to create proxy"); + return; +} + +static void registry_event_global_remove(void *object, uint32_t id) +{ + struct pw_proxy *registry = object; + GstPipeWireDeviceProvider *self = registry->object; + GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self); + GstPipeWireDevice *dev; + + dev = find_device (provider, id); + if (dev != NULL) { + gst_device_provider_device_remove (provider, GST_DEVICE (dev)); + gst_object_unref (dev); + } +} + +static const struct pw_registry_events registry_events = { + registry_event_global, + registry_event_global_remove, +}; + +static GList * +gst_pipewire_device_provider_probe (GstDeviceProvider * provider) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); + struct pw_loop *l = NULL; + struct pw_core *c = NULL; + struct pw_remote *r = NULL; + struct pw_proxy *reg = NULL; + + GST_DEBUG_OBJECT (self, "starting probe"); + + if (!(l = pw_loop_new ())) + return NULL; + + if (!(c = pw_core_new (l, NULL))) + return NULL; + + if (!(r = pw_remote_new (c, NULL))) + goto failed; + + pw_signal_add(&r->sync_reply, &self->on_sync_reply, on_sync_reply); + + pw_remote_connect (r); + + for (;;) { + enum pw_remote_state state; + + state = r->state; + + if (state <= 0) { + GST_ERROR_OBJECT (self, "Failed to connect: %s", r->error); + goto failed; + } + + if (state == PW_REMOTE_STATE_CONNECTED) + break; + + /* Wait until something happens */ + pw_loop_iterate (l, -1); + } + GST_DEBUG_OBJECT (self, "connected"); + + get_core_info (r, self); + + self->end = FALSE; + self->list_only = TRUE; + self->devices = NULL; + + reg = pw_proxy_new(r, SPA_ID_INVALID, c->type.registry, 0); + pw_proxy_set_implementation(reg, self, PW_VERSION_REGISTRY, ®istry_events, NULL); + pw_core_do_get_registry(r->core_proxy, reg->id); + pw_core_do_sync(r->core_proxy, 1); + + for (;;) { + if (r->state <= 0) + break; + if (self->end) + break; + pw_loop_iterate (l, -1); + } + + pw_remote_disconnect (r); + pw_remote_destroy (r); + pw_core_destroy (c); + pw_loop_destroy (l); + + return *self->devices; + +failed: + pw_loop_destroy (l); + return NULL; +} + +static void +on_remote_state_changed (struct pw_listener *listener, + struct pw_remote *remote) +{ + GstPipeWireDeviceProvider *self = SPA_CONTAINER_OF (listener, GstPipeWireDeviceProvider, remote_state_changed); + enum pw_remote_state state; + + state= remote->state; + + GST_DEBUG ("got remote state %d", state); + + switch (state) { + case PW_REMOTE_STATE_CONNECTING: + break; + case PW_REMOTE_STATE_UNCONNECTED: + case PW_REMOTE_STATE_CONNECTED: + break; + case PW_REMOTE_STATE_ERROR: + GST_ERROR_OBJECT (self, "remote error: %s", remote->error); + break; + } + pw_thread_loop_signal (self->main_loop, FALSE); +} + +static gboolean +gst_pipewire_device_provider_start (GstDeviceProvider * provider) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); + + GST_DEBUG_OBJECT (self, "starting provider"); + + self->loop = pw_loop_new (); + self->list_only = FALSE; + + if (!(self->main_loop = pw_thread_loop_new (self->loop, "pipewire-device-monitor"))) { + GST_ERROR_OBJECT (self, "Could not create PipeWire mainloop"); + goto failed_main_loop; + } + + if (!(self->core = pw_core_new (self->loop, NULL))) { + GST_ERROR_OBJECT (self, "Could not create PipeWire core"); + goto failed_core; + } + + if (pw_thread_loop_start (self->main_loop) != SPA_RESULT_OK) { + GST_ERROR_OBJECT (self, "Could not start PipeWire mainloop"); + goto failed_start; + } + + pw_thread_loop_lock (self->main_loop); + + if (!(self->remote = pw_remote_new (self->core, NULL))) { + GST_ERROR_OBJECT (self, "Failed to create remote"); + goto failed_remote; + } + + pw_signal_add (&self->remote->state_changed, + &self->remote_state_changed, + on_remote_state_changed); + + pw_remote_connect (self->remote); + for (;;) { + enum pw_remote_state state; + + state = self->remote->state; + + if (state <= 0) { + GST_WARNING_OBJECT (self, "Failed to connect: %s", self->remote->error); + goto not_running; + } + + if (state == PW_REMOTE_STATE_CONNECTED) + break; + + /* Wait until something happens */ + pw_thread_loop_wait (self->main_loop); + } + GST_DEBUG_OBJECT (self, "connected"); + get_core_info (self->remote, self); + + self->registry = pw_proxy_new(self->remote, SPA_ID_INVALID, self->core->type.registry, 0); + pw_proxy_set_implementation(self->registry, self, PW_VERSION_REGISTRY, ®istry_events, NULL); + pw_core_do_get_registry(self->remote->core_proxy, self->registry->id); + pw_core_do_sync(self->remote->core_proxy, 1); + + pw_thread_loop_unlock (self->main_loop); + + return TRUE; + +not_running: + pw_remote_destroy (self->remote); + self->remote = NULL; +failed_remote: + pw_thread_loop_unlock (self->main_loop); +failed_start: + pw_core_destroy (self->core); + self->core = NULL; +failed_core: + pw_thread_loop_destroy (self->main_loop); + self->main_loop = NULL; +failed_main_loop: + pw_loop_destroy (self->loop); + self->loop = NULL; + return TRUE; +} + +static void +gst_pipewire_device_provider_stop (GstDeviceProvider * provider) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); + + if (self->remote) { + pw_remote_disconnect (self->remote); + pw_remote_destroy (self->remote); + self->remote = NULL; + } + if (self->main_loop) { + pw_thread_loop_destroy (self->main_loop); + self->main_loop = NULL; + } + if (self->loop) { + pw_loop_destroy (self->loop); + self->loop = NULL; + } +} + +static void +gst_pipewire_device_provider_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); + + switch (prop_id) { + case PROP_CLIENT_NAME: + g_free (self->client_name); + if (!g_value_get_string (value)) { + GST_WARNING_OBJECT (self, + "Empty PipeWire client name not allowed. " + "Resetting to default value"); + self->client_name = pw_get_client_name (); + } else + self->client_name = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_provider_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); + + switch (prop_id) { + case PROP_CLIENT_NAME: + g_value_set_string (value, self->client_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_device_provider_finalize (GObject * object) +{ + GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); + + g_free (self->client_name); + + G_OBJECT_CLASS (gst_pipewire_device_provider_parent_class)->finalize (object); +} + +static void +gst_pipewire_device_provider_class_init (GstPipeWireDeviceProviderClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass); + gchar *client_name; + + gobject_class->set_property = gst_pipewire_device_provider_set_property; + gobject_class->get_property = gst_pipewire_device_provider_get_property; + gobject_class->finalize = gst_pipewire_device_provider_finalize; + + dm_class->probe = gst_pipewire_device_provider_probe; + dm_class->start = gst_pipewire_device_provider_start; + dm_class->stop = gst_pipewire_device_provider_stop; + + client_name = pw_get_client_name (); + g_object_class_install_property (gobject_class, + PROP_CLIENT_NAME, + g_param_spec_string ("client-name", "Client Name", + "The PipeWire client_name_to_use", client_name, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | + GST_PARAM_MUTABLE_READY)); + g_free (client_name); + + gst_device_provider_class_set_static_metadata (dm_class, + "PipeWire Device Provider", "Sink/Source/Audio/Video", + "List and provide PipeWire source and sink devices", + "Wim Taymans <wim.taymans@gmail.com>"); +} + +static void +gst_pipewire_device_provider_init (GstPipeWireDeviceProvider * self) +{ + self->client_name = pw_get_client_name (); +} diff --git a/src/gst/gstpipewiredeviceprovider.h b/src/gst/gstpipewiredeviceprovider.h new file mode 100644 index 00000000..462d98c3 --- /dev/null +++ b/src/gst/gstpipewiredeviceprovider.h @@ -0,0 +1,105 @@ +/* GStreamer + * Copyright (C) 2012 Olivier Crete <olivier.crete@collabora.com> + * (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * pipewiredeviceprovider.h: Device probing and monitoring + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef __GST_PIPEWIRE_DEVICE_PROVIDER_H__ +#define __GST_PIPEWIRE_DEVICE_PROVIDER_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <pipewire/pipewire.h> + +#include <gst/gst.h> + +G_BEGIN_DECLS + +typedef struct _GstPipeWireDevice GstPipeWireDevice; +typedef struct _GstPipeWireDeviceClass GstPipeWireDeviceClass; + +#define GST_TYPE_PIPEWIRE_DEVICE (gst_pipewire_device_get_type()) +#define GST_IS_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE)) +#define GST_IS_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE)) +#define GST_PIPEWIRE_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDeviceClass)) +#define GST_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDevice)) +#define GST_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstPipeWireDeviceClass)) +#define GST_PIPEWIRE_DEVICE_CAST(obj) ((GstPipeWireDevice *)(obj)) + +typedef enum { + GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN, + GST_PIPEWIRE_DEVICE_TYPE_SOURCE, + GST_PIPEWIRE_DEVICE_TYPE_SINK, +} GstPipeWireDeviceType; + +struct _GstPipeWireDevice { + GstDevice parent; + + GstPipeWireDeviceType type; + uint32_t id; + const gchar *element; +}; + +struct _GstPipeWireDeviceClass { + GstDeviceClass parent_class; +}; + +GType gst_pipewire_device_get_type (void); + +typedef struct _GstPipeWireDeviceProvider GstPipeWireDeviceProvider; +typedef struct _GstPipeWireDeviceProviderClass GstPipeWireDeviceProviderClass; + +#define GST_TYPE_PIPEWIRE_DEVICE_PROVIDER (gst_pipewire_device_provider_get_type()) +#define GST_IS_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER)) +#define GST_IS_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER)) +#define GST_PIPEWIRE_DEVICE_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass)) +#define GST_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProvider)) +#define GST_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass)) +#define GST_PIPEWIRE_DEVICE_PROVIDER_CAST(obj) ((GstPipeWireDeviceProvider *)(obj)) + +struct _GstPipeWireDeviceProvider { + GstDeviceProvider parent; + + gchar *client_name; + + struct pw_loop *loop; + struct pw_thread_loop *main_loop; + + struct pw_core *core; + struct pw_remote *remote; + struct pw_proxy *registry; + gboolean end; + gboolean list_only; + GList **devices; + struct pw_listener remote_state_changed; + struct pw_listener on_sync_reply; +}; + +struct _GstPipeWireDeviceProviderClass { + GstDeviceProviderClass parent_class; +}; + +GType gst_pipewire_device_provider_get_type (void); + +G_END_DECLS + +#endif /* __GST_PIPEWIRE_DEVICE_PROVIDER_H__ */ diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c new file mode 100644 index 00000000..346d35cb --- /dev/null +++ b/src/gst/gstpipewireformat.c @@ -0,0 +1,852 @@ +/* GStreamer + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE +#include <stdio.h> + +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/audio/audio.h> + +#include <spa/format-builder.h> +#include <spa/video/format-utils.h> +#include <spa/audio/format-utils.h> + +#include "gstpipewireformat.h" + +struct media_type { + const char *name; + uint32_t *media_type; + uint32_t *media_subtype; +}; + +static struct { + struct spa_type_map *map; + uint32_t format; + struct spa_type_media_type media_type; + struct spa_type_media_subtype media_subtype; + struct spa_type_media_subtype_video media_subtype_video; + struct spa_type_media_subtype_audio media_subtype_audio; + struct spa_type_format_video format_video; + struct spa_type_format_audio format_audio; + struct spa_type_video_format video_format; + struct spa_type_audio_format audio_format; +} type = { NULL, }; + +static void +ensure_types (struct spa_type_map *map) +{ + type.map = map; + + type.format = spa_type_map_get_id (map, SPA_TYPE__Format); + spa_type_media_type_map (map, &type.media_type); + spa_type_media_subtype_map (map, &type.media_subtype); + spa_type_media_subtype_video_map (map, &type.media_subtype_video); + spa_type_media_subtype_audio_map (map, &type.media_subtype_audio); + spa_type_format_video_map (map, &type.format_video); + spa_type_format_audio_map (map, &type.format_audio); + spa_type_video_format_map (map, &type.video_format); + spa_type_audio_format_map (map, &type.audio_format); +} + +static const struct media_type media_type_map[] = { + { "video/x-raw", &type.media_type.video, &type.media_subtype.raw }, + { "audio/x-raw", &type.media_type.audio, &type.media_subtype.raw }, + { "image/jpeg", &type.media_type.video, &type.media_subtype_video.mjpg }, + { "video/x-h264", &type.media_type.video, &type.media_subtype_video.h264 }, + { NULL, } +}; + +static const uint32_t *video_format_map[] = { + &type.video_format.UNKNOWN, + &type.video_format.ENCODED, + &type.video_format.I420, + &type.video_format.YV12, + &type.video_format.YUY2, + &type.video_format.UYVY, + &type.video_format.AYUV, + &type.video_format.RGBx, + &type.video_format.BGRx, + &type.video_format.xRGB, + &type.video_format.xBGR, + &type.video_format.RGBA, + &type.video_format.BGRA, + &type.video_format.ARGB, + &type.video_format.ABGR, + &type.video_format.RGB, + &type.video_format.BGR, + &type.video_format.Y41B, + &type.video_format.Y42B, + &type.video_format.YVYU, + &type.video_format.Y444, + &type.video_format.v210, + &type.video_format.v216, + &type.video_format.NV12, + &type.video_format.NV21, + &type.video_format.GRAY8, + &type.video_format.GRAY16_BE, + &type.video_format.GRAY16_LE, + &type.video_format.v308, + &type.video_format.RGB16, + &type.video_format.BGR16, + &type.video_format.RGB15, + &type.video_format.BGR15, + &type.video_format.UYVP, + &type.video_format.A420, + &type.video_format.RGB8P, + &type.video_format.YUV9, + &type.video_format.YVU9, + &type.video_format.IYU1, + &type.video_format.ARGB64, + &type.video_format.AYUV64, + &type.video_format.r210, + &type.video_format.I420_10BE, + &type.video_format.I420_10LE, + &type.video_format.I422_10BE, + &type.video_format.I422_10LE, + &type.video_format.Y444_10BE, + &type.video_format.Y444_10LE, + &type.video_format.GBR, + &type.video_format.GBR_10BE, + &type.video_format.GBR_10LE, + &type.video_format.NV16, + &type.video_format.NV24, + &type.video_format.NV12_64Z32, + &type.video_format.A420_10BE, + &type.video_format.A420_10LE, + &type.video_format.A422_10BE, + &type.video_format.A422_10LE, + &type.video_format.A444_10BE, + &type.video_format.A444_10LE, + &type.video_format.NV61, + &type.video_format.P010_10BE, + &type.video_format.P010_10LE, + &type.video_format.IYU2, + &type.video_format.VYUY, + &type.video_format.GBRA, + &type.video_format.GBRA_10BE, + &type.video_format.GBRA_10LE, + &type.video_format.GBR_12BE, + &type.video_format.GBR_12LE, + &type.video_format.GBRA_12BE, + &type.video_format.GBRA_12LE, + &type.video_format.I420_12BE, + &type.video_format.I420_12LE, + &type.video_format.I422_12BE, + &type.video_format.I422_12LE, + &type.video_format.Y444_12BE, + &type.video_format.Y444_12LE, +}; + +#if __BYTE_ORDER == __BIG_ENDIAN +#define _FORMAT_LE(fmt) &type.audio_format. fmt ## _OE +#define _FORMAT_BE(fmt) &type.audio_format. fmt +#elif __BYTE_ORDER == __LITTLE_ENDIAN +#define _FORMAT_LE(fmt) &type.audio_format. fmt +#define _FORMAT_BE(fmt) &type.audio_format. fmt ## _OE +#endif + +static const uint32_t *audio_format_map[] = { + &type.audio_format.UNKNOWN, + &type.audio_format.ENCODED, + &type.audio_format.S8, + &type.audio_format.U8, + _FORMAT_LE (S16), + _FORMAT_BE (S16), + _FORMAT_LE (U16), + _FORMAT_BE (U16), + _FORMAT_LE (S24_32), + _FORMAT_BE (S24_32), + _FORMAT_LE (U24_32), + _FORMAT_BE (U24_32), + _FORMAT_LE (S32), + _FORMAT_BE (S32), + _FORMAT_LE (U32), + _FORMAT_BE (U32), + _FORMAT_LE (S24), + _FORMAT_BE (S24), + _FORMAT_LE (U24), + _FORMAT_BE (U24), + _FORMAT_LE (S20), + _FORMAT_BE (S20), + _FORMAT_LE (U20), + _FORMAT_BE (U20), + _FORMAT_LE (S18), + _FORMAT_BE (S18), + _FORMAT_LE (U18), + _FORMAT_BE (U18), + _FORMAT_LE (F32), + _FORMAT_BE (F32), + _FORMAT_LE (F64), + _FORMAT_BE (F64), +}; + +typedef struct { + struct spa_pod_builder b; + const struct media_type *type; + const GstCapsFeatures *cf; + const GstStructure *cs; +} ConvertData; + +static const struct media_type * +find_media_types (const char *name) +{ + int i; + for (i = 0; media_type_map[i].name; i++) { + if (!strcmp (media_type_map[i].name, name)) + return &media_type_map[i]; + } + return NULL; +} + +static const char * +get_nth_string (const GValue *val, int idx) +{ + const GValue *v = NULL; + GType type = G_VALUE_TYPE (val); + + if (type == G_TYPE_STRING && idx == 0) + v = val; + else if (type == GST_TYPE_LIST) { + GArray *array = g_value_peek_pointer (val); + if (idx < array->len + 1) { + v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0)); + } + } + if (v) + return g_value_get_string (v); + + return NULL; +} + +static bool +get_nth_int (const GValue *val, int idx, int *res) +{ + const GValue *v = NULL; + GType type = G_VALUE_TYPE (val); + + if (type == G_TYPE_INT && idx == 0) { + v = val; + } else if (type == GST_TYPE_INT_RANGE) { + if (idx == 0 || idx == 1) { + *res = gst_value_get_int_range_min (val); + return true; + } else if (idx == 2) { + *res = gst_value_get_int_range_max (val); + return true; + } + } else if (type == GST_TYPE_LIST) { + GArray *array = g_value_peek_pointer (val); + if (idx < array->len + 1) { + v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0)); + } + } + if (v) { + *res = g_value_get_int (v); + return true; + } + return false; +} + +static gboolean +get_nth_fraction (const GValue *val, int idx, struct spa_fraction *f) +{ + const GValue *v = NULL; + GType type = G_VALUE_TYPE (val); + + if (type == GST_TYPE_FRACTION && idx == 0) { + v = val; + } else if (type == GST_TYPE_FRACTION_RANGE) { + if (idx == 0 || idx == 1) { + v = gst_value_get_fraction_range_min (val); + } else if (idx == 2) { + v = gst_value_get_fraction_range_max (val); + } + } else if (type == GST_TYPE_LIST) { + GArray *array = g_value_peek_pointer (val); + if (idx < array->len + 1) { + v = &g_array_index (array, GValue, SPA_MAX (idx-1, 0)); + } + } + if (v) { + f->num = gst_value_get_fraction_numerator (v); + f->denom = gst_value_get_fraction_denominator (v); + return true; + } + return false; +} + +static gboolean +get_nth_rectangle (const GValue *width, const GValue *height, int idx, struct spa_rectangle *r) +{ + const GValue *w = NULL, *h = NULL; + GType wt = G_VALUE_TYPE (width); + GType ht = G_VALUE_TYPE (height); + + if (wt == G_TYPE_INT && ht == G_TYPE_INT && idx == 0) { + w = width; + h = height; + } else if (wt == GST_TYPE_INT_RANGE && ht == GST_TYPE_INT_RANGE) { + if (idx == 0 || idx == 1) { + r->width = gst_value_get_int_range_min (width); + r->height = gst_value_get_int_range_min (height); + return true; + } else if (idx == 2) { + r->width = gst_value_get_int_range_max (width); + r->height = gst_value_get_int_range_max (height); + return true; + } + } else if (wt == GST_TYPE_LIST && ht == GST_TYPE_LIST) { + GArray *wa = g_value_peek_pointer (width); + GArray *ha = g_value_peek_pointer (height); + if (idx < wa->len + 1) + w = &g_array_index (wa, GValue, SPA_MAX (idx-1, 0)); + if (idx < ha->len + 1) + h = &g_array_index (ha, GValue, SPA_MAX (idx-1, 0)); + } + if (w && h) { + r->width = g_value_get_int (w); + r->height = g_value_get_int (h); + return true; + } + return false; +} + +static const uint32_t +get_range_type (const GValue *val) +{ + GType type = G_VALUE_TYPE (val); + + if (type == GST_TYPE_LIST) + return SPA_POD_PROP_RANGE_ENUM; + if (type == GST_TYPE_DOUBLE_RANGE || type == GST_TYPE_FRACTION_RANGE) + return SPA_POD_PROP_RANGE_MIN_MAX; + if (type == GST_TYPE_INT_RANGE) { + if (gst_value_get_int_range_step (val) == 1) + return SPA_POD_PROP_RANGE_MIN_MAX; + else + return SPA_POD_PROP_RANGE_STEP; + } + if (type == GST_TYPE_INT64_RANGE) { + if (gst_value_get_int64_range_step (val) == 1) + return SPA_POD_PROP_RANGE_MIN_MAX; + else + return SPA_POD_PROP_RANGE_STEP; + } + return SPA_POD_PROP_RANGE_NONE; +} + +static const uint32_t +get_range_type2 (const GValue *v1, const GValue *v2) +{ + uint32_t r1, r2; + + r1 = get_range_type (v1); + r2 = get_range_type (v2); + + if (r1 == r2) + return r1; + if (r1 == SPA_POD_PROP_RANGE_STEP || r2 == SPA_POD_PROP_RANGE_STEP) + return SPA_POD_PROP_RANGE_STEP; + if (r1 == SPA_POD_PROP_RANGE_MIN_MAX || r2 == SPA_POD_PROP_RANGE_MIN_MAX) + return SPA_POD_PROP_RANGE_MIN_MAX; + return SPA_POD_PROP_RANGE_MIN_MAX; +} + +static gboolean +handle_video_fields (ConvertData *d) +{ + struct spa_pod_frame f; + const GValue *value, *value2; + int i; + + value = gst_structure_get_value (d->cs, "format"); + if (value) { + const char *v; + int idx; + for (i = 0; (v = get_nth_string (value, i)); i++) { + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_video.format, + get_range_type (value)); + + idx = gst_video_format_from_string (v); + if (idx < SPA_N_ELEMENTS (video_format_map)) + spa_pod_builder_id (&d->b, *video_format_map[idx]); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + value = gst_structure_get_value (d->cs, "width"); + value2 = gst_structure_get_value (d->cs, "height"); + if (value || value2) { + struct spa_rectangle v; + for (i = 0; get_nth_rectangle (value, value2, i, &v); i++) { + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_video.size, + get_range_type2 (value, value2)); + + spa_pod_builder_rectangle (&d->b, v.width, v.height); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + + value = gst_structure_get_value (d->cs, "framerate"); + if (value) { + struct spa_fraction v; + for (i = 0; get_nth_fraction (value, i, &v); i++) { + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_video.framerate, + get_range_type (value)); + + spa_pod_builder_fraction (&d->b, v.num, v.denom); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + + value = gst_structure_get_value (d->cs, "max-framerate"); + if (value) { + struct spa_fraction v; + for (i = 0; get_nth_fraction (value, i, &v); i++) { + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_video.max_framerate, + get_range_type (value)); + + spa_pod_builder_fraction (&d->b, v.num, v.denom); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + return TRUE; +} + +static gboolean +handle_audio_fields (ConvertData *d) +{ + struct spa_pod_frame f; + const GValue *value; + int i = 0; + + value = gst_structure_get_value (d->cs, "format"); + if (value) { + const char *v; + int idx; + for (i = 0; (v = get_nth_string (value, i)); i++) { + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_audio.format, + get_range_type (value)); + + idx = gst_audio_format_from_string (v); + if (idx < SPA_N_ELEMENTS (audio_format_map)) + spa_pod_builder_id (&d->b, *audio_format_map[idx]); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + + value = gst_structure_get_value (d->cs, "layout"); + if (value) { + const char *v; + for (i = 0; (v = get_nth_string (value, i)); i++) { + enum spa_audio_layout layout; + + if (!strcmp (v, "interleaved")) + layout = SPA_AUDIO_LAYOUT_INTERLEAVED; + else if (!strcmp (v, "non-interleaved")) + layout = SPA_AUDIO_LAYOUT_NON_INTERLEAVED; + else + break; + + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_audio.layout, + get_range_type (value)); + + spa_pod_builder_int (&d->b, layout); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + value = gst_structure_get_value (d->cs, "rate"); + if (value) { + int v; + for (i = 0; get_nth_int (value, i, &v); i++) { + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_audio.rate, + get_range_type (value)); + + spa_pod_builder_int (&d->b, v); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + value = gst_structure_get_value (d->cs, "channels"); + if (value) { + int v; + for (i = 0; get_nth_int (value, i, &v); i++) { + if (i == 0) + spa_pod_builder_push_prop (&d->b, &f, + type.format_audio.channels, + get_range_type (value)); + + spa_pod_builder_int (&d->b, v); + } + if (i > 1) + SPA_POD_BUILDER_DEREF (&d->b, f.ref, struct spa_pod_prop)->body.flags |= SPA_POD_PROP_FLAG_UNSET; + spa_pod_builder_pop (&d->b, &f); + } + return TRUE; +} + +static uint32_t +write_pod (struct spa_pod_builder *b, uint32_t ref, const void *data, uint32_t size) +{ + if (ref == -1) + ref = b->offset; + + if (b->size <= b->offset) { + b->size = SPA_ROUND_UP_N (b->offset + size, 512); + b->data = realloc (b->data, b->size); + } + memcpy (b->data + ref, data, size); + return ref; +} + +static struct spa_format * +convert_1 (GstCapsFeatures *cf, GstStructure *cs) +{ + ConvertData d; + struct spa_pod_frame f; + + spa_zero (d); + d.cf = cf; + d.cs = cs; + + if (!(d.type = find_media_types (gst_structure_get_name (cs)))) + return NULL; + + d.b.write = write_pod; + + spa_pod_builder_push_format (&d.b, &f, type.format, + *d.type->media_type, + *d.type->media_subtype); + + if (*d.type->media_type == type.media_type.video) + handle_video_fields (&d); + else if (*d.type->media_type == type.media_type.audio) + handle_audio_fields (&d); + + spa_pod_builder_pop (&d.b, &f); + + return SPA_MEMBER (d.b.data, 0, struct spa_format); +} + +struct spa_format * +gst_caps_to_format (GstCaps *caps, guint index, struct spa_type_map *map) +{ + GstCapsFeatures *f; + GstStructure *s; + struct spa_format *res; + + g_return_val_if_fail (GST_IS_CAPS (caps), NULL); + g_return_val_if_fail (gst_caps_is_fixed (caps), NULL); + + ensure_types(map); + + f = gst_caps_get_features (caps, index); + s = gst_caps_get_structure (caps, index); + + res = convert_1 (f, s); + + return res; +} + +static gboolean +foreach_func (GstCapsFeatures *features, + GstStructure *structure, + GPtrArray *array) +{ + struct spa_format *fmt; + + if ((fmt = convert_1 (features, structure))) + g_ptr_array_insert (array, -1, fmt); + + return TRUE; +} + + +GPtrArray * +gst_caps_to_format_all (GstCaps *caps, struct spa_type_map *map) +{ + GPtrArray *res; + + ensure_types(map); + + res = g_ptr_array_new_full (gst_caps_get_size (caps), (GDestroyNotify)g_free); + gst_caps_foreach (caps, (GstCapsForeachFunc) foreach_func, res); + + return res; +} + +static void +handle_id_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res) +{ + const char * str; + uint32_t *id = SPA_POD_CONTENTS (struct spa_pod_prop, prop); + uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop); + uint32_t flags; + + flags = prop->body.flags; + if (!(flags & SPA_POD_PROP_FLAG_UNSET)) + flags &= ~SPA_POD_PROP_RANGE_MASK; + + switch (flags & SPA_POD_PROP_RANGE_MASK) { + case SPA_POD_PROP_RANGE_NONE: + if (!(str = spa_type_map_get_type (type.map, id[0]))) + return; + gst_caps_set_simple (res, key, G_TYPE_STRING, rindex (str, ':') + 1, NULL); + break; + case SPA_POD_PROP_RANGE_ENUM: + { + GValue list = { 0 }, v = { 0 }; + + g_value_init (&list, GST_TYPE_LIST); + for (i = 1; i < n_items; i++) { + if (!(str = spa_type_map_get_type (type.map, id[i]))) + continue; + + g_value_init (&v, G_TYPE_STRING); + g_value_set_string (&v, rindex (str, ':') + 1); + gst_value_list_append_and_take_value (&list, &v); + } + gst_caps_set_value (res, key, &list); + g_value_unset (&list); + break; + } + default: + break; + } +} + +static void +handle_int_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res) +{ + uint32_t *val = SPA_POD_CONTENTS (struct spa_pod_prop, prop); + uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop); + uint32_t flags; + + flags = prop->body.flags; + if (!(flags & SPA_POD_PROP_FLAG_UNSET)) + flags &= ~SPA_POD_PROP_RANGE_MASK; + + switch (flags & SPA_POD_PROP_RANGE_MASK) { + case SPA_POD_PROP_RANGE_NONE: + gst_caps_set_simple (res, key, G_TYPE_INT, val[0], NULL); + break; + case SPA_POD_PROP_RANGE_MIN_MAX: + case SPA_POD_PROP_RANGE_STEP: + { + if (n_items < 3) + return; + gst_caps_set_simple (res, key, GST_TYPE_INT_RANGE, val[1], val[2], NULL); + break; + } + case SPA_POD_PROP_RANGE_ENUM: + { + GValue list = { 0 }, v = { 0 }; + + g_value_init (&list, GST_TYPE_LIST); + for (i = 1; i < n_items; i++) { + g_value_init (&v, G_TYPE_INT); + g_value_set_int (&v, val[i]); + gst_value_list_append_and_take_value (&list, &v); + } + gst_caps_set_value (res, key, &list); + g_value_unset (&list); + break; + } + default: + break; + } +} + +static void +handle_rect_prop (struct spa_pod_prop *prop, const char *width, const char *height, GstCaps *res) +{ + struct spa_rectangle *rect = SPA_POD_CONTENTS (struct spa_pod_prop, prop); + uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop); + uint32_t flags; + + flags = prop->body.flags; + if (!(flags & SPA_POD_PROP_FLAG_UNSET)) + flags &= ~SPA_POD_PROP_RANGE_MASK; + + switch (flags & SPA_POD_PROP_RANGE_MASK) { + case SPA_POD_PROP_RANGE_NONE: + gst_caps_set_simple (res, width, G_TYPE_INT, rect[0].width, + height, G_TYPE_INT, rect[0].height, NULL); + break; + case SPA_POD_PROP_RANGE_MIN_MAX: + case SPA_POD_PROP_RANGE_STEP: + { + if (n_items < 3) + return; + gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width, + height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL); + break; + } + case SPA_POD_PROP_RANGE_ENUM: + { + GValue l1 = { 0 }, l2 = { 0 }, v1 = { 0 }, v2 = { 0 }; + + g_value_init (&l1, GST_TYPE_LIST); + g_value_init (&l2, GST_TYPE_LIST); + for (i = 1; i < n_items; i++) { + g_value_init (&v1, G_TYPE_INT); + g_value_set_int (&v1, rect[i].width); + gst_value_list_append_and_take_value (&l1, &v1); + + g_value_init (&v2, G_TYPE_INT); + g_value_set_int (&v2, rect[i].height); + gst_value_list_append_and_take_value (&l2, &v2); + } + gst_caps_set_value (res, width, &l1); + gst_caps_set_value (res, height, &l2); + g_value_unset (&l1); + g_value_unset (&l2); + break; + } + default: + break; + } +} + +static void +handle_fraction_prop (struct spa_pod_prop *prop, const char *key, GstCaps *res) +{ + struct spa_fraction *fract = SPA_POD_CONTENTS (struct spa_pod_prop, prop); + uint32_t i, n_items = SPA_POD_PROP_N_VALUES (prop); + uint32_t flags; + + flags = prop->body.flags; + if (!(flags & SPA_POD_PROP_FLAG_UNSET)) + flags &= ~SPA_POD_PROP_RANGE_MASK; + + switch (flags & SPA_POD_PROP_RANGE_MASK) { + case SPA_POD_PROP_RANGE_NONE: + gst_caps_set_simple (res, key, GST_TYPE_FRACTION, fract[0].num, fract[0].denom, NULL); + break; + case SPA_POD_PROP_RANGE_MIN_MAX: + case SPA_POD_PROP_RANGE_STEP: + { + if (n_items < 3) + return; + gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom, + fract[2].num, fract[2].denom, NULL); + break; + } + case SPA_POD_PROP_RANGE_ENUM: + { + GValue l1 = { 0 }, v1 = { 0 }; + + g_value_init (&l1, GST_TYPE_LIST); + for (i = 1; i < n_items; i++) { + g_value_init (&v1, GST_TYPE_FRACTION); + gst_value_set_fraction (&v1, fract[i].num, fract[i].denom); + gst_value_list_append_and_take_value (&l1, &v1); + } + gst_caps_set_value (res, key, &l1); + g_value_unset (&l1); + break; + } + default: + break; + } +} +GstCaps * +gst_caps_from_format (const struct spa_format *format, struct spa_type_map *map) +{ + GstCaps *res = NULL; + uint32_t media_type, media_subtype; + struct spa_pod_prop *prop; + + ensure_types(map); + + media_type = SPA_FORMAT_MEDIA_TYPE (format); + media_subtype = SPA_FORMAT_MEDIA_SUBTYPE (format); + + if (media_type == type.media_type.video) { + if (media_subtype == type.media_subtype.raw) { + res = gst_caps_new_empty_simple ("video/x-raw"); + if ((prop = spa_format_find_prop (format, type.format_video.format))) { + handle_id_prop (prop, "format", res); + } + } + else if (media_subtype == type.media_subtype_video.mjpg) { + res = gst_caps_new_empty_simple ("image/jpeg"); + } + else if (media_subtype == type.media_subtype_video.h264) { + res = gst_caps_new_simple ("video/x-h264", + "stream-format", G_TYPE_STRING, "byte-stream", + "alignment", G_TYPE_STRING, "au", + NULL); + } + if ((prop = spa_format_find_prop (format, type.format_video.size))) { + handle_rect_prop (prop, "width", "height", res); + } + if ((prop = spa_format_find_prop (format, type.format_video.framerate))) { + handle_fraction_prop (prop, "framerate", res); + } + if ((prop = spa_format_find_prop (format, type.format_video.max_framerate))) { + handle_fraction_prop (prop, "max-framerate", res); + } + } else if (media_type == type.media_type.audio) { + if (media_subtype == type.media_subtype.raw) { + res = gst_caps_new_simple ("audio/x-raw", + "layout", G_TYPE_STRING, "interleaved", + NULL); + if ((prop = spa_format_find_prop (format, type.format_audio.format))) { + handle_id_prop (prop, "format", res); + } + if ((prop = spa_format_find_prop (format, type.format_audio.rate))) { + handle_int_prop (prop, "rate", res); + } + if ((prop = spa_format_find_prop (format, type.format_audio.channels))) { + handle_int_prop (prop, "channels", res); + } + } + else if (media_subtype == type.media_subtype_audio.aac) { + } + } + return res; +} diff --git a/src/gst/gstpipewireformat.h b/src/gst/gstpipewireformat.h new file mode 100644 index 00000000..e5cc0776 --- /dev/null +++ b/src/gst/gstpipewireformat.h @@ -0,0 +1,37 @@ +/* GStreamer + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _GST_PIPEWIRE_FORMAT_H_ +#define _GST_PIPEWIRE_FORMAT_H_ + +#include <gst/gst.h> + +#include <spa/type-map.h> +#include <spa/format.h> + +G_BEGIN_DECLS + +struct spa_format * gst_caps_to_format (GstCaps *caps, guint index, struct spa_type_map *map); +GPtrArray * gst_caps_to_format_all (GstCaps *caps, struct spa_type_map *map); + +GstCaps * gst_caps_from_format (const struct spa_format *format, struct spa_type_map *map); + +G_END_DECLS + +#endif diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c new file mode 100644 index 00000000..eb36f957 --- /dev/null +++ b/src/gst/gstpipewirepool.c @@ -0,0 +1,178 @@ +/* GStreamer + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Suite 500, + * Boston, MA 02110-1335, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> + +#include "gstpipewirepool.h" + +GST_DEBUG_CATEGORY_STATIC (gst_pipewire_pool_debug_category); +#define GST_CAT_DEFAULT gst_pipewire_pool_debug_category + +G_DEFINE_TYPE (GstPipeWirePool, gst_pipewire_pool, GST_TYPE_BUFFER_POOL); + +enum +{ + ACTIVATED, + /* FILL ME */ + LAST_SIGNAL +}; + + +static guint pool_signals[LAST_SIGNAL] = { 0 }; + +GstPipeWirePool * +gst_pipewire_pool_new (void) +{ + GstPipeWirePool *pool; + + pool = g_object_new (GST_TYPE_PIPEWIRE_POOL, NULL); + + return pool; +} + +gboolean +gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer) +{ + g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE); + g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); + + GST_OBJECT_LOCK (pool); + g_queue_push_tail (&pool->available, buffer); + g_cond_signal (&pool->cond); + GST_OBJECT_UNLOCK (pool); + + return TRUE; +} + +gboolean +gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer) +{ + gboolean res; + + g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE); + g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); + + GST_OBJECT_LOCK (pool); + res = g_queue_remove (&pool->available, buffer); + GST_OBJECT_UNLOCK (pool); + + return res; +} + +static GstFlowReturn +acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer, + GstBufferPoolAcquireParams * params) +{ + GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); + + GST_OBJECT_LOCK (pool); + while (TRUE) { + if (G_UNLIKELY (GST_BUFFER_POOL_IS_FLUSHING (pool))) + goto flushing; + + if (p->available.length > 0) + break; + + GST_WARNING ("queue empty"); + g_cond_wait (&p->cond, GST_OBJECT_GET_LOCK (pool)); + } + *buffer = g_queue_pop_head (&p->available); + GST_OBJECT_UNLOCK (pool); + GST_DEBUG ("acquire buffer %p", *buffer); + + return GST_FLOW_OK; + +flushing: + { + GST_OBJECT_UNLOCK (pool); + return GST_FLOW_FLUSHING; + } +} + +static void +flush_start (GstBufferPool * pool) +{ + GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); + + GST_DEBUG ("flush start"); + GST_OBJECT_LOCK (pool); + g_cond_signal (&p->cond); + GST_OBJECT_UNLOCK (pool); +} + +static void +release_buffer (GstBufferPool * pool, GstBuffer *buffer) +{ + GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); + + GST_DEBUG ("release buffer %p", buffer); + GST_OBJECT_LOCK (pool); + g_queue_push_tail (&p->available, buffer); + g_cond_signal (&p->cond); + GST_OBJECT_UNLOCK (pool); +} + +static gboolean +do_start (GstBufferPool * pool) +{ + g_signal_emit (pool, pool_signals[ACTIVATED], 0, NULL); + return TRUE; +} + +static void +gst_pipewire_pool_finalize (GObject * object) +{ + GstPipeWirePool *pool = GST_PIPEWIRE_POOL (object); + + GST_DEBUG_OBJECT (pool, "finalize"); + + G_OBJECT_CLASS (gst_pipewire_pool_parent_class)->finalize (object); +} + +static void +gst_pipewire_pool_class_init (GstPipeWirePoolClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBufferPoolClass *bufferpool_class = GST_BUFFER_POOL_CLASS (klass); + + gobject_class->finalize = gst_pipewire_pool_finalize; + + bufferpool_class->start = do_start; + bufferpool_class->flush_start = flush_start; + bufferpool_class->acquire_buffer = acquire_buffer; + bufferpool_class->release_buffer = release_buffer; + + pool_signals[ACTIVATED] = + g_signal_new ("activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); + + GST_DEBUG_CATEGORY_INIT (gst_pipewire_pool_debug_category, "pipewirepool", 0, + "debug category for pipewirepool object"); +} + +static void +gst_pipewire_pool_init (GstPipeWirePool * pool) +{ + g_cond_init (&pool->cond); + g_queue_init (&pool->available); +} diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h new file mode 100644 index 00000000..80188f71 --- /dev/null +++ b/src/gst/gstpipewirepool.h @@ -0,0 +1,66 @@ +/* GStreamer + * Copyright (C) <2016> Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_PIPEWIRE_POOL_H__ +#define __GST_PIPEWIRE_POOL_H__ + +#include <gst/gst.h> + +#include <pipewire/pipewire.h> + +G_BEGIN_DECLS + +#define GST_TYPE_PIPEWIRE_POOL \ + (gst_pipewire_pool_get_type()) +#define GST_PIPEWIRE_POOL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePool)) +#define GST_PIPEWIRE_POOL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePoolClass)) +#define GST_IS_PIPEWIRE_POOL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_POOL)) +#define GST_IS_PIPEWIRE_POOL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_POOL)) +#define GST_PIPEWIRE_POOL_GET_CLASS(klass) \ + (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_POOL, GstPipeWirePoolClass)) + +typedef struct _GstPipeWirePool GstPipeWirePool; +typedef struct _GstPipeWirePoolClass GstPipeWirePoolClass; + +struct _GstPipeWirePool { + GstBufferPool parent; + + struct pw_stream *stream; + GQueue available; + GCond cond; +}; + +struct _GstPipeWirePoolClass { + GstBufferPoolClass parent_class; +}; + +GType gst_pipewire_pool_get_type (void); + +GstPipeWirePool * gst_pipewire_pool_new (void); + +gboolean gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer); +gboolean gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer); + +G_END_DECLS + +#endif /* __GST_PIPEWIRE_POOL_H__ */ diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c new file mode 100644 index 00000000..2d61fe8d --- /dev/null +++ b/src/gst/gstpipewiresink.c @@ -0,0 +1,941 @@ +/* GStreamer + * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-pipewiresink + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch -v videotestsrc ! pipewiresink + * ]| Sends a test video source to PipeWire + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gstpipewiresink.h" + +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <gio/gunixfdmessage.h> +#include <gst/allocators/gstfdmemory.h> +#include <gst/video/video.h> + +#include <spa/buffer.h> + +#include "gstpipewireformat.h" + +static GQuark process_mem_data_quark; + +GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug); +#define GST_CAT_DEFAULT pipewire_sink_debug + +#define DEFAULT_PROP_MODE GST_PIPEWIRE_SINK_MODE_DEFAULT + +enum +{ + PROP_0, + PROP_PATH, + PROP_CLIENT_NAME, + PROP_STREAM_PROPERTIES, + PROP_MODE +}; + +GType +gst_pipewire_sink_mode_get_type (void) +{ + static volatile gsize mode_type = 0; + static const GEnumValue mode[] = { + {GST_PIPEWIRE_SINK_MODE_DEFAULT, "GST_PIPEWIRE_SINK_MODE_DEFAULT", "default"}, + {GST_PIPEWIRE_SINK_MODE_RENDER, "GST_PIPEWIRE_SINK_MODE_RENDER", "render"}, + {GST_PIPEWIRE_SINK_MODE_PROVIDE, "GST_PIPEWIRE_SINK_MODE_PROVIDE", "provide"}, + {0, NULL, NULL}, + }; + + if (g_once_init_enter (&mode_type)) { + GType tmp = + g_enum_register_static ("GstPipeWireSinkMode", mode); + g_once_init_leave (&mode_type, tmp); + } + + return (GType) mode_type; +} + + +static GstStaticPadTemplate gst_pipewire_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY + ); + +#define gst_pipewire_sink_parent_class parent_class +G_DEFINE_TYPE (GstPipeWireSink, gst_pipewire_sink, GST_TYPE_BASE_SINK); + +static void gst_pipewire_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_pipewire_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static GstStateChangeReturn +gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition); + +static gboolean gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps); +static GstCaps *gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, + GstCaps * caps); + +static GstFlowReturn gst_pipewire_sink_render (GstBaseSink * psink, + GstBuffer * buffer); +static gboolean gst_pipewire_sink_start (GstBaseSink * basesink); +static gboolean gst_pipewire_sink_stop (GstBaseSink * basesink); + +static void +gst_pipewire_sink_finalize (GObject * object) +{ + GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object); + + g_object_unref (pwsink->pool); + + pw_thread_loop_destroy (pwsink->main_loop); + pwsink->main_loop = NULL; + + pw_loop_destroy (pwsink->loop); + pwsink->loop = NULL; + + if (pwsink->properties) + gst_structure_free (pwsink->properties); + g_object_unref (pwsink->allocator); + g_free (pwsink->path); + g_free (pwsink->client_name); + g_hash_table_unref (pwsink->buf_ids); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean +gst_pipewire_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) +{ + GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (bsink); + + gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->pool), 0, 0, 0); + return TRUE; +} + +static void +gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + + gobject_class->finalize = gst_pipewire_sink_finalize; + gobject_class->set_property = gst_pipewire_sink_set_property; + gobject_class->get_property = gst_pipewire_sink_get_property; + + g_object_class_install_property (gobject_class, + PROP_PATH, + g_param_spec_string ("path", + "Path", + "The sink path to connect to (NULL = default)", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_CLIENT_NAME, + g_param_spec_string ("client-name", + "Client Name", + "The client name to use (NULL = default)", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_STREAM_PROPERTIES, + g_param_spec_boxed ("stream-properties", + "Stream properties", + "List of PipeWire stream properties", + GST_TYPE_STRUCTURE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_MODE, + g_param_spec_enum ("mode", + "Mode", + "The mode to operate in", + GST_TYPE_PIPEWIRE_SINK_MODE, + DEFAULT_PROP_MODE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + gstelement_class->change_state = gst_pipewire_sink_change_state; + + gst_element_class_set_static_metadata (gstelement_class, + "PipeWire sink", "Sink/Video", + "Send video to PipeWire", "Wim Taymans <wim.taymans@gmail.com>"); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_pipewire_sink_template)); + + gstbasesink_class->set_caps = gst_pipewire_sink_setcaps; + gstbasesink_class->fixate = gst_pipewire_sink_sink_fixate; + gstbasesink_class->propose_allocation = gst_pipewire_sink_propose_allocation; + gstbasesink_class->start = gst_pipewire_sink_start; + gstbasesink_class->stop = gst_pipewire_sink_stop; + gstbasesink_class->render = gst_pipewire_sink_render; + + GST_DEBUG_CATEGORY_INIT (pipewire_sink_debug, "pipewiresink", 0, + "PipeWire Sink"); + + process_mem_data_quark = g_quark_from_static_string ("GstPipeWireSinkProcessMemQuark"); +} + + +#define PROP(f,key,type,...) \ + SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__) +#define PROP_R(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_READONLY,type,1,__VA_ARGS__) +#define PROP_MM(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__) +#define PROP_U_MM(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \ + SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__) +#define PROP_EN(f,key,type,n,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_RANGE_ENUM,type,n,__VA_ARGS__) +#define PROP_U_EN(f,key,type,n,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \ + SPA_POD_PROP_RANGE_ENUM,type,n,__VA_ARGS__) +static void +pool_activated (GstPipeWirePool *pool, GstPipeWireSink *sink) +{ + struct pw_remote *remote = sink->stream->remote; + struct pw_core *core = remote->core; + GstStructure *config; + GstCaps *caps; + guint size; + guint min_buffers; + guint max_buffers; + struct spa_param *port_params[3]; + struct spa_pod_builder b = { NULL }; + uint8_t buffer[1024]; + struct spa_pod_frame f[2]; + + config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool)); + gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers); + + spa_pod_builder_init (&b, buffer, sizeof (buffer)); + spa_pod_builder_push_object (&b, &f[0], 0, core->type.param_alloc_buffers.Buffers); + if (size == 0) + spa_pod_builder_add (&b, + PROP_U_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, 0, 0, INT32_MAX), 0); + else + spa_pod_builder_add (&b, + PROP_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, size, size, INT32_MAX), 0); + + spa_pod_builder_add (&b, + PROP_MM (&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, 0, 0, INT32_MAX), + PROP_U_MM (&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, min_buffers, min_buffers, max_buffers ? max_buffers : INT32_MAX), + PROP (&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, 16), + 0); + spa_pod_builder_pop (&b, &f[0]); + port_params[0] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param); + + spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable, + PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Header), + PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_header))); + port_params[1] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param); + + spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable, + PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Ringbuffer), + PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_ringbuffer)), + PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferSize, SPA_POD_TYPE_INT, + size * SPA_MAX (4, + SPA_MAX (min_buffers, max_buffers))), + PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferStride, SPA_POD_TYPE_INT, 0), + PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferBlocks, SPA_POD_TYPE_INT, 1), + PROP (&f[1], core->type.param_alloc_meta_enable.ringbufferAlign, SPA_POD_TYPE_INT, 16)); + port_params[2] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param); + + pw_thread_loop_lock (sink->main_loop); + pw_stream_finish_format (sink->stream, SPA_RESULT_OK, port_params, 2); + pw_thread_loop_unlock (sink->main_loop); +} + +static void +gst_pipewire_sink_init (GstPipeWireSink * sink) +{ + sink->allocator = gst_fd_allocator_new (); + sink->pool = gst_pipewire_pool_new (); + sink->client_name = pw_get_client_name(); + sink->mode = DEFAULT_PROP_MODE; + + g_signal_connect (sink->pool, "activated", G_CALLBACK (pool_activated), sink); + + sink->buf_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify) gst_buffer_unref); + + g_queue_init (&sink->queue); + + sink->loop = pw_loop_new (); + sink->main_loop = pw_thread_loop_new (sink->loop, "pipewire-sink-loop"); + sink->core = pw_core_new (sink->loop, NULL); + GST_DEBUG ("loop %p %p", sink->loop, sink->main_loop); +} + +static GstCaps * +gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps) +{ + GstStructure *structure; + + caps = gst_caps_make_writable (caps); + + structure = gst_caps_get_structure (caps, 0); + + if (gst_structure_has_name (structure, "video/x-raw")) { + gst_structure_fixate_field_nearest_int (structure, "width", 320); + gst_structure_fixate_field_nearest_int (structure, "height", 240); + gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); + + if (gst_structure_has_field (structure, "pixel-aspect-ratio")) + gst_structure_fixate_field_nearest_fraction (structure, + "pixel-aspect-ratio", 1, 1); + else + gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, + NULL); + + if (gst_structure_has_field (structure, "colorimetry")) + gst_structure_fixate_field_string (structure, "colorimetry", "bt601"); + if (gst_structure_has_field (structure, "chroma-site")) + gst_structure_fixate_field_string (structure, "chroma-site", "mpeg2"); + + if (gst_structure_has_field (structure, "interlace-mode")) + gst_structure_fixate_field_string (structure, "interlace-mode", + "progressive"); + else + gst_structure_set (structure, "interlace-mode", G_TYPE_STRING, + "progressive", NULL); + } else if (gst_structure_has_name (structure, "audio/x-raw")) { + gst_structure_fixate_field_string (structure, "format", "S16LE"); + gst_structure_fixate_field_nearest_int (structure, "channels", 2); + gst_structure_fixate_field_nearest_int (structure, "rate", 44100); + } + + caps = GST_BASE_SINK_CLASS (parent_class)->fixate (bsink, caps); + + return caps; +} + +static void +gst_pipewire_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object); + + switch (prop_id) { + case PROP_PATH: + g_free (pwsink->path); + pwsink->path = g_value_dup_string (value); + break; + + case PROP_CLIENT_NAME: + g_free (pwsink->client_name); + pwsink->client_name = g_value_dup_string (value); + break; + + case PROP_STREAM_PROPERTIES: + if (pwsink->properties) + gst_structure_free (pwsink->properties); + pwsink->properties = + gst_structure_copy (gst_value_get_structure (value)); + break; + + case PROP_MODE: + pwsink->mode = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string (value, pwsink->path); + break; + + case PROP_CLIENT_NAME: + g_value_set_string (value, pwsink->client_name); + break; + + case PROP_STREAM_PROPERTIES: + gst_value_set_structure (value, pwsink->properties); + break; + + case PROP_MODE: + g_value_set_enum (value, pwsink->mode); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +typedef struct { + GstPipeWireSink *sink; + guint id; + struct spa_buffer *buf; + struct spa_meta_header *header; + guint flags; + goffset offset; +} ProcessMemData; + +static void +process_mem_data_destroy (gpointer user_data) +{ + ProcessMemData *data = user_data; + + gst_object_unref (data->sink); + g_slice_free (ProcessMemData, data); +} + +static void +on_add_buffer (struct pw_listener *listener, + struct pw_stream *stream, + uint32_t id) +{ + GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_add_buffer); + struct spa_buffer *b; + GstBuffer *buf; + uint32_t i; + ProcessMemData data; + struct pw_core *core = pwsink->remote->core; + + GST_LOG_OBJECT (pwsink, "add buffer"); + + if (!(b = pw_stream_peek_buffer (pwsink->stream, id))) { + g_warning ("failed to peek buffer"); + return; + } + + buf = gst_buffer_new (); + + data.sink = gst_object_ref (pwsink); + data.id = id; + data.buf = b; + data.header = spa_buffer_find_meta (b, core->type.meta.Header); + + for (i = 0; i < b->n_datas; i++) { + struct spa_data *d = &b->datas[i]; + GstMemory *gmem = NULL; + + if (d->type == core->type.data.MemFd || + d->type == core->type.data.DmaBuf) { + gmem = gst_fd_allocator_alloc (pwsink->allocator, dup (d->fd), + d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE); + gst_memory_resize (gmem, d->chunk->offset + d->mapoffset, d->chunk->size); + data.offset = d->mapoffset; + } + else if (d->type == core->type.data.MemPtr) { + gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, d->chunk->offset, + d->chunk->size, NULL, NULL); + data.offset = 0; + } + if (gmem) + gst_buffer_append_memory (buf, gmem); + } + data.flags = GST_BUFFER_FLAGS (buf); + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf), + process_mem_data_quark, + g_slice_dup (ProcessMemData, &data), + process_mem_data_destroy); + + gst_pipewire_pool_add_buffer (pwsink->pool, buf); + g_hash_table_insert (pwsink->buf_ids, GINT_TO_POINTER (id), buf); + + pw_thread_loop_signal (pwsink->main_loop, FALSE); +} + +static void +on_remove_buffer (struct pw_listener *listener, + struct pw_stream *stream, + uint32_t id) +{ + GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_remove_buffer); + GstBuffer *buf; + + GST_LOG_OBJECT (pwsink, "remove buffer"); + buf = g_hash_table_lookup (pwsink->buf_ids, GINT_TO_POINTER (id)); + if (buf) { + GST_MINI_OBJECT_CAST (buf)->dispose = NULL; + if (!gst_pipewire_pool_remove_buffer (pwsink->pool, buf)) + gst_buffer_ref (buf); + if (g_queue_remove (&pwsink->queue, buf)) + gst_buffer_unref (buf); + g_hash_table_remove (pwsink->buf_ids, GINT_TO_POINTER (id)); + } +} + +static void +on_new_buffer (struct pw_listener *listener, + struct pw_stream *stream, + uint32_t id) +{ + GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_new_buffer); + GstBuffer *buf; + + GST_LOG_OBJECT (pwsink, "got new buffer %u", id); + if (pwsink->stream == NULL) { + GST_LOG_OBJECT (pwsink, "no stream"); + return; + } + buf = g_hash_table_lookup (pwsink->buf_ids, GINT_TO_POINTER (id)); + + if (buf) { + gst_buffer_unref (buf); + pw_thread_loop_signal (pwsink->main_loop, FALSE); + } +} + +static void +do_send_buffer (GstPipeWireSink *pwsink) +{ + GstBuffer *buffer; + ProcessMemData *data; + gboolean res; + guint i; + + buffer = g_queue_pop_head (&pwsink->queue); + if (buffer == NULL) { + GST_WARNING ("out of buffers"); + return; + } + + data = gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buffer), + process_mem_data_quark); + + if (data->header) { + data->header->seq = GST_BUFFER_OFFSET (buffer); + data->header->pts = GST_BUFFER_PTS (buffer); + data->header->dts_offset = GST_BUFFER_DTS (buffer); + } + for (i = 0; i < data->buf->n_datas; i++) { + struct spa_data *d = &data->buf->datas[i]; + GstMemory *mem = gst_buffer_peek_memory (buffer, i); + d->chunk->offset = mem->offset - data->offset; + d->chunk->size = mem->size; + } + + if (!(res = pw_stream_send_buffer (pwsink->stream, data->id))) { + g_warning ("can't send buffer"); + pw_thread_loop_signal (pwsink->main_loop, FALSE); + } else + pwsink->need_ready--; +} + + +static void +on_need_buffer (struct pw_listener *listener, + struct pw_stream *stream) +{ + GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_need_buffer); + + pwsink->need_ready++; + GST_DEBUG ("need buffer %u", pwsink->need_ready); + do_send_buffer (pwsink); +} + +static void +on_state_changed (struct pw_listener *listener, + struct pw_stream *stream) +{ + GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_state_changed); + enum pw_stream_state state; + + state = stream->state; + GST_DEBUG ("got stream state %d", state); + + switch (state) { + case PW_STREAM_STATE_UNCONNECTED: + case PW_STREAM_STATE_CONNECTING: + case PW_STREAM_STATE_CONFIGURE: + case PW_STREAM_STATE_READY: + case PW_STREAM_STATE_PAUSED: + case PW_STREAM_STATE_STREAMING: + break; + case PW_STREAM_STATE_ERROR: + GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, + ("stream error: %s", stream->error), (NULL)); + break; + } + pw_thread_loop_signal (pwsink->main_loop, FALSE); +} + +static void +on_format_changed (struct pw_listener *listener, + struct pw_stream *stream, + struct spa_format *format) +{ + GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, stream_format_changed); + + if (gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool))) + pool_activated (pwsink->pool, pwsink); +} + +static gboolean +gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) +{ + GstPipeWireSink *pwsink; + GPtrArray *possible; + enum pw_stream_state state; + gboolean res = FALSE; + + pwsink = GST_PIPEWIRE_SINK (bsink); + + possible = gst_caps_to_format_all (caps, pwsink->remote->core->type.map); + + pw_thread_loop_lock (pwsink->main_loop); + state = pwsink->stream->state; + + if (state == PW_STREAM_STATE_ERROR) + goto start_error; + + if (state == PW_STREAM_STATE_UNCONNECTED) { + enum pw_stream_flags flags = 0; + + if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE) + flags |= PW_STREAM_FLAG_AUTOCONNECT; + + pw_stream_connect (pwsink->stream, + PW_DIRECTION_OUTPUT, + PW_STREAM_MODE_BUFFER, + pwsink->path, + flags, + possible->len, + (const struct spa_format **) possible->pdata); + + while (TRUE) { + state = pwsink->stream->state; + + if (state == PW_STREAM_STATE_READY) + break; + + if (state == PW_STREAM_STATE_ERROR) + goto start_error; + + pw_thread_loop_wait (pwsink->main_loop); + } + } + res = TRUE; + + pw_thread_loop_unlock (pwsink->main_loop); + + pwsink->negotiated = res; + + return res; + +start_error: + { + GST_ERROR ("could not start stream"); + pw_thread_loop_unlock (pwsink->main_loop); + g_ptr_array_unref (possible); + return FALSE; + } +} + +static GstFlowReturn +gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) +{ + GstPipeWireSink *pwsink; + GstFlowReturn res = GST_FLOW_OK; + + pwsink = GST_PIPEWIRE_SINK (bsink); + + if (!pwsink->negotiated) + goto not_negotiated; + + pw_thread_loop_lock (pwsink->main_loop); + if (pwsink->stream->state != PW_STREAM_STATE_STREAMING) + goto done; + + if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->pool)) { + GstBuffer *b = NULL; + GstMapInfo info = { 0, }; + + if (!gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool))) + gst_buffer_pool_set_active (GST_BUFFER_POOL_CAST (pwsink->pool), TRUE); + + if ((res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->pool), &b, NULL)) != GST_FLOW_OK) + goto done; + + gst_buffer_map (b, &info, GST_MAP_WRITE); + gst_buffer_extract (buffer, 0, info.data, info.size); + gst_buffer_unmap (b, &info); + gst_buffer_resize (b, 0, gst_buffer_get_size (buffer)); + buffer = b; + } else { + gst_buffer_ref (buffer); + } + + GST_DEBUG ("push buffer in queue"); + g_queue_push_tail (&pwsink->queue, buffer); + + if (pwsink->need_ready && pwsink->mode == GST_PIPEWIRE_SINK_MODE_PROVIDE) + do_send_buffer (pwsink); + +done: + pw_thread_loop_unlock (pwsink->main_loop); + + return res; + +not_negotiated: + { + return GST_FLOW_NOT_NEGOTIATED; + } +} + +static gboolean +copy_properties (GQuark field_id, + const GValue *value, + gpointer user_data) +{ + struct pw_properties *properties = user_data; + + if (G_VALUE_HOLDS_STRING (value)) + pw_properties_set (properties, + g_quark_to_string (field_id), + g_value_get_string (value)); + return TRUE; +} + +static gboolean +gst_pipewire_sink_start (GstBaseSink * basesink) +{ + GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink); + struct pw_properties *props; + + pwsink->negotiated = FALSE; + + if (pwsink->properties) { + props = pw_properties_new (NULL, NULL); + gst_structure_foreach (pwsink->properties, copy_properties, props); + } else { + props = NULL; + } + + pw_thread_loop_lock (pwsink->main_loop); + pwsink->stream = pw_stream_new (pwsink->remote, pwsink->client_name, props); + pwsink->pool->stream = pwsink->stream; + + pw_signal_add (&pwsink->stream->state_changed, &pwsink->stream_state_changed, on_state_changed); + pw_signal_add (&pwsink->stream->format_changed, &pwsink->stream_format_changed, on_format_changed); + pw_signal_add (&pwsink->stream->add_buffer, &pwsink->stream_add_buffer, on_add_buffer); + pw_signal_add (&pwsink->stream->remove_buffer, &pwsink->stream_remove_buffer, on_remove_buffer); + pw_signal_add (&pwsink->stream->new_buffer, &pwsink->stream_new_buffer, on_new_buffer); + pw_signal_add (&pwsink->stream->need_buffer, &pwsink->stream_need_buffer, on_need_buffer); + pw_thread_loop_unlock (pwsink->main_loop); + + return TRUE; +} + +static gboolean +gst_pipewire_sink_stop (GstBaseSink * basesink) +{ + GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink); + + pw_thread_loop_lock (pwsink->main_loop); + if (pwsink->stream) { + pw_stream_disconnect (pwsink->stream); + pw_stream_destroy (pwsink->stream); + pwsink->stream = NULL; + pwsink->pool->stream = NULL; + } + pw_thread_loop_unlock (pwsink->main_loop); + + pwsink->negotiated = FALSE; + + return TRUE; +} + +static void +on_remote_state_changed (struct pw_listener *listener, + struct pw_remote *remote) +{ + GstPipeWireSink *pwsink = SPA_CONTAINER_OF (listener, GstPipeWireSink, remote_state_changed); + enum pw_remote_state state; + + state = remote->state; + GST_DEBUG ("got remote state %d", state); + + switch (state) { + case PW_REMOTE_STATE_UNCONNECTED: + case PW_REMOTE_STATE_CONNECTING: + case PW_REMOTE_STATE_CONNECTED: + break; + case PW_REMOTE_STATE_ERROR: + GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, + ("remote error: %s", remote->error), (NULL)); + break; + } + pw_thread_loop_signal (pwsink->main_loop, FALSE); +} + +static gboolean +gst_pipewire_sink_open (GstPipeWireSink * pwsink) +{ + if (pw_thread_loop_start (pwsink->main_loop) != SPA_RESULT_OK) + goto mainloop_error; + + pw_thread_loop_lock (pwsink->main_loop); + pwsink->remote = pw_remote_new (pwsink->core, NULL); + + pw_signal_add (&pwsink->remote->state_changed, &pwsink->remote_state_changed, on_remote_state_changed); + + pw_remote_connect (pwsink->remote); + + while (TRUE) { + enum pw_remote_state state = pwsink->remote->state; + + if (state == PW_REMOTE_STATE_CONNECTED) + break; + + if (state == PW_REMOTE_STATE_ERROR) + goto connect_error; + + pw_thread_loop_wait (pwsink->main_loop); + } + pw_thread_loop_unlock (pwsink->main_loop); + + return TRUE; + + /* ERRORS */ +mainloop_error: + { + GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, + ("Failed to start mainloop"), (NULL)); + return FALSE; + } +connect_error: + { + pw_thread_loop_unlock (pwsink->main_loop); + return FALSE; + } +} + +static gboolean +gst_pipewire_sink_close (GstPipeWireSink * pwsink) +{ + pw_thread_loop_lock (pwsink->main_loop); + if (pwsink->stream) { + pw_stream_disconnect (pwsink->stream); + } + if (pwsink->remote) { + pw_remote_disconnect (pwsink->remote); + + while (TRUE) { + enum pw_remote_state state = pwsink->remote->state; + + if (state == PW_REMOTE_STATE_UNCONNECTED) + break; + + if (state == PW_REMOTE_STATE_ERROR) + break; + + pw_thread_loop_wait (pwsink->main_loop); + } + } + pw_thread_loop_unlock (pwsink->main_loop); + + pw_thread_loop_stop (pwsink->main_loop); + + if (pwsink->stream) { + pw_stream_destroy (pwsink->stream); + pwsink->stream = NULL; + } + + if (pwsink->remote) { + pw_remote_destroy (pwsink->remote); + pwsink->remote = NULL; + } + + return TRUE; +} + +static GstStateChangeReturn +gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstPipeWireSink *this = GST_PIPEWIRE_SINK_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_pipewire_sink_open (this)) + goto open_failed; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* uncork and start recording */ + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* stop recording ASAP by corking */ + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_pipewire_sink_close (this); + break; + default: + break; + } + return ret; + + /* ERRORS */ +open_failed: + { + return GST_STATE_CHANGE_FAILURE; + } +} diff --git a/src/gst/gstpipewiresink.h b/src/gst/gstpipewiresink.h new file mode 100644 index 00000000..d4287d6f --- /dev/null +++ b/src/gst/gstpipewiresink.h @@ -0,0 +1,114 @@ +/* GStreamer + * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_PIPEWIRE_SINK_H__ +#define __GST_PIPEWIRE_SINK_H__ + +#include <gst/gst.h> +#include <gst/base/gstbasesink.h> + +#include <pipewire/pipewire.h> +#include <gst/gstpipewirepool.h> + +G_BEGIN_DECLS + +#define GST_TYPE_PIPEWIRE_SINK \ + (gst_pipewire_sink_get_type()) +#define GST_PIPEWIRE_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSink)) +#define GST_PIPEWIRE_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSinkClass)) +#define GST_IS_PIPEWIRE_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SINK)) +#define GST_IS_PIPEWIRE_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SINK)) +#define GST_PIPEWIRE_SINK_CAST(obj) \ + ((GstPipeWireSink *) (obj)) + +typedef struct _GstPipeWireSink GstPipeWireSink; +typedef struct _GstPipeWireSinkClass GstPipeWireSinkClass; + + +/** + * GstPipeWireSinkMode: + * @GST_PIPEWIRE_SINK_MODE_DEFAULT: the default mode as configured in the server + * @GST_PIPEWIRE_SINK_MODE_RENDER: try to render the media + * @GST_PIPEWIRE_SINK_MODE_PROVIDE: provide the media + * + * Different modes of operation. + */ +typedef enum +{ + GST_PIPEWIRE_SINK_MODE_DEFAULT, + GST_PIPEWIRE_SINK_MODE_RENDER, + GST_PIPEWIRE_SINK_MODE_PROVIDE, +} GstPipeWireSinkMode; + +#define GST_TYPE_PIPEWIRE_SINK_MODE (gst_pipewire_sink_mode_get_type ()) + +/** + * GstPipeWireSink: + * + * Opaque data structure. + */ +struct _GstPipeWireSink { + GstBaseSink element; + + /*< private >*/ + gchar *path; + gchar *client_name; + + /* video state */ + gboolean negotiated; + + struct pw_loop *loop; + struct pw_thread_loop *main_loop; + + struct pw_core *core; + struct pw_remote *remote; + struct pw_listener remote_state_changed; + + struct pw_stream *stream; + struct pw_listener stream_state_changed; + struct pw_listener stream_format_changed; + struct pw_listener stream_add_buffer; + struct pw_listener stream_remove_buffer; + struct pw_listener stream_new_buffer; + struct pw_listener stream_need_buffer; + + GstAllocator *allocator; + GstStructure *properties; + GstPipeWireSinkMode mode; + + GstPipeWirePool *pool; + GHashTable *buf_ids; + GQueue queue; + guint need_ready; +}; + +struct _GstPipeWireSinkClass { + GstBaseSinkClass parent_class; +}; + +GType gst_pipewire_sink_get_type (void); +GType gst_pipewire_sink_mode_get_type (void); + +G_END_DECLS + +#endif /* __GST_PIPEWIRE_SINK_H__ */ diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c new file mode 100644 index 00000000..0395b19f --- /dev/null +++ b/src/gst/gstpipewiresrc.c @@ -0,0 +1,1154 @@ +/* GStreamer + * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-pipewiresrc + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch -v pipewiresrc ! videoconvert ! ximagesink + * ]| Shows pipewire output in an X window. + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "gstpipewiresrc.h" +#include "gstpipewireformat.h" + +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <gio/gunixfdmessage.h> +#include <gst/net/gstnetclientclock.h> +#include <gst/allocators/gstfdmemory.h> +#include <gst/video/video.h> + +#include <spa/buffer.h> + +#include "gstpipewireclock.h" + +static GQuark process_mem_data_quark; + +GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug); +#define GST_CAT_DEFAULT pipewire_src_debug + +#define DEFAULT_ALWAYS_COPY false + +enum +{ + PROP_0, + PROP_PATH, + PROP_CLIENT_NAME, + PROP_STREAM_PROPERTIES, + PROP_ALWAYS_COPY, +}; + + +static GstStaticPadTemplate gst_pipewire_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY + ); + +#define gst_pipewire_src_parent_class parent_class +G_DEFINE_TYPE (GstPipeWireSrc, gst_pipewire_src, GST_TYPE_PUSH_SRC); + +static GstStateChangeReturn +gst_pipewire_src_change_state (GstElement * element, GstStateChange transition); + +static gboolean gst_pipewire_src_negotiate (GstBaseSrc * basesrc); + +static GstFlowReturn gst_pipewire_src_create (GstPushSrc * psrc, + GstBuffer ** buffer); +static gboolean gst_pipewire_src_unlock (GstBaseSrc * basesrc); +static gboolean gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc); +static gboolean gst_pipewire_src_start (GstBaseSrc * basesrc); +static gboolean gst_pipewire_src_stop (GstBaseSrc * basesrc); +static gboolean gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event); +static gboolean gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query); + +static void +gst_pipewire_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object); + + switch (prop_id) { + case PROP_PATH: + g_free (pwsrc->path); + pwsrc->path = g_value_dup_string (value); + break; + + case PROP_CLIENT_NAME: + g_free (pwsrc->client_name); + pwsrc->client_name = g_value_dup_string (value); + break; + + case PROP_STREAM_PROPERTIES: + if (pwsrc->properties) + gst_structure_free (pwsrc->properties); + pwsrc->properties = + gst_structure_copy (gst_value_get_structure (value)); + break; + + case PROP_ALWAYS_COPY: + pwsrc->always_copy = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_pipewire_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string (value, pwsrc->path); + break; + + case PROP_CLIENT_NAME: + g_value_set_string (value, pwsrc->client_name); + break; + + case PROP_STREAM_PROPERTIES: + gst_value_set_structure (value, pwsrc->properties); + break; + + case PROP_ALWAYS_COPY: + g_value_set_boolean (value, pwsrc->always_copy); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstClock * +gst_pipewire_src_provide_clock (GstElement * elem) +{ + GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (elem); + GstClock *clock; + + GST_OBJECT_LOCK (pwsrc); + if (!GST_OBJECT_FLAG_IS_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK)) + goto clock_disabled; + + if (pwsrc->clock && pwsrc->is_live) + clock = GST_CLOCK_CAST (gst_object_ref (pwsrc->clock)); + else + clock = NULL; + GST_OBJECT_UNLOCK (pwsrc); + + return clock; + + /* ERRORS */ +clock_disabled: + { + GST_DEBUG_OBJECT (pwsrc, "clock provide disabled"); + GST_OBJECT_UNLOCK (pwsrc); + return NULL; + } +} + +static void +clear_queue (GstPipeWireSrc *pwsrc) +{ + g_queue_foreach (&pwsrc->queue, (GFunc) gst_mini_object_unref, NULL); + g_queue_clear (&pwsrc->queue); +} + +static void +gst_pipewire_src_finalize (GObject * object) +{ + GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object); + + clear_queue (pwsrc); + + pw_thread_loop_destroy (pwsrc->main_loop); + pwsrc->main_loop = NULL; + pw_loop_destroy (pwsrc->loop); + pwsrc->loop = NULL; + + if (pwsrc->properties) + gst_structure_free (pwsrc->properties); + g_object_unref (pwsrc->fd_allocator); + if (pwsrc->clock) + gst_object_unref (pwsrc->clock); + g_free (pwsrc->path); + g_free (pwsrc->client_name); + g_hash_table_unref (pwsrc->buf_ids); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_pipewire_src_class_init (GstPipeWireSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSrcClass *gstbasesrc_class; + GstPushSrcClass *gstpushsrc_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesrc_class = (GstBaseSrcClass *) klass; + gstpushsrc_class = (GstPushSrcClass *) klass; + + gobject_class->finalize = gst_pipewire_src_finalize; + gobject_class->set_property = gst_pipewire_src_set_property; + gobject_class->get_property = gst_pipewire_src_get_property; + + g_object_class_install_property (gobject_class, + PROP_PATH, + g_param_spec_string ("path", + "Path", + "The source path to connect to (NULL = default)", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_CLIENT_NAME, + g_param_spec_string ("client-name", + "Client Name", + "The client name to use (NULL = default)", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_STREAM_PROPERTIES, + g_param_spec_boxed ("stream-properties", + "stream properties", + "list of PipeWire stream properties", + GST_TYPE_STRUCTURE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_ALWAYS_COPY, + g_param_spec_boolean ("always-copy", + "Always copy", + "Always copy the buffer and data", + DEFAULT_ALWAYS_COPY, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + gstelement_class->provide_clock = gst_pipewire_src_provide_clock; + gstelement_class->change_state = gst_pipewire_src_change_state; + + gst_element_class_set_static_metadata (gstelement_class, + "PipeWire source", "Source/Video", + "Uses PipeWire to create video", "Wim Taymans <wim.taymans@gmail.com>"); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_pipewire_src_template)); + + gstbasesrc_class->negotiate = gst_pipewire_src_negotiate; + gstbasesrc_class->unlock = gst_pipewire_src_unlock; + gstbasesrc_class->unlock_stop = gst_pipewire_src_unlock_stop; + gstbasesrc_class->start = gst_pipewire_src_start; + gstbasesrc_class->stop = gst_pipewire_src_stop; + gstbasesrc_class->event = gst_pipewire_src_event; + gstbasesrc_class->query = gst_pipewire_src_query; + gstpushsrc_class->create = gst_pipewire_src_create; + + GST_DEBUG_CATEGORY_INIT (pipewire_src_debug, "pipewiresrc", 0, + "PipeWire Source"); + + process_mem_data_quark = g_quark_from_static_string ("GstPipeWireSrcProcessMemQuark"); +} + +static void +gst_pipewire_src_init (GstPipeWireSrc * src) +{ + /* we operate in time */ + gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); + + GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_PROVIDE_CLOCK); + + src->always_copy = DEFAULT_ALWAYS_COPY; + + g_queue_init (&src->queue); + + src->fd_allocator = gst_fd_allocator_new (); + src->client_name = pw_get_client_name (); + src->buf_ids = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) gst_buffer_unref); + + src->loop = pw_loop_new (); + src->main_loop = pw_thread_loop_new (src->loop, "pipewire-main-loop"); + src->core = pw_core_new (src->loop, NULL); + GST_DEBUG ("loop %p, mainloop %p", src->loop, src->main_loop); + +} + +typedef struct { + GstPipeWireSrc *src; + guint id; + struct spa_buffer *buf; + struct spa_meta_header *header; + guint flags; + goffset offset; +} ProcessMemData; + +static void +process_mem_data_destroy (gpointer user_data) +{ + ProcessMemData *data = user_data; + + gst_object_unref (data->src); + g_slice_free (ProcessMemData, data); +} + +static gboolean +buffer_recycle (GstMiniObject *obj) +{ + ProcessMemData *data; + GstPipeWireSrc *src; + + gst_mini_object_ref (obj); + data = gst_mini_object_get_qdata (obj, + process_mem_data_quark); + GST_BUFFER_FLAGS (obj) = data->flags; + src = data->src; + + GST_LOG_OBJECT (obj, "recycle buffer"); + pw_thread_loop_lock (src->main_loop); + pw_stream_recycle_buffer (src->stream, data->id); + pw_thread_loop_unlock (src->main_loop); + + return FALSE; +} + +static void +on_add_buffer (struct pw_listener *listener, + struct pw_stream *stream, + guint id) +{ + GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_add_buffer); + struct spa_buffer *b; + GstBuffer *buf; + uint32_t i; + ProcessMemData data; + struct pw_remote *remote = pwsrc->stream->remote; + struct pw_core *core = remote->core; + + GST_LOG_OBJECT (pwsrc, "add buffer"); + + if (!(b = pw_stream_peek_buffer (pwsrc->stream, id))) { + g_warning ("failed to peek buffer"); + return; + } + + buf = gst_buffer_new (); + GST_MINI_OBJECT_CAST (buf)->dispose = buffer_recycle; + + data.src = gst_object_ref (pwsrc); + data.id = id; + data.buf = b; + data.header = spa_buffer_find_meta (b, core->type.meta.Header); + + for (i = 0; i < b->n_datas; i++) { + struct spa_data *d = &b->datas[i]; + GstMemory *gmem = NULL; + + if (d->type == core->type.data.MemFd || d->type == core->type.data.DmaBuf) { + gmem = gst_fd_allocator_alloc (pwsrc->fd_allocator, dup (d->fd), + d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE); + gst_memory_resize (gmem, d->chunk->offset + d->mapoffset, d->chunk->size); + data.offset = d->mapoffset; + } + else if (d->type == core->type.data.MemPtr) { + gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, d->chunk->offset + d->mapoffset, + d->chunk->size, NULL, NULL); + data.offset = 0; + } + if (gmem) + gst_buffer_append_memory (buf, gmem); + } + data.flags = GST_BUFFER_FLAGS (buf); + gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf), + process_mem_data_quark, + g_slice_dup (ProcessMemData, &data), + process_mem_data_destroy); + + g_hash_table_insert (pwsrc->buf_ids, GINT_TO_POINTER (id), buf); +} + +static void +on_remove_buffer (struct pw_listener *listener, + struct pw_stream *stream, + guint id) +{ + GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_remove_buffer); + GstBuffer *buf; + + GST_LOG_OBJECT (pwsrc, "remove buffer"); + buf = g_hash_table_lookup (pwsrc->buf_ids, GINT_TO_POINTER (id)); + if (buf) { + GList *walk; + + GST_MINI_OBJECT_CAST (buf)->dispose = NULL; + + walk = pwsrc->queue.head; + while (walk) { + GList *next = walk->next; + + if (walk->data == buf) { + gst_buffer_unref (buf); + g_queue_delete_link (&pwsrc->queue, walk); + } + walk = next; + } + g_hash_table_remove (pwsrc->buf_ids, GINT_TO_POINTER (id)); + } +} + +static void +on_new_buffer (struct pw_listener *listener, + struct pw_stream *stream, + guint id) +{ + GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_new_buffer); + GstBuffer *buf; + ProcessMemData *data; + struct spa_meta_header *h; + guint i; + + buf = g_hash_table_lookup (pwsrc->buf_ids, GINT_TO_POINTER (id)); + if (buf == NULL) { + g_warning ("unknown buffer %d", id); + return; + } + GST_LOG_OBJECT (pwsrc, "got new buffer %p", buf); + + data = gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buf), + process_mem_data_quark); + h = data->header; + if (h) { + GST_INFO ("pts %" G_GUINT64_FORMAT ", dts_offset %"G_GUINT64_FORMAT, h->pts, h->dts_offset); + + if (GST_CLOCK_TIME_IS_VALID (h->pts)) { + GST_BUFFER_PTS (buf) = h->pts; + if (GST_BUFFER_PTS (buf) + h->dts_offset > 0) + GST_BUFFER_DTS (buf) = GST_BUFFER_PTS (buf) + h->dts_offset; + } + GST_BUFFER_OFFSET (buf) = h->seq; + } + for (i = 0; i < data->buf->n_datas; i++) { + struct spa_data *d = &data->buf->datas[i]; + GstMemory *mem = gst_buffer_peek_memory (buf, i); + mem->offset = d->chunk->offset + data->offset; + mem->size = d->chunk->size; + } + + if (pwsrc->always_copy) + buf = gst_buffer_copy_deep (buf); + else + gst_buffer_ref (buf); + + g_queue_push_tail (&pwsrc->queue, buf); + + pw_thread_loop_signal (pwsrc->main_loop, FALSE); + return; +} + +static void +on_state_changed (struct pw_listener *listener, + struct pw_stream *stream) +{ + GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_state_changed); + enum pw_stream_state state = stream->state; + + GST_DEBUG ("got stream state %s", pw_stream_state_as_string (state)); + + switch (state) { + case PW_STREAM_STATE_UNCONNECTED: + case PW_STREAM_STATE_CONNECTING: + case PW_STREAM_STATE_CONFIGURE: + case PW_STREAM_STATE_READY: + case PW_STREAM_STATE_PAUSED: + case PW_STREAM_STATE_STREAMING: + break; + case PW_STREAM_STATE_ERROR: + GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, + ("stream error: %s", stream->error), (NULL)); + break; + } + pw_thread_loop_signal (pwsrc->main_loop, FALSE); +} + +static void +parse_stream_properties (GstPipeWireSrc *pwsrc, struct pw_properties *props) +{ + const gchar *var; + gboolean is_live; + + GST_OBJECT_LOCK (pwsrc); + var = pw_properties_get (props, "pipewire.latency.is-live"); + is_live = pwsrc->is_live = var ? (atoi (var) == 1) : FALSE; + + var = pw_properties_get (props, "pipewire.latency.min"); + pwsrc->min_latency = var ? (GstClockTime) atoi (var) : 0; + + var = pw_properties_get (props, "pipewire.latency.max"); + pwsrc->max_latency = var ? (GstClockTime) atoi (var) : GST_CLOCK_TIME_NONE; + GST_OBJECT_UNLOCK (pwsrc); + + GST_DEBUG_OBJECT (pwsrc, "live %d", is_live); + + gst_base_src_set_live (GST_BASE_SRC (pwsrc), is_live); +} + +static gboolean +gst_pipewire_src_stream_start (GstPipeWireSrc *pwsrc) +{ + pw_thread_loop_lock (pwsrc->main_loop); + GST_DEBUG_OBJECT (pwsrc, "doing stream start"); + while (TRUE) { + enum pw_stream_state state = pwsrc->stream->state; + + GST_DEBUG_OBJECT (pwsrc, "waiting for STREAMING, now %s", pw_stream_state_as_string (state)); + if (state == PW_STREAM_STATE_STREAMING) + break; + + if (state == PW_STREAM_STATE_ERROR) + goto start_error; + + if (pwsrc->remote->state == PW_REMOTE_STATE_ERROR) + goto start_error; + + pw_thread_loop_wait (pwsrc->main_loop); + } + + parse_stream_properties (pwsrc, pwsrc->stream->properties); + pw_thread_loop_unlock (pwsrc->main_loop); + + pw_thread_loop_lock (pwsrc->main_loop); + GST_DEBUG_OBJECT (pwsrc, "signal started"); + pwsrc->started = TRUE; + pw_thread_loop_signal (pwsrc->main_loop, FALSE); + pw_thread_loop_unlock (pwsrc->main_loop); + + return TRUE; + +start_error: + { + GST_DEBUG_OBJECT (pwsrc, "error starting stream"); + pw_thread_loop_unlock (pwsrc->main_loop); + return FALSE; + } +} + +static enum pw_stream_state +wait_negotiated (GstPipeWireSrc *this) +{ + enum pw_stream_state state; + + pw_thread_loop_lock (this->main_loop); + while (TRUE) { + state = this->stream->state; + + GST_DEBUG_OBJECT (this, "waiting for started signal, state now %s", + pw_stream_state_as_string (state)); + + if (state == PW_STREAM_STATE_ERROR) + break; + + if (this->remote->state == PW_REMOTE_STATE_ERROR) + break; + + if (this->started) + break; + + pw_thread_loop_wait (this->main_loop); + } + GST_DEBUG_OBJECT (this, "got started signal"); + pw_thread_loop_unlock (this->main_loop); + + return state; +} + +static gboolean +gst_pipewire_src_negotiate (GstBaseSrc * basesrc) +{ + GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc); + GstCaps *thiscaps; + GstCaps *caps = NULL; + GstCaps *peercaps = NULL; + gboolean result = FALSE; + GPtrArray *possible; + + /* first see what is possible on our source pad */ + thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL); + GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps); + /* nothing or anything is allowed, we're done */ + if (thiscaps == NULL) + goto no_nego_needed; + + if (G_UNLIKELY (gst_caps_is_empty (thiscaps))) + goto no_caps; + + /* get the peer caps */ + peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), thiscaps); + GST_DEBUG_OBJECT (basesrc, "caps of peer: %" GST_PTR_FORMAT, peercaps); + if (peercaps) { + /* The result is already a subset of our caps */ + caps = peercaps; + gst_caps_unref (thiscaps); + } else { + /* no peer, work with our own caps then */ + caps = thiscaps; + } + if (caps == NULL || gst_caps_is_empty (caps)) + goto no_common_caps; + + GST_DEBUG_OBJECT (basesrc, "have common caps: %" GST_PTR_FORMAT, caps); + + /* open a connection with these caps */ + possible = gst_caps_to_format_all (caps, pwsrc->remote->core->type.map); + gst_caps_unref (caps); + + /* first disconnect */ + pw_thread_loop_lock (pwsrc->main_loop); + if (pwsrc->stream->state != PW_STREAM_STATE_UNCONNECTED) { + GST_DEBUG_OBJECT (basesrc, "disconnect capture"); + pw_stream_disconnect (pwsrc->stream); + while (TRUE) { + enum pw_stream_state state = pwsrc->stream->state; + + GST_DEBUG_OBJECT (basesrc, "waiting for UNCONNECTED, now %s", pw_stream_state_as_string (state)); + if (state == PW_STREAM_STATE_UNCONNECTED) + break; + + if (state == PW_STREAM_STATE_ERROR) { + g_ptr_array_unref (possible); + goto connect_error; + } + + pw_thread_loop_wait (pwsrc->main_loop); + } + } + + GST_DEBUG_OBJECT (basesrc, "connect capture with path %s", pwsrc->path); + pw_stream_connect (pwsrc->stream, + PW_DIRECTION_INPUT, + PW_STREAM_MODE_BUFFER, + pwsrc->path, + PW_STREAM_FLAG_AUTOCONNECT, + possible->len, + (const struct spa_format **)possible->pdata); + g_ptr_array_free (possible, TRUE); + + while (TRUE) { + enum pw_stream_state state = pwsrc->stream->state; + + GST_DEBUG_OBJECT (basesrc, "waiting for PAUSED, now %s", pw_stream_state_as_string (state)); + if (state == PW_STREAM_STATE_PAUSED || + state == PW_STREAM_STATE_STREAMING) + break; + + if (state == PW_STREAM_STATE_ERROR) + goto connect_error; + + if (pwsrc->remote->state == PW_REMOTE_STATE_ERROR) + goto connect_error; + + pw_thread_loop_wait (pwsrc->main_loop); + } + pw_thread_loop_unlock (pwsrc->main_loop); + + result = gst_pipewire_src_stream_start (pwsrc); + + pwsrc->negotiated = result; + + return result; + +no_nego_needed: + { + GST_DEBUG_OBJECT (basesrc, "no negotiation needed"); + if (thiscaps) + gst_caps_unref (thiscaps); + return TRUE; + } +no_caps: + { + GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT, + ("No supported formats found"), + ("This element did not produce valid caps")); + if (thiscaps) + gst_caps_unref (thiscaps); + return FALSE; + } +no_common_caps: + { + GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT, + ("No supported formats found"), + ("This element does not have formats in common with the peer")); + if (caps) + gst_caps_unref (caps); + return FALSE; + } +connect_error: + { + pw_thread_loop_unlock (pwsrc->main_loop); + return FALSE; + } +} + +#define PROP(f,key,type,...) \ + SPA_POD_PROP (f,key,0,type,1,__VA_ARGS__) +#define PROP_U_MM(f,key,type,...) \ + SPA_POD_PROP (f,key,SPA_POD_PROP_FLAG_UNSET | \ + SPA_POD_PROP_RANGE_MIN_MAX,type,3,__VA_ARGS__) + +static void +on_format_changed (struct pw_listener *listener, + struct pw_stream *stream, + struct spa_format *format) +{ + GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, stream_format_changed); + GstCaps *caps; + gboolean res; + struct pw_remote *remote = stream->remote; + struct pw_core *core = remote->core; + + if (format == NULL) { + GST_DEBUG_OBJECT (pwsrc, "clear format"); + pw_stream_finish_format (pwsrc->stream, SPA_RESULT_OK, NULL, 0); + return; + } + + caps = gst_caps_from_format (format, core->type.map); + GST_DEBUG_OBJECT (pwsrc, "we got format %" GST_PTR_FORMAT, caps); + res = gst_base_src_set_caps (GST_BASE_SRC (pwsrc), caps); + gst_caps_unref (caps); + + if (res) { + struct spa_param *params[2]; + struct spa_pod_builder b = { NULL }; + uint8_t buffer[512]; + struct spa_pod_frame f[2]; + + spa_pod_builder_init (&b, buffer, sizeof (buffer)); + spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_buffers.Buffers, + PROP_U_MM (&f[1], core->type.param_alloc_buffers.size, SPA_POD_TYPE_INT, 0, 0, INT32_MAX), + PROP_U_MM (&f[1], core->type.param_alloc_buffers.stride, SPA_POD_TYPE_INT, 0, 0, INT32_MAX), + PROP_U_MM (&f[1], core->type.param_alloc_buffers.buffers, SPA_POD_TYPE_INT, 16, 0, INT32_MAX), + PROP (&f[1], core->type.param_alloc_buffers.align, SPA_POD_TYPE_INT, 16)); + params[0] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param); + + spa_pod_builder_object (&b, &f[0], 0, core->type.param_alloc_meta_enable.MetaEnable, + PROP (&f[1], core->type.param_alloc_meta_enable.type, SPA_POD_TYPE_ID, core->type.meta.Header), + PROP (&f[1], core->type.param_alloc_meta_enable.size, SPA_POD_TYPE_INT, sizeof (struct spa_meta_header))); + params[1] = SPA_POD_BUILDER_DEREF (&b, f[0].ref, struct spa_param); + + GST_DEBUG_OBJECT (pwsrc, "doing finish format"); + pw_stream_finish_format (pwsrc->stream, SPA_RESULT_OK, params, 2); + } else { + GST_WARNING_OBJECT (pwsrc, "finish format with error"); + pw_stream_finish_format (pwsrc->stream, SPA_RESULT_INVALID_MEDIA_TYPE, NULL, 0); + } +} + +static gboolean +gst_pipewire_src_unlock (GstBaseSrc * basesrc) +{ + GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc); + + pw_thread_loop_lock (pwsrc->main_loop); + GST_DEBUG_OBJECT (pwsrc, "setting flushing"); + pwsrc->flushing = TRUE; + pw_thread_loop_signal (pwsrc->main_loop, FALSE); + pw_thread_loop_unlock (pwsrc->main_loop); + + return TRUE; +} + +static gboolean +gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc) +{ + GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc); + + pw_thread_loop_lock (pwsrc->main_loop); + GST_DEBUG_OBJECT (pwsrc, "unsetting flushing"); + pwsrc->flushing = FALSE; + pw_thread_loop_unlock (pwsrc->main_loop); + + return TRUE; +} + +static gboolean +gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event) +{ + gboolean res = FALSE; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CUSTOM_UPSTREAM: + if (gst_video_event_is_force_key_unit (event)) { + GstClockTime running_time; + gboolean all_headers; + guint count; + + gst_video_event_parse_upstream_force_key_unit (event, + &running_time, &all_headers, &count); + +#if 0 + pw_buffer_builder_init (&b); + + refresh.last_id = 0; + refresh.request_type = all_headers ? 1 : 0; + refresh.pts = running_time; + pw_buffer_builder_add_refresh_request (&b, &refresh); + + pw_buffer_builder_end (&b, &pbuf); + + GST_OBJECT_LOCK (pwsrc); + if (pwsrc->stream_state == PW_STREAM_STATE_STREAMING) { + GST_DEBUG_OBJECT (pwsrc, "send refresh request"); + pw_stream_send_buffer (pwsrc->stream, &pbuf); + } + GST_OBJECT_UNLOCK (pwsrc); + + pw_buffer_unref (&pbuf); +#endif + res = TRUE; + } else { + res = GST_BASE_SRC_CLASS (parent_class)->event (src, event); + } + break; + default: + res = GST_BASE_SRC_CLASS (parent_class)->event (src, event); + break; + } + return res; +} + +static gboolean +gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query) +{ + gboolean res = FALSE; + GstPipeWireSrc *pwsrc; + + pwsrc = GST_PIPEWIRE_SRC (src); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + GST_OBJECT_LOCK (pwsrc); + pwsrc->min_latency = 10000000; + pwsrc->max_latency = GST_CLOCK_TIME_NONE; + gst_query_set_latency (query, pwsrc->is_live, pwsrc->min_latency, pwsrc->max_latency); + GST_OBJECT_UNLOCK (pwsrc); + res = TRUE; + break; + default: + res = GST_BASE_SRC_CLASS (parent_class)->query (src, query); + break; + } + return res; +} + +static GstFlowReturn +gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) +{ + GstPipeWireSrc *pwsrc; + GstClockTime pts, dts, base_time; + + pwsrc = GST_PIPEWIRE_SRC (psrc); + + if (!pwsrc->negotiated) + goto not_negotiated; + + pw_thread_loop_lock (pwsrc->main_loop); + while (TRUE) { + enum pw_stream_state state; + + if (pwsrc->flushing) + goto streaming_stopped; + + state = pwsrc->stream->state; + if (state == PW_STREAM_STATE_ERROR) + goto streaming_error; + + if (state != PW_STREAM_STATE_STREAMING) + goto streaming_stopped; + + *buffer = g_queue_pop_head (&pwsrc->queue); + GST_DEBUG ("popped buffer %p", *buffer); + if (*buffer != NULL) + break; + + pw_thread_loop_wait (pwsrc->main_loop); + } + pw_thread_loop_unlock (pwsrc->main_loop); + + if (pwsrc->is_live) + base_time = GST_ELEMENT_CAST (psrc)->base_time; + else + base_time = 0; + + pts = GST_BUFFER_PTS (*buffer); + dts = GST_BUFFER_DTS (*buffer); + + if (GST_CLOCK_TIME_IS_VALID (pts)) + pts = (pts >= base_time ? pts - base_time : 0); + if (GST_CLOCK_TIME_IS_VALID (dts)) + dts = (dts >= base_time ? dts - base_time : 0); + + GST_INFO ("pts %" G_GUINT64_FORMAT ", dts %"G_GUINT64_FORMAT + ", base-time %"GST_TIME_FORMAT" -> %"GST_TIME_FORMAT", %"GST_TIME_FORMAT, + GST_BUFFER_PTS (*buffer), GST_BUFFER_DTS (*buffer), GST_TIME_ARGS (base_time), + GST_TIME_ARGS (pts), GST_TIME_ARGS (dts)); + + GST_BUFFER_PTS (*buffer) = pts; + GST_BUFFER_DTS (*buffer) = dts; + + return GST_FLOW_OK; + +not_negotiated: + { + return GST_FLOW_NOT_NEGOTIATED; + } +streaming_error: + { + pw_thread_loop_unlock (pwsrc->main_loop); + return GST_FLOW_ERROR; + } +streaming_stopped: + { + pw_thread_loop_unlock (pwsrc->main_loop); + return GST_FLOW_FLUSHING; + } +} + +static gboolean +gst_pipewire_src_start (GstBaseSrc * basesrc) +{ + return TRUE; +} + +static gboolean +gst_pipewire_src_stop (GstBaseSrc * basesrc) +{ + GstPipeWireSrc *pwsrc; + + pwsrc = GST_PIPEWIRE_SRC (basesrc); + + pw_thread_loop_lock (pwsrc->main_loop); + clear_queue (pwsrc); + pw_thread_loop_unlock (pwsrc->main_loop); + + return TRUE; +} + +static void +on_remote_state_changed (struct pw_listener *listener, + struct pw_remote *remote) +{ + GstPipeWireSrc *pwsrc = SPA_CONTAINER_OF (listener, GstPipeWireSrc, remote_state_changed); + enum pw_remote_state state = remote->state; + + GST_DEBUG ("got remote state %s", pw_remote_state_as_string (state)); + + switch (state) { + case PW_REMOTE_STATE_UNCONNECTED: + case PW_REMOTE_STATE_CONNECTING: + case PW_REMOTE_STATE_CONNECTED: + break; + case PW_REMOTE_STATE_ERROR: + GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, + ("remote error: %s", remote->error), (NULL)); + break; + } + pw_thread_loop_signal (pwsrc->main_loop, FALSE); +} + +static gboolean +copy_properties (GQuark field_id, + const GValue *value, + gpointer user_data) +{ + struct pw_properties *properties = user_data; + + if (G_VALUE_HOLDS_STRING (value)) + pw_properties_set (properties, + g_quark_to_string (field_id), + g_value_get_string (value)); + return TRUE; +} + +static gboolean +gst_pipewire_src_open (GstPipeWireSrc * pwsrc) +{ + struct pw_properties *props; + + if (pw_thread_loop_start (pwsrc->main_loop) != SPA_RESULT_OK) + goto mainloop_failed; + + pw_thread_loop_lock (pwsrc->main_loop); + if ((pwsrc->remote = pw_remote_new (pwsrc->core, NULL)) == NULL) + goto no_remote; + + pw_signal_add (&pwsrc->remote->state_changed, &pwsrc->remote_state_changed, on_remote_state_changed); + + pw_remote_connect (pwsrc->remote); + + while (TRUE) { + enum pw_remote_state state = pwsrc->remote->state; + + GST_DEBUG ("waiting for CONNECTED, now %s", pw_remote_state_as_string (state)); + if (state == PW_REMOTE_STATE_CONNECTED) + break; + + if (state == PW_REMOTE_STATE_ERROR) + goto connect_error; + + pw_thread_loop_wait (pwsrc->main_loop); + } + + if (pwsrc->properties) { + props = pw_properties_new (NULL, NULL); + gst_structure_foreach (pwsrc->properties, copy_properties, props); + } else { + props = NULL; + } + + if ((pwsrc->stream = pw_stream_new (pwsrc->remote, pwsrc->client_name, props)) == NULL) + goto no_stream; + + pw_signal_add (&pwsrc->stream->state_changed, &pwsrc->stream_state_changed, on_state_changed); + pw_signal_add (&pwsrc->stream->format_changed, &pwsrc->stream_format_changed, on_format_changed); + pw_signal_add (&pwsrc->stream->add_buffer, &pwsrc->stream_add_buffer, on_add_buffer); + pw_signal_add (&pwsrc->stream->remove_buffer, &pwsrc->stream_remove_buffer, on_remove_buffer); + pw_signal_add (&pwsrc->stream->new_buffer, &pwsrc->stream_new_buffer, on_new_buffer); + + pwsrc->clock = gst_pipewire_clock_new (pwsrc->stream); + pw_thread_loop_unlock (pwsrc->main_loop); + + return TRUE; + + /* ERRORS */ +mainloop_failed: + { + GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("error starting mainloop"), (NULL)); + return FALSE; + } +no_remote: + { + GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("can't create remote"), (NULL)); + pw_thread_loop_unlock (pwsrc->main_loop); + return FALSE; + } +connect_error: + { + pw_thread_loop_unlock (pwsrc->main_loop); + return FALSE; + } +no_stream: + { + GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("can't create stream"), (NULL)); + pw_thread_loop_unlock (pwsrc->main_loop); + return FALSE; + } +} + +static void +gst_pipewire_src_close (GstPipeWireSrc * pwsrc) +{ + clear_queue (pwsrc); + + pw_thread_loop_stop (pwsrc->main_loop); + + pw_stream_destroy (pwsrc->stream); + pwsrc->stream = NULL; + + pw_remote_destroy (pwsrc->remote); + pwsrc->remote = NULL; + + GST_OBJECT_LOCK (pwsrc); + g_clear_object (&pwsrc->clock); + GST_OBJECT_UNLOCK (pwsrc); +} + +static GstStateChangeReturn +gst_pipewire_src_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstPipeWireSrc *this = GST_PIPEWIRE_SRC_CAST (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_pipewire_src_open (this)) + goto open_failed; + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* uncork and start recording */ + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + /* stop recording ASAP by corking */ + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + if (wait_negotiated (this) == PW_STREAM_STATE_ERROR) + goto open_failed; + + if (gst_base_src_is_live (GST_BASE_SRC (element))) + ret = GST_STATE_CHANGE_NO_PREROLL; + break; + case GST_STATE_CHANGE_PLAYING_TO_PAUSED: + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + this->negotiated = FALSE; + break; + case GST_STATE_CHANGE_READY_TO_NULL: + gst_pipewire_src_close (this); + break; + default: + break; + } + return ret; + + /* ERRORS */ +open_failed: + { + return GST_STATE_CHANGE_FAILURE; + } +} diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h new file mode 100644 index 00000000..f888bcca --- /dev/null +++ b/src/gst/gstpipewiresrc.h @@ -0,0 +1,97 @@ +/* GStreamer + * Copyright (C) <2015> Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_PIPEWIRE_SRC_H__ +#define __GST_PIPEWIRE_SRC_H__ + +#include <gst/gst.h> +#include <gst/base/gstpushsrc.h> + +#include <pipewire/pipewire.h> + +G_BEGIN_DECLS + +#define GST_TYPE_PIPEWIRE_SRC \ + (gst_pipewire_src_get_type()) +#define GST_PIPEWIRE_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrc)) +#define GST_PIPEWIRE_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrcClass)) +#define GST_IS_PIPEWIRE_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SRC)) +#define GST_IS_PIPEWIRE_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SRC)) +#define GST_PIPEWIRE_SRC_CAST(obj) \ + ((GstPipeWireSrc *) (obj)) + +typedef struct _GstPipeWireSrc GstPipeWireSrc; +typedef struct _GstPipeWireSrcClass GstPipeWireSrcClass; + +/** + * GstPipeWireSrc: + * + * Opaque data structure. + */ +struct _GstPipeWireSrc { + GstPushSrc element; + + /*< private >*/ + gchar *path; + gchar *client_name; + gboolean always_copy; + + gboolean negotiated; + gboolean flushing; + gboolean started; + + gboolean is_live; + GstClockTime min_latency; + GstClockTime max_latency; + + struct pw_loop *loop; + struct pw_thread_loop *main_loop; + + struct pw_core *core; + struct pw_remote *remote; + struct pw_listener remote_state_changed; + + struct pw_stream *stream; + struct pw_listener stream_state_changed; + struct pw_listener stream_format_changed; + struct pw_listener stream_add_buffer; + struct pw_listener stream_remove_buffer; + struct pw_listener stream_new_buffer; + + GstAllocator *fd_allocator; + GstStructure *properties; + + GHashTable *buf_ids; + GQueue queue; + GstClock *clock; +}; + +struct _GstPipeWireSrcClass { + GstPushSrcClass parent_class; +}; + +GType gst_pipewire_src_get_type (void); + +G_END_DECLS + +#endif /* __GST_PIPEWIRE_SRC_H__ */ diff --git a/src/gst/meson.build b/src/gst/meson.build new file mode 100644 index 00000000..cfc604d6 --- /dev/null +++ b/src/gst/meson.build @@ -0,0 +1,31 @@ +pipewire_gst_sources = [ + 'gstpipewire.c', + 'gstpipewireclock.c', + 'gstpipewiredeviceprovider.c', + 'gstpipewireformat.c', + 'gstpipewirepool.c', + 'gstpipewiresink.c', + 'gstpipewiresrc.c', +] + +pipewire_gst_headers = [ + 'gstpipewireclock.h', + 'gstpipewiredeviceprovider.h', + 'gstpipewireformat.h', + 'gstpipewirepool.h', + 'gstpipewiresink.h', + 'gstpipewiresrc.h', +] + +pipewire_gst_c_args = [ + '-DHAVE_CONFIG_H', +] + +pipewire_gst = shared_library('gstpipewire', + pipewire_gst_sources, + c_args : pipewire_gst_c_args, + include_directories : [configinc, spa_inc], + dependencies : [gobject_dep, glib_dep, gio_dep, gst_dep, pipewire_dep], + install : true, + install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')), +) diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 00000000..c9032de4 --- /dev/null +++ b/src/meson.build @@ -0,0 +1,8 @@ + +subdir('pipewire') +subdir('extensions') +subdir('daemon') +subdir('tools') +subdir('modules') +subdir('gst') +subdir('examples') diff --git a/src/modules/meson.build b/src/modules/meson.build new file mode 100644 index 00000000..de3be9de --- /dev/null +++ b/src/modules/meson.build @@ -0,0 +1,91 @@ +subdir('spa') + +pipewire_module_c_args = [ + '-DHAVE_CONFIG_H', + '-D_GNU_SOURCE', +] + +pipewire_module_flatpak = shared_library('pipewire-module-flatpak', [ 'module-flatpak.c' ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : modules_install_dir, + dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_autolink = shared_library('pipewire-module-autolink', [ 'module-autolink.c' ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : modules_install_dir, + dependencies : [mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_mixer = shared_library('pipewire-module-mixer', + [ 'module-mixer.c', 'spa/spa-node.c' ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : modules_install_dir, + dependencies : [mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_client_node = shared_library('pipewire-module-client-node', + [ 'module-client-node.c', + 'module-client-node/client-node.c', + 'module-client-node/protocol-native.c', + 'module-protocol-native/connection.c', + 'spa/spa-node.c', ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : modules_install_dir, + dependencies : [mathlib, dl_lib, pipewire_dep], +) + +#pipewire_module_protocol_dbus = shared_library('pipewire-module-protocol-dbus', [ 'module-protocol-dbus.c', gdbus_target ], +# c_args : pipewire_module_c_args, +# include_directories : [configinc, spa_inc], +# link_with : spalib, +# install : true, +# install_dir : modules_install_dir, +# dependencies : [glib_dep, gio_dep, mathlib, dl_lib, pipewire_dep], +#) + +pipewire_module_protocol_native = shared_library('pipewire-module-protocol-native', + [ 'module-protocol-native.c', + 'module-protocol-native/protocol-native.c', + 'module-protocol-native/connection.c' ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : modules_install_dir, + dependencies : [mathlib, dl_lib, pipewire_dep], +) + +if jack_dep.found() +pipewire_module_jack = shared_library('pipewire-module-jack', + [ 'module-jack.c', + 'module-jack/shm.c' ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : modules_install_dir, + dependencies : [jack_dep, mathlib, dl_lib, rt_lib, pipewire_dep], +) +endif + +pipewire_module_suspend_on_idle = shared_library('pipewire-module-suspend-on-idle', [ 'module-suspend-on-idle.c' ], + c_args : pipewire_module_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : modules_install_dir, + dependencies : [mathlib, dl_lib, pipewire_dep], +) diff --git a/src/modules/module-autolink.c b/src/modules/module-autolink.c new file mode 100644 index 00000000..edcb145b --- /dev/null +++ b/src/modules/module-autolink.c @@ -0,0 +1,329 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include "config.h" + +#include "pipewire/interfaces.h" +#include "pipewire/core.h" +#include "pipewire/module.h" + +struct impl { + struct pw_core *core; + struct pw_properties *properties; + + struct pw_listener global_added; + struct pw_listener global_removed; + + struct spa_list node_list; +}; + +struct node_info { + struct impl *impl; + struct pw_node *node; + struct spa_list link; + struct pw_listener state_changed; + struct pw_listener port_added; + struct pw_listener port_removed; + struct pw_listener port_unlinked; + struct pw_listener link_state_changed; + struct pw_listener link_destroy; +}; + +static struct node_info *find_node_info(struct impl *impl, struct pw_node *node) +{ + struct node_info *info; + + spa_list_for_each(info, &impl->node_list, link) { + if (info->node == node) + return info; + } + return NULL; +} + +static void node_info_free(struct node_info *info) +{ + spa_list_remove(&info->link); + pw_signal_remove(&info->state_changed); + pw_signal_remove(&info->port_added); + pw_signal_remove(&info->port_removed); + pw_signal_remove(&info->port_unlinked); + pw_signal_remove(&info->link_destroy); + pw_signal_remove(&info->link_state_changed); + free(info); +} + +static void try_link_port(struct pw_node *node, struct pw_port *port, struct node_info *info); + +static void +on_link_port_unlinked(struct pw_listener *listener, struct pw_link *link, struct pw_port *port) +{ + struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, port_unlinked); + struct impl *impl = info->impl; + + pw_log_debug("module %p: link %p: port %p unlinked", impl, link, port); + if (port->direction == PW_DIRECTION_OUTPUT && link->input) + try_link_port(link->input->node, link->input, info); +} + +static void +on_link_state_changed(struct pw_listener *listener, + struct pw_link *link, enum pw_link_state old, enum pw_link_state state) +{ + struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, link_state_changed); + struct impl *impl = info->impl; + + switch (state) { + case PW_LINK_STATE_ERROR: + { + struct pw_resource *resource; + + pw_log_debug("module %p: link %p: state error: %s", impl, link, + link->error); + + spa_list_for_each(resource, &link->resource_list, link) { + pw_core_notify_error(resource->client->core_resource, + resource->id, SPA_RESULT_ERROR, link->error); + } + if (info->node->owner) { + pw_core_notify_error(info->node->owner->client->core_resource, + info->node->owner->id, + SPA_RESULT_ERROR, link->error); + } + break; + } + + + case PW_LINK_STATE_UNLINKED: + pw_log_debug("module %p: link %p: unlinked", impl, link); + break; + + case PW_LINK_STATE_INIT: + case PW_LINK_STATE_NEGOTIATING: + case PW_LINK_STATE_ALLOCATING: + case PW_LINK_STATE_PAUSED: + case PW_LINK_STATE_RUNNING: + break; + } +} + +static void +on_link_destroy(struct pw_listener *listener, struct pw_link *link) +{ + struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, link_destroy); + struct impl *impl = info->impl; + + pw_log_debug("module %p: link %p destroyed", impl, link); + pw_signal_remove(&info->port_unlinked); + pw_signal_remove(&info->link_state_changed); + pw_signal_remove(&info->link_destroy); + spa_list_init(&info->port_unlinked.link); + spa_list_init(&info->link_state_changed.link); + spa_list_init(&info->link_destroy.link); +} + +static void try_link_port(struct pw_node *node, struct pw_port *port, struct node_info *info) +{ + struct impl *impl = info->impl; + struct pw_properties *props; + const char *str; + uint32_t path_id; + char *error = NULL; + struct pw_link *link; + struct pw_port *target; + + props = node->properties; + if (props == NULL) { + pw_log_debug("module %p: node has no properties", impl); + return; + } + + str = pw_properties_get(props, "pipewire.target.node"); + if (str != NULL) + path_id = atoi(str); + else { + str = pw_properties_get(props, "pipewire.autoconnect"); + if (str == NULL || atoi(str) == 0) { + pw_log_debug("module %p: node does not need autoconnect", impl); + return; + } + path_id = SPA_ID_INVALID; + } + + pw_log_debug("module %p: try to find and link to node '%d'", impl, path_id); + + target = pw_core_find_port(impl->core, port, path_id, NULL, 0, NULL, &error); + if (target == NULL) + goto error; + + if (port->direction == PW_DIRECTION_OUTPUT) + link = pw_link_new(impl->core, port, target, NULL, NULL, &error); + else + link = pw_link_new(impl->core, target, port, NULL, NULL, &error); + + if (link == NULL) + goto error; + + pw_signal_add(&link->port_unlinked, &info->port_unlinked, on_link_port_unlinked); + pw_signal_add(&link->state_changed, &info->link_state_changed, on_link_state_changed); + pw_signal_add(&link->destroy_signal, &info->link_destroy, on_link_destroy); + + pw_link_activate(link); + + return; + + error: + pw_log_error("module %p: can't link node '%s'", impl, error); + if (info->node->owner && info->node->owner->client->core_resource) { + pw_core_notify_error(info->node->owner->client->core_resource, + info->node->owner->id, SPA_RESULT_ERROR, error); + } + free(error); + return; +} + +static void on_port_added(struct pw_listener *listener, struct pw_node *node, struct pw_port *port) +{ + struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, port_added); + + try_link_port(node, port, info); +} + +static void +on_port_removed(struct pw_listener *listener, struct pw_node *node, struct pw_port *port) +{ +} + +static void on_node_created(struct pw_node *node, struct node_info *info) +{ + struct pw_port *port; + + spa_list_for_each(port, &node->input_ports, link) + on_port_added(&info->port_added, node, port); + + spa_list_for_each(port, &node->output_ports, link) + on_port_added(&info->port_added, node, port); +} + +static void +on_state_changed(struct pw_listener *listener, + struct pw_node *node, enum pw_node_state old, enum pw_node_state state) +{ + struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, state_changed); + + if (old == PW_NODE_STATE_CREATING && state == PW_NODE_STATE_SUSPENDED) + on_node_created(node, info); +} + +static void +on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added); + + if (global->type == impl->core->type.node) { + struct pw_node *node = global->object; + struct node_info *ninfo; + + ninfo = calloc(1, sizeof(struct node_info)); + ninfo->impl = impl; + ninfo->node = node; + spa_list_insert(impl->node_list.prev, &ninfo->link); + spa_list_init(&ninfo->port_unlinked.link); + spa_list_init(&ninfo->link_state_changed.link); + spa_list_init(&ninfo->link_destroy.link); + + pw_signal_add(&node->port_added, &ninfo->port_added, on_port_added); + pw_signal_add(&node->port_removed, &ninfo->port_removed, on_port_removed); + pw_signal_add(&node->state_changed, &ninfo->state_changed, on_state_changed); + + pw_log_debug("module %p: node %p added", impl, node); + + if (node->info.state > PW_NODE_STATE_CREATING) + on_node_created(node, ninfo); + } +} + +static void +on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed); + + if (global->type == impl->core->type.node) { + struct pw_node *node = global->object; + struct node_info *ninfo; + + if ((ninfo = find_node_info(impl, node))) + node_info_free(ninfo); + + pw_log_debug("module %p: node %p removed", impl, node); + } +} + +/** + * module_new: + * @core: #struct pw_core + * @properties: #struct pw_properties + * + * Make a new #struct impl object with given @properties + * + * Returns: a new #struct impl + */ +static struct impl *module_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("module %p: new", impl); + + impl->core = core; + impl->properties = properties; + + spa_list_init(&impl->node_list); + + pw_signal_add(&core->global_added, &impl->global_added, on_global_added); + pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed); + + return impl; +} + +#if 0 +static void module_destroy(struct impl *impl) +{ + pw_log_debug("module %p: destroy", impl); + + pw_global_destroy(impl->global); + + pw_signal_remove(&impl->global_added); + pw_signal_remove(&impl->global_removed); + pw_signal_remove(&impl->port_added); + pw_signal_remove(&impl->port_removed); + pw_signal_remove(&impl->port_unlinked); + pw_signal_remove(&impl->link_state_changed); + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + module->user_data = module_new(module->core, NULL); + return true; +} diff --git a/src/modules/module-client-node.c b/src/modules/module-client-node.c new file mode 100644 index 00000000..d1505068 --- /dev/null +++ b/src/modules/module-client-node.c @@ -0,0 +1,97 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <dlfcn.h> + +#include "config.h" + +#include "pipewire/interfaces.h" +#include "pipewire/core.h" +#include "pipewire/module.h" + +#include "module-client-node/client-node.h" + +struct pw_protocol *pw_protocol_native_ext_client_node_init(void); + +struct impl { + struct pw_node_factory this; + struct pw_properties *properties; +}; + +static struct pw_node *create_node(struct pw_node_factory *factory, + struct pw_resource *resource, + const char *name, + struct pw_properties *properties) +{ + struct pw_client_node *node; + + node = pw_client_node_new(resource, name, properties); + if (node == NULL) + goto no_mem; + + return node->node; + + no_mem: + pw_log_error("can't create node"); + pw_core_notify_error(resource->client->core_resource, + resource->client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory"); + return NULL; +} + +static struct impl *module_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("module %p: new", impl); + + impl->properties = properties; + + impl->this.core = core; + impl->this.name = "client-node"; + impl->this.type = spa_type_map_get_id(core->type.map, PIPEWIRE_TYPE__ClientNode); + pw_signal_init(&impl->this.destroy_signal); + impl->this.create_node = create_node; + + pw_protocol_native_ext_client_node_init(); + + spa_list_insert(core->node_factory_list.prev, &impl->this.link); + + pw_core_add_global(core, NULL, core->type.node_factory, 0, impl, NULL, &impl->this.global); + + return impl; +} + +#if 0 +static void module_destroy(struct impl *impl) +{ + pw_log_debug("module %p: destroy", impl); + + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + module_new(module->core, NULL); + return true; +} diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c new file mode 100644 index 00000000..2db5c53b --- /dev/null +++ b/src/modules/module-client-node/client-node.c @@ -0,0 +1,1242 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <dlfcn.h> +#include <sys/socket.h> +#include <sys/mman.h> +#include <sys/eventfd.h> + +#include "spa/node.h" +#include "spa/format-builder.h" +#include "spa/lib/format.h" + +#include "pipewire/pipewire.h" +#include "pipewire/interfaces.h" +#include "pipewire/transport.h" + +#include "pipewire/core.h" +#include "modules/spa/spa-node.h" +#include "client-node.h" + +/** \cond */ + +#define MAX_INPUTS 64 +#define MAX_OUTPUTS 64 + +#define MAX_BUFFERS 64 + +#define CHECK_IN_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_INPUTS) +#define CHECK_OUT_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_OUTPUTS) +#define CHECK_PORT_ID(this,d,p) (CHECK_IN_PORT_ID(this,d,p) || CHECK_OUT_PORT_ID(this,d,p)) +#define CHECK_FREE_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && !(this)->in_ports[p].valid) +#define CHECK_FREE_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && !(this)->out_ports[p].valid) +#define CHECK_FREE_PORT(this,d,p) (CHECK_FREE_IN_PORT (this,d,p) || CHECK_FREE_OUT_PORT (this,d,p)) +#define CHECK_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && (this)->in_ports[p].valid) +#define CHECK_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && (this)->out_ports[p].valid) +#define CHECK_PORT(this,d,p) (CHECK_IN_PORT (this,d,p) || CHECK_OUT_PORT (this,d,p)) + +#define CHECK_PORT_BUFFER(this,b,p) (b < p->n_buffers) + +struct proxy_buffer { + struct spa_buffer *outbuf; + struct spa_buffer buffer; + struct spa_meta metas[4]; + struct spa_data datas[4]; + off_t offset; + size_t size; + bool outstanding; +}; + +struct proxy_port { + bool valid; + struct spa_port_info info; + struct spa_format *format; + uint32_t n_formats; + struct spa_format **formats; + uint32_t n_params; + struct spa_param **params; + struct spa_port_io *io; + + uint32_t n_buffers; + struct proxy_buffer buffers[MAX_BUFFERS]; +}; + +struct proxy { + struct spa_node node; + + struct impl *impl; + + struct spa_type_map *map; + struct spa_log *log; + struct spa_loop *data_loop; + + const struct spa_node_callbacks *callbacks; + void *user_data; + + struct pw_resource *resource; + + struct spa_source data_source; + int writefd; + + uint32_t max_inputs; + uint32_t n_inputs; + uint32_t max_outputs; + uint32_t n_outputs; + struct proxy_port in_ports[MAX_INPUTS]; + struct proxy_port out_ports[MAX_OUTPUTS]; + + uint8_t format_buffer[1024]; + uint32_t seq; +}; + +struct impl { + struct pw_client_node this; + + uint32_t type_client_node; + + struct pw_core *core; + + struct proxy proxy; + + struct pw_transport *transport; + + struct pw_listener node_free; + struct pw_listener initialized; + struct pw_listener global_added; + + int fds[2]; + int other_fds[2]; +}; + +/** \endcond */ + +static int clear_buffers(struct proxy *this, struct proxy_port *port) +{ + if (port->n_buffers) { + spa_log_info(this->log, "proxy %p: clear buffers", this); + port->n_buffers = 0; + } + return SPA_RESULT_OK; +} + +static int spa_proxy_node_get_props(struct spa_node *node, struct spa_props **props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int spa_proxy_node_set_props(struct spa_node *node, const struct spa_props *props) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static inline void do_flush(struct proxy *this) +{ + uint64_t cmd = 1; + if (write(this->writefd, &cmd, 8) != 8) + spa_log_warn(this->log, "proxy %p: error writing event: %s", this, strerror(errno)); + +} + +static inline void send_need_input(struct proxy *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, proxy); + + pw_transport_add_event(impl->transport, + &SPA_EVENT_INIT(impl->core->type.event_transport.NeedInput)); + do_flush(this); +} + +static inline void send_have_output(struct proxy *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, proxy); + + pw_transport_add_event(impl->transport, + &SPA_EVENT_INIT(impl->core->type.event_transport.HaveOutput)); + do_flush(this); +} + +static int spa_proxy_node_send_command(struct spa_node *node, struct spa_command *command) +{ + struct proxy *this; + int res = SPA_RESULT_OK; + struct pw_core *core; + + if (node == NULL || command == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (this->resource == NULL) + return SPA_RESULT_OK; + + core = this->impl->core; + + if (SPA_COMMAND_TYPE(command) == core->type.command_node.ClockUpdate) { + pw_client_node_notify_node_command(this->resource, this->seq++, command); + } else { + /* send start */ + pw_client_node_notify_node_command(this->resource, this->seq, command); + if (SPA_COMMAND_TYPE(command) == core->type.command_node.Start) + send_need_input(this); + + res = SPA_RESULT_RETURN_ASYNC(this->seq++); + } + return res; +} + +static int +spa_proxy_node_set_callbacks(struct spa_node *node, + const struct spa_node_callbacks *callbacks, + void *user_data) +{ + struct proxy *this; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + this->callbacks = callbacks; + this->user_data = user_data; + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_get_n_ports(struct spa_node *node, + uint32_t *n_input_ports, + uint32_t *max_input_ports, + uint32_t *n_output_ports, + uint32_t *max_output_ports) +{ + struct proxy *this; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (n_input_ports) + *n_input_ports = this->n_inputs; + if (max_input_ports) + *max_input_ports = this->max_inputs; + if (n_output_ports) + *n_output_ports = this->n_outputs; + if (max_output_ports) + *max_output_ports = this->max_outputs; + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_get_port_ids(struct spa_node *node, + uint32_t n_input_ports, + uint32_t *input_ids, + uint32_t n_output_ports, + uint32_t *output_ids) +{ + struct proxy *this; + int c, i; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (input_ids) { + for (c = 0, i = 0; i < MAX_INPUTS && c < n_input_ports; i++) { + if (this->in_ports[i].valid) + input_ids[c++] = i; + } + } + if (output_ids) { + for (c = 0, i = 0; i < MAX_OUTPUTS && c < n_output_ports; i++) { + if (this->out_ports[i].valid) + output_ids[c++] = i; + } + } + return SPA_RESULT_OK; +} + +static void +do_update_port(struct proxy *this, + enum spa_direction direction, + uint32_t port_id, + uint32_t change_mask, + uint32_t n_possible_formats, + const struct spa_format **possible_formats, + const struct spa_format *format, + uint32_t n_params, + const struct spa_param **params, + const struct spa_port_info *info) +{ + struct proxy_port *port; + uint32_t i; + + if (direction == SPA_DIRECTION_INPUT) { + port = &this->in_ports[port_id]; + } else { + port = &this->out_ports[port_id]; + } + + if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS) { + for (i = 0; i < port->n_formats; i++) + free(port->formats[i]); + port->n_formats = n_possible_formats; + port->formats = + realloc(port->formats, port->n_formats * sizeof(struct spa_format *)); + for (i = 0; i < port->n_formats; i++) + port->formats[i] = spa_format_copy(possible_formats[i]); + } + if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_FORMAT) { + if (port->format) + free(port->format); + port->format = spa_format_copy(format); + } + + if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) { + for (i = 0; i < port->n_params; i++) + free(port->params[i]); + port->n_params = n_params; + port->params = realloc(port->params, port->n_params * sizeof(struct spa_param *)); + for (i = 0; i < port->n_params; i++) + port->params[i] = spa_param_copy(params[i]); + } + + if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_INFO && info) + port->info = *info; + + if (!port->valid) { + spa_log_info(this->log, "proxy %p: adding port %d", this, port_id); + port->format = NULL; + port->valid = true; + + if (direction == SPA_DIRECTION_INPUT) + this->n_inputs++; + else + this->n_outputs++; + } +} + +static void +clear_port(struct proxy *this, + struct proxy_port *port, enum spa_direction direction, uint32_t port_id) +{ + do_update_port(this, + direction, + port_id, + PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS | + PW_CLIENT_NODE_PORT_UPDATE_FORMAT | + PW_CLIENT_NODE_PORT_UPDATE_PARAMS | + PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, NULL, 0, NULL, NULL); + clear_buffers(this, port); +} + +static void do_uninit_port(struct proxy *this, enum spa_direction direction, uint32_t port_id) +{ + struct proxy_port *port; + + spa_log_info(this->log, "proxy %p: removing port %d", this, port_id); + if (direction == SPA_DIRECTION_INPUT) { + port = &this->in_ports[port_id]; + this->n_inputs--; + } else { + port = &this->out_ports[port_id]; + this->n_outputs--; + } + clear_port(this, port, direction, port_id); + port->valid = false; +} + +static int +spa_proxy_node_add_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) +{ + struct proxy *this; + struct proxy_port *port; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_FREE_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + clear_port(this, port, direction, port_id); + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_remove_port(struct spa_node *node, enum spa_direction direction, uint32_t port_id) +{ + struct proxy *this; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + do_uninit_port(this, direction, port_id); + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_port_enum_formats(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + struct spa_format **format, + const struct spa_format *filter, + uint32_t index) +{ + struct proxy *this; + struct proxy_port *port; + struct spa_format *fmt; + struct spa_pod_builder b = { NULL, }; + int res; + uint32_t count, match = 0; + + if (node == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + + count = match = filter ? 0 : index; + + next: + if (count >= port->n_formats) + return SPA_RESULT_ENUM_END; + + fmt = port->formats[count++]; + + spa_pod_builder_init(&b, this->format_buffer, sizeof(this->format_buffer)); + + if ((res = spa_format_filter(fmt, filter, &b)) != SPA_RESULT_OK || match++ != index) + goto next; + + *format = SPA_POD_BUILDER_DEREF(&b, 0, struct spa_format); + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_port_set_format(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, uint32_t flags, const struct spa_format *format) +{ + struct proxy *this; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + if (this->resource == NULL) + return SPA_RESULT_OK; + + pw_client_node_notify_set_format(this->resource, + this->seq, direction, port_id, flags, format); + + return SPA_RESULT_RETURN_ASYNC(this->seq++); +} + +static int +spa_proxy_node_port_get_format(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, const struct spa_format **format) +{ + struct proxy *this; + struct proxy_port *port; + + if (node == NULL || format == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + + if (!port->format) + return SPA_RESULT_NO_FORMAT; + + *format = port->format; + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_port_get_info(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, const struct spa_port_info **info) +{ + struct proxy *this; + struct proxy_port *port; + + if (node == NULL || info == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + + *info = &port->info; + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_port_enum_params(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, uint32_t index, struct spa_param **param) +{ + struct proxy *this; + struct proxy_port *port; + + spa_return_val_if_fail(node != NULL, SPA_RESULT_INVALID_ARGUMENTS); + spa_return_val_if_fail(param != NULL, SPA_RESULT_INVALID_ARGUMENTS); + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), SPA_RESULT_INVALID_PORT); + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + + if (index >= port->n_params) + return SPA_RESULT_ENUM_END; + + *param = port->params[index]; + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_port_set_param(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, const struct spa_param *param) +{ + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int +spa_proxy_node_port_set_io(struct spa_node *node, + enum spa_direction direction, uint32_t port_id, struct spa_port_io *io) +{ + struct proxy *this; + struct proxy_port *port; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + port->io = io; + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_port_use_buffers(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + struct proxy *this; + struct impl *impl; + struct proxy_port *port; + uint32_t i, j; + size_t n_mem; + struct pw_client_node_buffer *mb; + struct spa_meta_shared *msh; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + impl = this->impl; + spa_log_info(this->log, "proxy %p: use buffers %p %u", this, buffers, n_buffers); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + + if (!port->format) + return SPA_RESULT_NO_FORMAT; + + clear_buffers(this, port); + + if (n_buffers > 0) { + mb = alloca(n_buffers * sizeof(struct pw_client_node_buffer)); + } else { + mb = NULL; + } + + port->n_buffers = n_buffers; + + if (this->resource == NULL) + return SPA_RESULT_OK; + + n_mem = 0; + for (i = 0; i < n_buffers; i++) { + struct proxy_buffer *b = &port->buffers[i]; + + msh = spa_buffer_find_meta(buffers[i], impl->core->type.meta.Shared); + if (msh == NULL) { + spa_log_error(this->log, "missing shared metadata on buffer %d", i); + return SPA_RESULT_ERROR; + } + + b->outbuf = buffers[i]; + memcpy(&b->buffer, buffers[i], sizeof(struct spa_buffer)); + b->buffer.datas = b->datas; + b->buffer.metas = b->metas; + + mb[i].buffer = &b->buffer; + mb[i].mem_id = n_mem++; + mb[i].offset = 0; + mb[i].size = msh->size; + + pw_client_node_notify_add_mem(this->resource, + direction, + port_id, + mb[i].mem_id, + impl->core->type.data.MemFd, + msh->fd, msh->flags, msh->offset, msh->size); + + for (j = 0; j < buffers[i]->n_metas; j++) { + memcpy(&b->buffer.metas[j], &buffers[i]->metas[j], sizeof(struct spa_meta)); + } + + for (j = 0; j < buffers[i]->n_datas; j++) { + struct spa_data *d = &buffers[i]->datas[j]; + + memcpy(&b->buffer.datas[j], d, sizeof(struct spa_data)); + + if (d->type == impl->core->type.data.DmaBuf || + d->type == impl->core->type.data.MemFd) { + pw_client_node_notify_add_mem(this->resource, + direction, + port_id, + n_mem, + d->type, + d->fd, + d->flags, d->mapoffset, d->maxsize); + b->buffer.datas[j].type = impl->core->type.data.Id; + b->buffer.datas[j].data = SPA_UINT32_TO_PTR(n_mem); + n_mem++; + } else if (d->type == impl->core->type.data.MemPtr) { + b->buffer.datas[j].data = SPA_INT_TO_PTR(b->size); + b->size += d->maxsize; + } else { + b->buffer.datas[j].type = SPA_ID_INVALID; + b->buffer.datas[j].data = 0; + spa_log_error(this->log, "invalid memory type %d", d->type); + } + } + } + + pw_client_node_notify_use_buffers(this->resource, + this->seq, direction, port_id, n_buffers, mb); + + return SPA_RESULT_RETURN_ASYNC(this->seq++); +} + +static int +spa_proxy_node_port_alloc_buffers(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, + struct spa_param **params, + uint32_t n_params, + struct spa_buffer **buffers, + uint32_t *n_buffers) +{ + struct proxy *this; + struct proxy_port *port; + + if (node == NULL || buffers == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + if (!CHECK_PORT(this, direction, port_id)) + return SPA_RESULT_INVALID_PORT; + + port = + direction == SPA_DIRECTION_INPUT ? &this->in_ports[port_id] : &this->out_ports[port_id]; + + if (!port->format) + return SPA_RESULT_NO_FORMAT; + + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int +spa_proxy_node_port_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id) +{ + struct proxy *this; + struct impl *impl; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + impl = this->impl; + + if (!CHECK_OUT_PORT(this, SPA_DIRECTION_OUTPUT, port_id)) + return SPA_RESULT_INVALID_PORT; + + spa_log_trace(this->log, "reuse buffer %d", buffer_id); + { + struct pw_event_transport_reuse_buffer rb = PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT + (impl->core->type.event_transport.ReuseBuffer, port_id, buffer_id); + pw_transport_add_event(impl->transport, (struct spa_event *) &rb); + } + + return SPA_RESULT_OK; +} + +static int +spa_proxy_node_port_send_command(struct spa_node *node, + enum spa_direction direction, + uint32_t port_id, struct spa_command *command) +{ + struct proxy *this; + + if (node == NULL || command == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + + spa_log_warn(this->log, "unhandled command %d", SPA_COMMAND_TYPE(command)); + return SPA_RESULT_NOT_IMPLEMENTED; +} + +static int spa_proxy_node_process_input(struct spa_node *node) +{ + struct impl *impl; + struct proxy *this; + int i; + + if (node == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + impl = this->impl; + + for (i = 0; i < MAX_INPUTS; i++) { + struct spa_port_io *io = this->in_ports[i].io; + + if (!io) + continue; + + pw_log_trace("%d %d", io->status, io->buffer_id); + + impl->transport->inputs[i] = *io; + io->status = SPA_RESULT_OK; + } + send_have_output(this); + + if (this->callbacks->need_input) + return SPA_RESULT_OK; + else + return SPA_RESULT_NEED_BUFFER; +} + +static int spa_proxy_node_process_output(struct spa_node *node) +{ + struct proxy *this; + struct impl *impl; + int i; + bool send_need = false, flush = false; + + this = SPA_CONTAINER_OF(node, struct proxy, node); + impl = this->impl; + + for (i = 0; i < MAX_OUTPUTS; i++) { + struct spa_port_io *io = this->out_ports[i].io, tmp; + + if (!io) + continue; + + if (io->buffer_id != SPA_ID_INVALID) { + struct pw_event_transport_reuse_buffer rb = + PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT(impl->core->type.event_transport. + ReuseBuffer, i, io->buffer_id); + + spa_log_trace(this->log, "reuse buffer %d", io->buffer_id); + + pw_transport_add_event(impl->transport, (struct spa_event *) &rb); + io->buffer_id = SPA_ID_INVALID; + flush = true; + } + + tmp = impl->transport->outputs[i]; + impl->transport->outputs[i] = *io; + + pw_log_trace("%d %d %d %d", io->status, io->buffer_id, tmp.status, tmp.buffer_id); + + if (io->status == SPA_RESULT_NEED_BUFFER) + send_need = true; + + *io = tmp; + } + if (send_need) + send_need_input(this); + else if (flush) + do_flush(this); + + return SPA_RESULT_HAVE_BUFFER; +} + +static int handle_node_event(struct proxy *this, struct spa_event *event) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, proxy); + int i; + + if (SPA_EVENT_TYPE(event) == impl->core->type.event_transport.HaveOutput) { + for (i = 0; i < MAX_OUTPUTS; i++) { + struct spa_port_io *io = this->out_ports[i].io; + + if (!io) + continue; + + *io = impl->transport->outputs[i]; + pw_log_trace("%d %d", io->status, io->buffer_id); + } + this->callbacks->have_output(&this->node, this->user_data); + } else if (SPA_EVENT_TYPE(event) == impl->core->type.event_transport.NeedInput) { + this->callbacks->need_input(&this->node, this->user_data); + } else if (SPA_EVENT_TYPE(event) == impl->core->type.event_transport.ReuseBuffer) { + struct pw_event_transport_reuse_buffer *p = + (struct pw_event_transport_reuse_buffer *) event; + this->callbacks->reuse_buffer(&this->node, p->body.port_id.value, + p->body.buffer_id.value, this->user_data); + } + return SPA_RESULT_OK; +} + +static void +client_node_done(void *object, int seq, int res) +{ + struct pw_resource *resource = object; + struct pw_client_node *node = resource->object; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct proxy *this = &impl->proxy; + + this->callbacks->done(&this->node, seq, res, this->user_data); +} + + +static void +client_node_update(void *object, + uint32_t change_mask, + uint32_t max_input_ports, + uint32_t max_output_ports, const struct spa_props *props) +{ + struct pw_resource *resource = object; + struct pw_client_node *node = resource->object; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct proxy *this = &impl->proxy; + + if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_INPUTS) + this->max_inputs = max_input_ports; + if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS) + this->max_outputs = max_output_ports; + + spa_log_info(this->log, "proxy %p: got node update max_in %u, max_out %u", this, + this->max_inputs, this->max_outputs); +} + +static void +client_node_port_update(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t change_mask, + uint32_t n_possible_formats, + const struct spa_format **possible_formats, + const struct spa_format *format, + uint32_t n_params, + const struct spa_param **params, const struct spa_port_info *info) +{ + struct pw_resource *resource = object; + struct pw_client_node *node = resource->object; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct proxy *this = &impl->proxy; + bool remove; + + spa_log_info(this->log, "proxy %p: got port update", this); + if (!CHECK_PORT_ID(this, direction, port_id)) + return; + + remove = (change_mask == 0); + + if (remove) { + do_uninit_port(this, direction, port_id); + } else { + do_update_port(this, + direction, + port_id, + change_mask, + n_possible_formats, + possible_formats, format, n_params, params, info); + } +} + +static void client_node_event(void *object, struct spa_event *event) +{ + struct pw_resource *resource = object; + struct pw_client_node *node = resource->object; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct proxy *this = &impl->proxy; + + this->callbacks->event(&this->node, event, this->user_data); +} + +static void client_node_destroy(void *object) +{ + struct pw_resource *resource = object; + struct pw_client_node *node = resource->object; + pw_client_node_destroy(node); +} + +static struct pw_client_node_methods client_node_methods = { + &client_node_done, + &client_node_update, + &client_node_port_update, + &client_node_event, + &client_node_destroy, +}; + +static void proxy_on_data_fd_events(struct spa_source *source) +{ + struct proxy *this = source->data; + struct impl *impl = this->impl; + + if (source->rmask & (SPA_IO_ERR | SPA_IO_HUP)) { + spa_log_warn(this->log, "proxy %p: got error", this); + return; + } + + if (source->rmask & SPA_IO_IN) { + struct spa_event event; + uint64_t cmd; + + if (read(this->data_source.fd, &cmd, 8) != 8) + spa_log_warn(this->log, "proxy %p: error reading event: %s", + this, strerror(errno)); + + while (pw_transport_next_event(impl->transport, &event) == SPA_RESULT_OK) { + struct spa_event *ev = alloca(SPA_POD_SIZE(&event)); + pw_transport_parse_event(impl->transport, ev); + handle_node_event(this, ev); + } + } +} + +static const struct spa_node proxy_node = { + SPA_VERSION_NODE, + NULL, + spa_proxy_node_get_props, + spa_proxy_node_set_props, + spa_proxy_node_send_command, + spa_proxy_node_set_callbacks, + spa_proxy_node_get_n_ports, + spa_proxy_node_get_port_ids, + spa_proxy_node_add_port, + spa_proxy_node_remove_port, + spa_proxy_node_port_enum_formats, + spa_proxy_node_port_set_format, + spa_proxy_node_port_get_format, + spa_proxy_node_port_get_info, + spa_proxy_node_port_enum_params, + spa_proxy_node_port_set_param, + spa_proxy_node_port_use_buffers, + spa_proxy_node_port_alloc_buffers, + spa_proxy_node_port_set_io, + spa_proxy_node_port_reuse_buffer, + spa_proxy_node_port_send_command, + spa_proxy_node_process_input, + spa_proxy_node_process_output, +}; + +static int +proxy_init(struct proxy *this, + struct spa_dict *info, + const struct spa_support *support, + uint32_t n_support) +{ + uint32_t i; + + for (i = 0; i < n_support; i++) { + if (strcmp(support[i].type, SPA_TYPE__Log) == 0) + this->log = support[i].data; + else if (strcmp(support[i].type, SPA_TYPE_LOOP__DataLoop) == 0) + this->data_loop = support[i].data; + } + if (this->data_loop == NULL) { + spa_log_error(this->log, "a data-loop is needed"); + } + + this->node = proxy_node; + + this->data_source.func = proxy_on_data_fd_events; + this->data_source.data = this; + this->data_source.fd = -1; + this->data_source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP; + this->data_source.rmask = 0; + + return SPA_RESULT_RETURN_ASYNC(this->seq++); +} + +static void on_initialized(struct pw_listener *listener, struct pw_node *node) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, initialized); + struct pw_client_node *this = &impl->this; + struct pw_transport_info info; + int readfd, writefd; + + if (this->resource == NULL) + return; + + impl->transport = pw_transport_new(node->info.max_input_ports, node->info.max_output_ports); + impl->transport->area->n_input_ports = node->info.n_input_ports; + impl->transport->area->n_output_ports = node->info.n_output_ports; + + pw_client_node_get_fds(this, &readfd, &writefd); + pw_transport_get_info(impl->transport, &info); + + pw_client_node_notify_transport(this->resource, node->global->id, + readfd, writefd, info.memfd, info.offset, info.size); +} + +static void +on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added); + + if (global->object == impl->this.node) + global->owner = impl->this.resource; +} + +static int proxy_clear(struct proxy *this) +{ + uint32_t i; + + for (i = 0; i < MAX_INPUTS; i++) { + if (this->in_ports[i].valid) + clear_port(this, &this->in_ports[i], SPA_DIRECTION_INPUT, i); + } + for (i = 0; i < MAX_OUTPUTS; i++) { + if (this->out_ports[i].valid) + clear_port(this, &this->out_ports[i], SPA_DIRECTION_OUTPUT, i); + } + + return SPA_RESULT_OK; +} + +static void client_node_resource_destroy(struct pw_resource *resource) +{ + struct pw_client_node *this = resource->object; + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct proxy *proxy = &impl->proxy; + + pw_log_debug("client-node %p: destroy", impl); + pw_signal_emit(&this->destroy_signal, this); + + impl->proxy.resource = this->resource = NULL; + + pw_signal_remove(&impl->global_added); + pw_signal_remove(&impl->initialized); + + if (proxy->data_source.fd != -1) + spa_loop_remove_source(proxy->data_loop, &proxy->data_source); + + pw_node_destroy(this->node); +} + +static void on_node_free(struct pw_listener *listener, struct pw_node *node) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, node_free); + + pw_log_debug("client-node %p: free", &impl->this); + proxy_clear(&impl->proxy); + + pw_signal_remove(&impl->node_free); + + if (impl->transport) + pw_transport_destroy(impl->transport); + + if (impl->fds[0] != -1) + close(impl->fds[0]); + if (impl->fds[1] != -1) + close(impl->fds[1]); + free(impl); +} + +/** Create a new client node + * \param client an owner \ref pw_client + * \param id an id + * \param name a name + * \param properties extra properties + * \return a newly allocated client node + * + * Create a new \ref pw_node. + * + * \memberof pw_client_node + */ +struct pw_client_node *pw_client_node_new(struct pw_resource *resource, + const char *name, + struct pw_properties *properties) +{ + struct impl *impl; + struct pw_client_node *this; + struct pw_core *core = resource->client->core; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return NULL; + + this = &impl->this; + + impl->core = core; + impl->fds[0] = impl->fds[1] = -1; + pw_log_debug("client-node %p: new", impl); + + impl->type_client_node = spa_type_map_get_id(core->type.map, PIPEWIRE_TYPE__ClientNode); + + pw_signal_init(&this->destroy_signal); + + proxy_init(&impl->proxy, NULL, core->support, core->n_support); + impl->proxy.impl = impl; + + this->resource = resource; + this->node = pw_spa_node_new(core, + this->resource, + name, + true, + &impl->proxy.node, + NULL, + properties); + if (this->node == NULL) + goto error_no_node; + + pw_resource_set_implementation(this->resource, + this, + PW_VERSION_CLIENT_NODE, + &client_node_methods, + (pw_destroy_t) client_node_resource_destroy); + + impl->proxy.resource = this->resource; + + pw_signal_add(&this->node->free_signal, &impl->node_free, on_node_free); + pw_signal_add(&this->node->initialized, &impl->initialized, on_initialized); + pw_signal_add(&impl->core->global_added, &impl->global_added, on_global_added); + + return this; + + error_no_node: + pw_resource_destroy(this->resource); + proxy_clear(&impl->proxy); + free(impl); + return NULL; +} + +/** Destroy a client node + * \param node the client node to destroy + * \memberof pw_client_node + */ +void pw_client_node_destroy(struct pw_client_node *node) +{ + pw_resource_destroy(node->resource); +} + +/** Get the set of fds for this \ref pw_client_node + * + * \param node a \ref pw_client_node + * \param[out] readfd an fd for reading + * \param[out] writefd an fd for writing + * \return 0 on success < 0 on error + * + * Create or return a previously created set of fds for \a node. + * + * \memberof pw_client_node + */ +int pw_client_node_get_fds(struct pw_client_node *node, int *readfd, int *writefd) +{ + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + + if (impl->fds[0] == -1) { +#if 0 + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, impl->fds) != + 0) + return SPA_RESULT_ERRNO; + + impl->proxy.data_source.fd = impl->fds[0]; + impl->proxy.writefd = impl->fds[0]; + impl->other_fds[0] = impl->fds[1]; + impl->other_fds[1] = impl->fds[1]; +#else + impl->fds[0] = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + impl->fds[1] = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + impl->proxy.data_source.fd = impl->fds[0]; + impl->proxy.writefd = impl->fds[1]; + impl->other_fds[0] = impl->fds[1]; + impl->other_fds[1] = impl->fds[0]; +#endif + + spa_loop_add_source(impl->proxy.data_loop, &impl->proxy.data_source); + pw_log_debug("client-node %p: add data fd %d", node, impl->proxy.data_source.fd); + } + *readfd = impl->other_fds[0]; + *writefd = impl->other_fds[1]; + + return SPA_RESULT_OK; +} diff --git a/src/modules/module-client-node/client-node.h b/src/modules/module-client-node/client-node.h new file mode 100644 index 00000000..1dcaec8c --- /dev/null +++ b/src/modules/module-client-node/client-node.h @@ -0,0 +1,57 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_CLIENT_NODE_H__ +#define __PIPEWIRE_CLIENT_NODE_H__ + +#include <pipewire/node.h> +#include <extensions/client-node.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \class pw_client_node + * + * PipeWire client node interface + */ +struct pw_client_node { + struct pw_node *node; + + struct pw_resource *resource; + + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_client_node *node)); +}; + +struct pw_client_node * +pw_client_node_new(struct pw_resource *resource, + const char *name, + struct pw_properties *properties); + +void +pw_client_node_destroy(struct pw_client_node *node); + +int +pw_client_node_get_fds(struct pw_client_node *node, int *readfd, int *writefd); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_CLIENT_NODE_H__ */ diff --git a/src/modules/module-client-node/protocol-native.c b/src/modules/module-client-node/protocol-native.c new file mode 100644 index 00000000..d7933f77 --- /dev/null +++ b/src/modules/module-client-node/protocol-native.c @@ -0,0 +1,873 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <errno.h> + +#include "spa/pod-iter.h" + +#include "pipewire/pipewire.h" +#include "pipewire/interfaces.h" +#include "pipewire/protocol.h" +#include "pipewire/client.h" +#include "extensions/client-node.h" + +#include "modules/module-protocol-native/connection.h" + +/** \cond */ + +typedef bool(*demarshal_func_t) (void *object, void *data, size_t size); + +/** \endcond */ + +static void +client_node_marshal_done(void *object, int seq, int res) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_DONE); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, seq, + SPA_POD_TYPE_INT, res); + + pw_connection_end_write(connection, b); +} + + +static void +client_node_marshal_update(void *object, + uint32_t change_mask, + uint32_t max_input_ports, + uint32_t max_output_ports, const struct spa_props *props) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_UPDATE); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, change_mask, + SPA_POD_TYPE_INT, max_input_ports, + SPA_POD_TYPE_INT, max_output_ports, SPA_POD_TYPE_POD, props); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_port_update(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t change_mask, + uint32_t n_possible_formats, + const struct spa_format **possible_formats, + const struct spa_format *format, + uint32_t n_params, + const struct spa_param **params, const struct spa_port_info *info) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f[2]; + int i; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_PORT_UPDATE); + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f[0], + SPA_POD_TYPE_INT, direction, + SPA_POD_TYPE_INT, port_id, + SPA_POD_TYPE_INT, change_mask, SPA_POD_TYPE_INT, n_possible_formats, 0); + + for (i = 0; i < n_possible_formats; i++) + spa_pod_builder_add(b, SPA_POD_TYPE_POD, possible_formats[i], 0); + + spa_pod_builder_add(b, SPA_POD_TYPE_POD, format, SPA_POD_TYPE_INT, n_params, 0); + + for (i = 0; i < n_params; i++) { + const struct spa_param *p = params[i]; + spa_pod_builder_add(b, SPA_POD_TYPE_POD, p, 0); + } + + if (info) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f[1], + SPA_POD_TYPE_INT, info->flags, SPA_POD_TYPE_INT, info->rate, 0); + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f[1], 0); + } else { + spa_pod_builder_add(b, SPA_POD_TYPE_POD, NULL, 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f[0], 0); + + pw_connection_end_write(connection, b); +} + +static void client_node_marshal_event_method(void *object, struct spa_event *event) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_EVENT); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_POD, event); + + pw_connection_end_write(connection, b); +} + +static void client_node_marshal_destroy(void *object) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CLIENT_NODE_METHOD_DESTROY); + + spa_pod_builder_struct(b, &f, 0); + + pw_connection_end_write(connection, b); +} + +static bool client_node_demarshal_set_props(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t seq; + const struct spa_props *props = NULL; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &seq, + -SPA_POD_TYPE_OBJECT, &props, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->set_props(proxy, seq, props); + return true; +} + +static bool client_node_demarshal_event_event(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + const struct spa_event *event; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &event, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->event(proxy, event); + return true; +} + +static bool client_node_demarshal_add_port(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + int32_t seq, direction, port_id; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &seq, + SPA_POD_TYPE_INT, &direction, SPA_POD_TYPE_INT, &port_id, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->add_port(proxy, seq, direction, + port_id); + return true; +} + +static bool client_node_demarshal_remove_port(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + int32_t seq, direction, port_id; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &seq, + SPA_POD_TYPE_INT, &direction, SPA_POD_TYPE_INT, &port_id, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->remove_port(proxy, seq, direction, + port_id); + return true; +} + +static bool client_node_demarshal_set_format(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t seq, direction, port_id, flags; + const struct spa_format *format = NULL; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &seq, + SPA_POD_TYPE_INT, &direction, + SPA_POD_TYPE_INT, &port_id, + SPA_POD_TYPE_INT, &flags, + -SPA_POD_TYPE_OBJECT, &format, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->set_format(proxy, seq, direction, + port_id, flags, + format); + return true; +} + +static bool client_node_demarshal_set_param(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t seq, direction, port_id; + const struct spa_param *param = NULL; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &seq, + SPA_POD_TYPE_INT, &direction, + SPA_POD_TYPE_INT, &port_id, + -SPA_POD_TYPE_OBJECT, ¶m, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->set_param(proxy, seq, direction, + port_id, param); + return true; +} + +static bool client_node_demarshal_add_mem(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + struct pw_connection *connection = proxy->remote->protocol_private; + uint32_t direction, port_id, mem_id, type, memfd_idx, flags, offset, sz; + int memfd; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &direction, + SPA_POD_TYPE_INT, &port_id, + SPA_POD_TYPE_INT, &mem_id, + SPA_POD_TYPE_ID, &type, + SPA_POD_TYPE_INT, &memfd_idx, + SPA_POD_TYPE_INT, &flags, + SPA_POD_TYPE_INT, &offset, SPA_POD_TYPE_INT, &sz, 0)) + return false; + + memfd = pw_connection_get_fd(connection, memfd_idx); + + ((struct pw_client_node_events *) proxy->implementation)->add_mem(proxy, + direction, + port_id, + mem_id, + type, + memfd, flags, offset, sz); + return true; +} + +static bool client_node_demarshal_use_buffers(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t seq, direction, port_id, n_buffers, data_id; + struct pw_client_node_buffer *buffers; + int i, j; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &seq, + SPA_POD_TYPE_INT, &direction, + SPA_POD_TYPE_INT, &port_id, SPA_POD_TYPE_INT, &n_buffers, 0)) + return false; + + buffers = alloca(sizeof(struct pw_client_node_buffer) * n_buffers); + for (i = 0; i < n_buffers; i++) { + struct spa_buffer *buf = buffers[i].buffer = alloca(sizeof(struct spa_buffer)); + + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &buffers[i].mem_id, + SPA_POD_TYPE_INT, &buffers[i].offset, + SPA_POD_TYPE_INT, &buffers[i].size, + SPA_POD_TYPE_INT, &buf->id, + SPA_POD_TYPE_INT, &buf->n_metas, 0)) + return false; + + buf->metas = alloca(sizeof(struct spa_meta) * buf->n_metas); + for (j = 0; j < buf->n_metas; j++) { + struct spa_meta *m = &buf->metas[j]; + + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_ID, &m->type, + SPA_POD_TYPE_INT, &m->size, 0)) + return false; + } + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &buf->n_datas, 0)) + return false; + + buf->datas = alloca(sizeof(struct spa_data) * buf->n_datas); + for (j = 0; j < buf->n_datas; j++) { + struct spa_data *d = &buf->datas[j]; + + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_ID, &d->type, + SPA_POD_TYPE_INT, &data_id, + SPA_POD_TYPE_INT, &d->flags, + SPA_POD_TYPE_INT, &d->mapoffset, + SPA_POD_TYPE_INT, &d->maxsize, 0)) + return false; + + d->data = SPA_UINT32_TO_PTR(data_id); + } + } + ((struct pw_client_node_events *) proxy->implementation)->use_buffers(proxy, + seq, + direction, + port_id, + n_buffers, buffers); + return true; +} + +static bool client_node_demarshal_node_command(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + const struct spa_command *command; + uint32_t seq; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &seq, SPA_POD_TYPE_OBJECT, &command, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->node_command(proxy, seq, command); + return true; +} + +static bool client_node_demarshal_port_command(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + const struct spa_command *command; + uint32_t direction, port_id; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &direction, + SPA_POD_TYPE_INT, &port_id, + SPA_POD_TYPE_OBJECT, &command, 0)) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->port_command(proxy, + direction, + port_id, + command); + return true; +} + +static bool client_node_demarshal_transport(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + struct pw_connection *connection = proxy->remote->protocol_private; + uint32_t node_id, ridx, widx, memfd_idx, offset, sz; + int readfd, writefd, memfd; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &node_id, + SPA_POD_TYPE_INT, &ridx, + SPA_POD_TYPE_INT, &widx, + SPA_POD_TYPE_INT, &memfd_idx, + SPA_POD_TYPE_INT, &offset, + SPA_POD_TYPE_INT, &sz, 0)) + return false; + + readfd = pw_connection_get_fd(connection, ridx); + writefd = pw_connection_get_fd(connection, widx); + memfd = pw_connection_get_fd(connection, memfd_idx); + if (readfd == -1 || writefd == -1 || memfd_idx == -1) + return false; + + ((struct pw_client_node_events *) proxy->implementation)->transport(proxy, node_id, + readfd, writefd, + memfd, offset, sz); + return true; +} + +static void +client_node_marshal_set_props(void *object, uint32_t seq, const struct spa_props *props) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_SET_PROPS); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, seq, + SPA_POD_TYPE_POD, props); + + pw_connection_end_write(connection, b); +} + +static void client_node_marshal_event_event(void *object, const struct spa_event *event) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_EVENT); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_POD, event); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_add_port(void *object, + uint32_t seq, enum spa_direction direction, uint32_t port_id) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_ADD_PORT); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, seq, + SPA_POD_TYPE_INT, direction, SPA_POD_TYPE_INT, port_id); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_remove_port(void *object, + uint32_t seq, enum spa_direction direction, uint32_t port_id) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_REMOVE_PORT); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, seq, + SPA_POD_TYPE_INT, direction, SPA_POD_TYPE_INT, port_id); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_set_format(void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_format *format) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_SET_FORMAT); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, seq, + SPA_POD_TYPE_INT, direction, + SPA_POD_TYPE_INT, port_id, + SPA_POD_TYPE_INT, flags, + SPA_POD_TYPE_POD, format); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_set_param(void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + const struct spa_param *param) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_SET_PARAM); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, seq, + SPA_POD_TYPE_INT, direction, + SPA_POD_TYPE_INT, port_id, + SPA_POD_TYPE_POD, param); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_add_mem(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t mem_id, + uint32_t type, + int memfd, uint32_t flags, uint32_t offset, uint32_t size) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_ADD_MEM); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, direction, + SPA_POD_TYPE_INT, port_id, + SPA_POD_TYPE_INT, mem_id, + SPA_POD_TYPE_ID, type, + SPA_POD_TYPE_INT, pw_connection_add_fd(connection, memfd), + SPA_POD_TYPE_INT, flags, + SPA_POD_TYPE_INT, offset, SPA_POD_TYPE_INT, size); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_use_buffers(void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + uint32_t n_buffers, struct pw_client_node_buffer *buffers) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i, j; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_USE_BUFFERS); + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, seq, + SPA_POD_TYPE_INT, direction, + SPA_POD_TYPE_INT, port_id, SPA_POD_TYPE_INT, n_buffers, 0); + + for (i = 0; i < n_buffers; i++) { + struct spa_buffer *buf = buffers[i].buffer; + + spa_pod_builder_add(b, + SPA_POD_TYPE_INT, buffers[i].mem_id, + SPA_POD_TYPE_INT, buffers[i].offset, + SPA_POD_TYPE_INT, buffers[i].size, + SPA_POD_TYPE_INT, buf->id, SPA_POD_TYPE_INT, buf->n_metas, 0); + + for (j = 0; j < buf->n_metas; j++) { + struct spa_meta *m = &buf->metas[j]; + spa_pod_builder_add(b, + SPA_POD_TYPE_ID, m->type, SPA_POD_TYPE_INT, m->size, 0); + } + spa_pod_builder_add(b, SPA_POD_TYPE_INT, buf->n_datas, 0); + for (j = 0; j < buf->n_datas; j++) { + struct spa_data *d = &buf->datas[j]; + spa_pod_builder_add(b, + SPA_POD_TYPE_ID, d->type, + SPA_POD_TYPE_INT, SPA_PTR_TO_UINT32(d->data), + SPA_POD_TYPE_INT, d->flags, + SPA_POD_TYPE_INT, d->mapoffset, + SPA_POD_TYPE_INT, d->maxsize, 0); + } + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_node_command(void *object, uint32_t seq, const struct spa_command *command) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_NODE_COMMAND); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, seq, SPA_POD_TYPE_POD, command); + + pw_connection_end_write(connection, b); +} + +static void +client_node_marshal_port_command(void *object, + uint32_t direction, + uint32_t port_id, + const struct spa_command *command) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_PORT_COMMAND); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, direction, + SPA_POD_TYPE_INT, port_id, + SPA_POD_TYPE_POD, command); + + pw_connection_end_write(connection, b); +} + +static void client_node_marshal_transport(void *object, uint32_t node_id, int readfd, int writefd, + int memfd, uint32_t offset, uint32_t size) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_NODE_EVENT_TRANSPORT); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, node_id, + SPA_POD_TYPE_INT, pw_connection_add_fd(connection, readfd), + SPA_POD_TYPE_INT, pw_connection_add_fd(connection, writefd), + SPA_POD_TYPE_INT, pw_connection_add_fd(connection, memfd), + SPA_POD_TYPE_INT, offset, SPA_POD_TYPE_INT, size); + + pw_connection_end_write(connection, b); +} + + +static bool client_node_demarshal_done(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t seq, res; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &seq, + SPA_POD_TYPE_INT, &res, 0)) + return false; + + ((struct pw_client_node_methods *) resource->implementation)->done(resource, seq, res); + return true; +} + +static bool client_node_demarshal_update(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t change_mask, max_input_ports, max_output_ports; + const struct spa_props *props; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &change_mask, + SPA_POD_TYPE_INT, &max_input_ports, + SPA_POD_TYPE_INT, &max_output_ports, -SPA_POD_TYPE_OBJECT, &props, 0)) + return false; + + ((struct pw_client_node_methods *) resource->implementation)->update(resource, change_mask, + max_input_ports, + max_output_ports, + props); + return true; +} + +static bool client_node_demarshal_port_update(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t i, direction, port_id, change_mask, n_possible_formats, n_params; + const struct spa_param **params = NULL; + const struct spa_format **possible_formats = NULL, *format = NULL; + struct spa_port_info info, *infop = NULL; + struct spa_pod *ipod; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &direction, + SPA_POD_TYPE_INT, &port_id, + SPA_POD_TYPE_INT, &change_mask, + SPA_POD_TYPE_INT, &n_possible_formats, 0)) + return false; + + possible_formats = alloca(n_possible_formats * sizeof(struct spa_format *)); + for (i = 0; i < n_possible_formats; i++) + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &possible_formats[i], 0)) + return false; + + if (!spa_pod_iter_get(&it, -SPA_POD_TYPE_OBJECT, &format, SPA_POD_TYPE_INT, &n_params, 0)) + return false; + + params = alloca(n_params * sizeof(struct spa_param *)); + for (i = 0; i < n_params; i++) + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, ¶ms[i], 0)) + return false; + + if (!spa_pod_iter_get(&it, -SPA_POD_TYPE_STRUCT, &ipod, 0)) + return false; + + if (ipod) { + struct spa_pod_iter it2; + infop = &info; + + if (!spa_pod_iter_pod(&it2, ipod) || + !spa_pod_iter_get(&it2, + SPA_POD_TYPE_INT, &info.flags, + SPA_POD_TYPE_INT, &info.rate, 0)) + return false; + } + + ((struct pw_client_node_methods *) resource->implementation)->port_update(resource, + direction, + port_id, + change_mask, + n_possible_formats, + possible_formats, + format, + n_params, + params, infop); + return true; +} + +static bool client_node_demarshal_event_method(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + struct spa_event *event; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &event, 0)) + return false; + + ((struct pw_client_node_methods *) resource->implementation)->event(resource, event); + return true; +} + +static bool client_node_demarshal_destroy(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + + if (!spa_pod_iter_struct(&it, data, size)) + return false; + + ((struct pw_client_node_methods *) resource->implementation)->destroy(resource); + return true; +} + +static const struct pw_client_node_methods pw_protocol_native_client_client_node_methods = { + &client_node_marshal_done, + &client_node_marshal_update, + &client_node_marshal_port_update, + &client_node_marshal_event_method, + &client_node_marshal_destroy +}; + +static const demarshal_func_t pw_protocol_native_client_client_node_demarshal[] = { + &client_node_demarshal_transport, + &client_node_demarshal_set_props, + &client_node_demarshal_event_event, + &client_node_demarshal_add_port, + &client_node_demarshal_remove_port, + &client_node_demarshal_set_format, + &client_node_demarshal_set_param, + &client_node_demarshal_add_mem, + &client_node_demarshal_use_buffers, + &client_node_demarshal_node_command, + &client_node_demarshal_port_command, +}; + +static const struct pw_interface pw_protocol_native_client_client_node_interface = { + PIPEWIRE_TYPE_NODE_BASE "Client", + PW_VERSION_CLIENT_NODE, + PW_CLIENT_NODE_METHOD_NUM, &pw_protocol_native_client_client_node_methods, + PW_CLIENT_NODE_EVENT_NUM, pw_protocol_native_client_client_node_demarshal, +}; + +static const demarshal_func_t pw_protocol_native_server_client_node_demarshal[] = { + &client_node_demarshal_done, + &client_node_demarshal_update, + &client_node_demarshal_port_update, + &client_node_demarshal_event_method, + &client_node_demarshal_destroy, +}; + +static const struct pw_client_node_events pw_protocol_native_server_client_node_events = { + &client_node_marshal_transport, + &client_node_marshal_set_props, + &client_node_marshal_event_event, + &client_node_marshal_add_port, + &client_node_marshal_remove_port, + &client_node_marshal_set_format, + &client_node_marshal_set_param, + &client_node_marshal_add_mem, + &client_node_marshal_use_buffers, + &client_node_marshal_node_command, + &client_node_marshal_port_command, +}; + +const struct pw_interface pw_protocol_native_server_client_node_interface = { + PIPEWIRE_TYPE_NODE_BASE "Client", + PW_VERSION_CLIENT_NODE, + PW_CLIENT_NODE_METHOD_NUM, &pw_protocol_native_server_client_node_demarshal, + PW_CLIENT_NODE_EVENT_NUM, &pw_protocol_native_server_client_node_events, +}; + +struct pw_protocol *pw_protocol_native_ext_client_node_init(void) +{ + static bool init = false; + struct pw_protocol *protocol; + + protocol = pw_protocol_get(PW_TYPE_PROTOCOL__Native); + + if (init) + return protocol; + + pw_protocol_add_interfaces(protocol, + &pw_protocol_native_client_client_node_interface, + &pw_protocol_native_server_client_node_interface); + + init = true; + + return protocol; +} diff --git a/src/modules/module-flatpak.c b/src/modules/module-flatpak.c new file mode 100644 index 00000000..86d7f7de --- /dev/null +++ b/src/modules/module-flatpak.c @@ -0,0 +1,712 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "config.h" + +#include <dbus/dbus.h> + +#include "pipewire/interfaces.h" +#include "pipewire/utils.h" + +#include "pipewire/core.h" +#include "pipewire/module.h" + +struct impl { + struct pw_core *core; + struct pw_properties *properties; + + DBusConnection *bus; + + struct pw_listener global_added; + struct pw_listener global_removed; + + struct spa_list client_list; + + struct spa_source *dispatch_event; +}; + +struct client_info { + struct impl *impl; + struct spa_list link; + struct pw_client *client; + bool is_sandboxed; + const struct pw_core_methods *old_methods; + struct pw_core_methods core_methods; + struct spa_list async_pending; + struct pw_listener resource_impl; + struct pw_listener resource_removed; +}; + +struct async_pending { + struct spa_list link; + struct client_info *info; + bool handled; + char *handle; + struct pw_resource *resource; + char *factory_name; + char *name; + struct pw_properties *properties; + uint32_t new_id; +}; + +static struct client_info *find_client_info(struct impl *impl, struct pw_client *client) +{ + struct client_info *info; + + spa_list_for_each(info, &impl->client_list, link) { + if (info->client == client) + return info; + } + return NULL; +} + +static void close_request(struct async_pending *p) +{ + DBusMessage *m = NULL; + struct impl *impl = p->info->impl; + + pw_log_debug("pending %p: handle %s", p, p->handle); + + if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Request", + p->handle, + "org.freedesktop.portal.Request", "Close"))) { + pw_log_error("Failed to create message"); + return; + } + + if (!dbus_connection_send(impl->bus, m, NULL)) + pw_log_error("Failed to send message"); + + dbus_message_unref(m); +} + +static struct async_pending *find_pending(struct client_info *cinfo, const char *handle) +{ + struct async_pending *p; + + spa_list_for_each(p, &cinfo->async_pending, link) { + if (strcmp(p->handle, handle) == 0) + return p; + } + return NULL; +} + +static void free_pending(struct async_pending *p) +{ + if (!p->handled) + close_request(p); + + pw_log_debug("pending %p: handle %s", p, p->handle); + spa_list_remove(&p->link); + free(p->handle); + free(p->factory_name); + free(p->name); + if (p->properties) + pw_properties_free(p->properties); + free(p); +} + +static void client_info_free(struct client_info *cinfo) +{ + struct async_pending *p, *tmp; + + spa_list_for_each_safe(p, tmp, &cinfo->async_pending, link) + free_pending(p); + + spa_list_remove(&cinfo->link); + free(cinfo); +} + +static bool client_is_sandboxed(struct pw_client *cl) +{ + char data[2048], *ptr; + size_t n, size; + const char *state = NULL; + const char *current; + bool result; + int fd; + pid_t pid; + + if (cl->ucred_valid) { + pw_log_info("client has trusted pid %d", cl->ucred.pid); + } else { + pw_log_info("no trusted pid found, assuming not sandboxed\n"); + return false; + } + + pid = cl->ucred.pid; + + sprintf(data, "/proc/%u/cgroup", pid); + fd = open(data, O_RDONLY | O_CLOEXEC, 0); + if (fd == -1) + return false; + + spa_zero(data); + size = sizeof(data); + ptr = data; + + while (size > 0) { + int r; + + if ((r = read(fd, data, size)) < 0) { + if (errno == EINTR) + continue; + else + break; + } + if (r == 0) + break; + + ptr += r; + size -= r; + } + close(fd); + + result = false; + while ((current = pw_split_walk(data, "\n", &n, &state)) != NULL) { + if (strncmp(current, "1:name=systemd:", strlen("1:name=systemd:")) == 0) { + const char *p = strstr(current, "flatpak-"); + if (p && p - current < n) { + pw_log_info("found a flatpak cgroup, assuming sandboxed\n"); + result = true; + break; + } + } + } + return result; +} + +static bool +check_global_owner(struct pw_core *core, struct pw_client *client, struct pw_global *global) +{ + if (global == NULL) + return false; + + if (global->owner == NULL) + return true; + + if (global->owner->client->ucred.uid == client->ucred.uid) + return true; + + return false; +} + +static bool +do_global_filter(struct pw_global *global, struct pw_client *client, void *data) +{ + if (global->type == client->core->type.link) { + struct pw_link *link = global->object; + + /* we must be able to see both nodes */ + if (link->output + && !check_global_owner(client->core, client, link->output->node->global)) + return false; + + if (link->input + && !check_global_owner(client->core, client, link->input->node->global)) + return false; + } else if (!check_global_owner(client->core, client, global)) + return false; + + return true; +} + +static DBusHandlerResult +portal_response(DBusConnection *connection, DBusMessage *msg, void *user_data) +{ + struct client_info *cinfo = user_data; + + if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + uint32_t response = 2; + DBusError error; + struct async_pending *p; + + dbus_error_init(&error); + + dbus_connection_remove_filter(connection, portal_response, cinfo); + + if (!dbus_message_get_args + (msg, &error, DBUS_TYPE_UINT32, &response, DBUS_TYPE_INVALID)) { + pw_log_error("failed to parse Response: %s", error.message); + dbus_error_free(&error); + } + + p = find_pending(cinfo, dbus_message_get_path(msg)); + if (p == NULL) + return DBUS_HANDLER_RESULT_HANDLED; + + p->handled = true; + + pw_log_debug("portal check result: %d", response); + + if (response == 0) { + cinfo->old_methods->create_node (p->resource, + p->factory_name, + p->name, + &p->properties->dict, + p->new_id); + } else { + pw_core_notify_error(cinfo->client->core_resource, + p->resource->id, SPA_RESULT_NO_PERMISSION, "not allowed"); + + } + free_pending(p); + pw_client_set_busy(cinfo->client, false); + + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + + +static void do_create_node(void *object, + const char *factory_name, + const char *name, + const struct spa_dict *props, + uint32_t new_id) +{ + struct pw_resource *resource = object; + struct client_info *cinfo = resource->access_private; + struct impl *impl = cinfo->impl; + struct pw_client *client = resource->client; + DBusMessage *m = NULL, *r = NULL; + DBusError error; + pid_t pid; + DBusMessageIter msg_iter; + DBusMessageIter dict_iter; + const char *handle; + const char *device; + struct async_pending *p; + + if (!cinfo->is_sandboxed) { + cinfo->old_methods->create_node (object, factory_name, name, props, new_id); + return; + } + if (strcmp(factory_name, "client-node") != 0) { + pw_log_error("can only allow client-node"); + goto not_allowed; + } + + pw_log_info("ask portal for client %p", cinfo->client); + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Device", "AccessDevice"))) + goto no_method_call; + + device = "camera"; + + pid = cinfo->client->ucred.pid; + if (!dbus_message_append_args(m, DBUS_TYPE_UINT32, &pid, DBUS_TYPE_INVALID)) + goto message_failed; + + dbus_message_iter_init_append(m, &msg_iter); + dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "s", &dict_iter); + dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &device); + dbus_message_iter_close_container(&msg_iter, &dict_iter); + + dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter); + dbus_message_iter_close_container(&msg_iter, &dict_iter); + + if (!(r = dbus_connection_send_with_reply_and_block(impl->bus, m, -1, &error))) + goto send_failed; + + dbus_message_unref(m); + + if (!dbus_message_get_args(r, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID)) + goto parse_failed; + + dbus_message_unref(r); + + dbus_bus_add_match(impl->bus, + "type='signal',interface='org.freedesktop.portal.Request'", &error); + dbus_connection_flush(impl->bus); + if (dbus_error_is_set(&error)) + goto subscribe_failed; + + dbus_connection_add_filter(impl->bus, portal_response, cinfo, NULL); + + p = calloc(1, sizeof(struct async_pending)); + p->info = cinfo; + p->handle = strdup(handle); + p->handled = false; + p->resource = resource; + p->factory_name = strdup(factory_name); + p->name = strdup(name); + p->properties = props ? pw_properties_new_dict(props) : NULL; + p->new_id = new_id; + pw_client_set_busy(client, true); + + pw_log_debug("pending %p: handle %s", p, handle); + spa_list_insert(cinfo->async_pending.prev, &p->link); + + return; + + no_method_call: + pw_log_error("Failed to create message"); + goto not_allowed; + message_failed: + dbus_message_unref(m); + goto not_allowed; + send_failed: + pw_log_error("Failed to call portal: %s", error.message); + dbus_error_free(&error); + dbus_message_unref(m); + goto not_allowed; + parse_failed: + pw_log_error("Failed to parse AccessDevice result: %s", error.message); + dbus_error_free(&error); + dbus_message_unref(r); + goto not_allowed; + subscribe_failed: + pw_log_error("Failed to subscribe to Request signal: %s", error.message); + dbus_error_free(&error); + goto not_allowed; + not_allowed: + pw_core_notify_error(client->core_resource, + resource->id, SPA_RESULT_NO_PERMISSION, "not allowed"); + return; +} + +static void +do_create_link(void *object, + uint32_t output_node_id, + uint32_t output_port_id, + uint32_t input_node_id, + uint32_t input_port_id, + const struct spa_format *filter, + const struct spa_dict *props, + uint32_t new_id) +{ + struct pw_resource *resource = object; + struct client_info *cinfo = resource->access_private; + struct pw_client *client = resource->client; + + if (cinfo->is_sandboxed) { + pw_core_notify_error(client->core_resource, + resource->id, SPA_RESULT_NO_PERMISSION, "not allowed"); + return; + } + cinfo->old_methods->create_link (object, + output_node_id, + output_port_id, + input_node_id, + input_port_id, + filter, + props, + new_id); +} + +static void on_resource_impl(struct pw_listener *listener, + struct pw_client *client, + struct pw_resource *resource) +{ + struct client_info *cinfo = SPA_CONTAINER_OF(listener, struct client_info, resource_impl); + struct impl *impl = cinfo->impl; + + if (resource->type == impl->core->type.core) { + cinfo->old_methods = resource->implementation; + cinfo->core_methods = *cinfo->old_methods; + resource->implementation = &cinfo->core_methods; + resource->access_private = cinfo; + cinfo->core_methods.create_node = do_create_node; + cinfo->core_methods.create_link = do_create_link; + } +} + +static void +on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added); + + if (global->type == impl->core->type.client) { + struct pw_client *client = global->object; + struct client_info *cinfo; + + cinfo = calloc(1, sizeof(struct client_info)); + cinfo->impl = impl; + cinfo->client = client; + cinfo->is_sandboxed = client_is_sandboxed(client); + spa_list_init(&cinfo->async_pending); + + pw_signal_add(&client->resource_impl, &cinfo->resource_impl, on_resource_impl); + + spa_list_insert(impl->client_list.prev, &cinfo->link); + + pw_log_debug("module %p: client %p added", impl, client); + } +} + +static void +on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed); + + if (global->type == impl->core->type.client) { + struct pw_client *client = global->object; + struct client_info *cinfo; + + if ((cinfo = find_client_info(impl, client))) + client_info_free(cinfo); + + pw_log_debug("module %p: client %p removed", impl, client); + } +} + +static void dispatch_cb(struct spa_loop_utils *utils, struct spa_source *source, void *userdata) +{ + struct impl *impl = userdata; + + if (dbus_connection_dispatch(impl->bus) == DBUS_DISPATCH_COMPLETE) + pw_loop_enable_idle(impl->core->main_loop, source, false); +} + +static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) +{ + struct impl *impl = userdata; + + pw_loop_enable_idle(impl->core->main_loop, + impl->dispatch_event, status == DBUS_DISPATCH_COMPLETE ? false : true); +} + +static inline enum spa_io dbus_to_io(DBusWatch *watch) +{ + enum spa_io mask; + unsigned int flags; + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(watch)) + return 0; + + flags = dbus_watch_get_flags(watch); + mask = SPA_IO_HUP | SPA_IO_ERR; + + if (flags & DBUS_WATCH_READABLE) + mask |= SPA_IO_IN; + if (flags & DBUS_WATCH_WRITABLE) + mask |= SPA_IO_OUT; + + return mask; +} + +static inline unsigned int io_to_dbus(enum spa_io mask) +{ + unsigned int flags = 0; + + if (mask & SPA_IO_IN) + flags |= DBUS_WATCH_READABLE; + if (mask & SPA_IO_OUT) + flags |= DBUS_WATCH_WRITABLE; + if (mask & SPA_IO_HUP) + flags |= DBUS_WATCH_HANGUP; + if (mask & SPA_IO_ERR) + flags |= DBUS_WATCH_ERROR; + return flags; +} + +static void +handle_io_event(struct spa_loop_utils *utils, + struct spa_source *source, int fd, enum spa_io mask, void *userdata) +{ + DBusWatch *watch = userdata; + + if (!dbus_watch_get_enabled(watch)) { + pw_log_warn("Asked to handle disabled watch: %p %i", (void *) watch, fd); + return; + } + dbus_watch_handle(watch, io_to_dbus(mask)); +} + +static dbus_bool_t add_watch(DBusWatch *watch, void *userdata) +{ + struct impl *impl = userdata; + struct spa_source *source; + + pw_log_debug("add watch %p %d", watch, dbus_watch_get_unix_fd(watch)); + + /* we dup because dbus tends to add the same fd multiple times and our epoll + * implementation does not like that */ + source = pw_loop_add_io(impl->core->main_loop, + dup(dbus_watch_get_unix_fd(watch)), + dbus_to_io(watch), true, handle_io_event, watch); + + dbus_watch_set_data(watch, source, NULL); + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *userdata) +{ + struct impl *impl = userdata; + struct spa_source *source; + + if ((source = dbus_watch_get_data(watch))) + pw_loop_destroy_source(impl->core->main_loop, source); +} + +static void toggle_watch(DBusWatch *watch, void *userdata) +{ + struct impl *impl = userdata; + struct spa_source *source; + + source = dbus_watch_get_data(watch); + + pw_loop_update_io(impl->core->main_loop, source, dbus_to_io(watch)); +} + +static void +handle_timer_event(struct spa_loop_utils *utils, struct spa_source *source, void *userdata) +{ + DBusTimeout *timeout = userdata; + uint64_t t; + struct timespec ts; + + if (dbus_timeout_get_enabled(timeout)) { + t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + spa_loop_utils_update_timer(utils, source, &ts, NULL, false); + + dbus_timeout_handle(timeout); + } +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *userdata) +{ + struct impl *impl = userdata; + struct spa_source *source; + struct timespec ts; + uint64_t t; + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + source = pw_loop_add_timer(impl->core->main_loop, handle_timer_event, timeout); + + dbus_timeout_set_data(timeout, source, NULL); + + t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + pw_loop_update_timer(impl->core->main_loop, source, &ts, NULL, false); + return TRUE; +} + +static void remove_timeout(DBusTimeout *timeout, void *userdata) +{ + struct impl *impl = userdata; + struct spa_source *source; + + if ((source = dbus_timeout_get_data(timeout))) + pw_loop_destroy_source(impl->core->main_loop, source); +} + +static void toggle_timeout(DBusTimeout *timeout, void *userdata) +{ + struct impl *impl = userdata; + struct spa_source *source; + struct timespec ts, *tsp; + + source = dbus_timeout_get_data(timeout); + + if (dbus_timeout_get_enabled(timeout)) { + uint64_t t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; + ts.tv_sec = t / SPA_NSEC_PER_SEC; + ts.tv_nsec = t % SPA_NSEC_PER_SEC; + tsp = &ts; + } else { + tsp = NULL; + } + pw_loop_update_timer(impl->core->main_loop, source, tsp, NULL, false); +} + +static void wakeup_main(void *userdata) +{ + struct impl *impl = userdata; + + pw_loop_enable_idle(impl->core->main_loop, impl->dispatch_event, true); +} + +static struct impl *module_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + DBusError error; + + dbus_error_init(&error); + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("module %p: new", impl); + + impl->core = core; + impl->properties = properties; + + impl->bus = dbus_bus_get_private(DBUS_BUS_SESSION, &error); + if (impl->bus == NULL) + goto error; + + impl->dispatch_event = pw_loop_add_idle(core->main_loop, false, dispatch_cb, impl); + + dbus_connection_set_exit_on_disconnect(impl->bus, false); + dbus_connection_set_dispatch_status_function(impl->bus, dispatch_status, impl, NULL); + dbus_connection_set_watch_functions(impl->bus, add_watch, remove_watch, toggle_watch, impl, + NULL); + dbus_connection_set_timeout_functions(impl->bus, add_timeout, remove_timeout, + toggle_timeout, impl, NULL); + dbus_connection_set_wakeup_main_function(impl->bus, wakeup_main, impl, NULL); + + spa_list_init(&impl->client_list); + + pw_signal_add(&core->global_added, &impl->global_added, on_global_added); + pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed); + core->global_filter = do_global_filter; + core->global_filter_data = impl; + + return impl; + + error: + pw_log_error("Failed to connect to system bus: %s", error.message); + dbus_error_free(&error); + return NULL; +} + +#if 0 +static void module_destroy(struct impl *impl) +{ + pw_log_debug("module %p: destroy", impl); + + dbus_connection_close(impl->bus); + dbus_connection_unref(impl->bus); + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + module_new(module->core, NULL); + return true; +} diff --git a/src/modules/module-jack.c b/src/modules/module-jack.c new file mode 100644 index 00000000..49100beb --- /dev/null +++ b/src/modules/module-jack.c @@ -0,0 +1,754 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/file.h> +#include <sys/mman.h> +#include <semaphore.h> + +#include <jack/jack.h> +#include <jack/session.h> + +#include "config.h" + +#include "pipewire/pipewire.h" +#include "pipewire/log.h" +#include "pipewire/interfaces.h" + +#include "pipewire/core.h" +#include "pipewire/node.h" +#include "pipewire/module.h" +#include "pipewire/client.h" +#include "pipewire/resource.h" +#include "pipewire/link.h" +#include "pipewire/node-factory.h" +#include "pipewire/data-loop.h" +#include "pipewire/main-loop.h" + +#include "modules/module-jack/defs.h" +#include "modules/module-jack/shm.h" +#include "modules/module-jack/shared.h" +#include "modules/module-jack/synchro.h" +#include "modules/module-jack/server.h" + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#define LOCK_SUFFIX ".lock" +#define LOCK_SUFFIXLEN 5 + +int segment_num = 0; + +typedef bool(*demarshal_func_t) (void *object, void *data, size_t size); + +struct socket { + int fd; + struct sockaddr_un addr; + char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN]; + + struct pw_loop *loop; + struct spa_source *source; + char *core_name; + struct spa_list link; +}; + +struct impl { + struct pw_core *core; + struct spa_list link; + + struct pw_properties *properties; + + struct spa_list socket_list; + struct spa_list client_list; + + struct spa_loop_control_hooks hooks; + + struct jack_server server; +}; + +struct client { + struct impl *impl; + struct spa_list link; + struct pw_client *client; + int fd; + struct spa_source *source; + struct pw_listener busy_changed; +}; + +static int process_messages(struct client *client); + +static void client_destroy(void *data) +{ + struct pw_client *client = data; + struct client *this = client->user_data; + + pw_loop_destroy_source(this->impl->core->main_loop, this->source); + spa_list_remove(&this->link); + + close(this->fd); +} + +static int +handle_register_port(struct client *client) +{ + int result = 0; + int ref_num; + char name[JACK_PORT_NAME_SIZE + 1]; + char port_type[JACK_PORT_TYPE_SIZE + 1]; + unsigned int flags; + unsigned int buffer_size; + static jack_port_id_t port_index = 0; + + CheckSize(kRegisterPort_size); + CheckRead(&ref_num, sizeof(int)); + CheckRead(name, sizeof(name)); + CheckRead(port_type, sizeof(port_type)); + CheckRead(&flags, sizeof(unsigned int)); + CheckRead(&buffer_size, sizeof(unsigned int)); + + pw_log_error("protocol-jack %p: kRegisterPort %d %s %s %u %u", client->impl, + ref_num, name, port_type, flags, buffer_size); + port_index++; + + CheckWrite(&result, sizeof(int)); + CheckWrite(&port_index, sizeof(jack_port_id_t)); + return 0; +} + +static int +handle_activate_client(struct client *client) +{ + int result = 0; + int ref_num; + int is_real_time; + + CheckSize(kActivateClient_size); + CheckRead(&ref_num, sizeof(int)); + CheckRead(&is_real_time, sizeof(int)); + + pw_log_error("protocol-jack %p: kActivateClient %d %d", client->impl, + ref_num, is_real_time); + + CheckWrite(&result, sizeof(int)); + return 0; +} + +static int +handle_deactivate_client(struct client *client) +{ + int result = 0; + int ref_num; + + CheckSize(kDeactivateClient_size); + CheckRead(&ref_num, sizeof(int)); + + pw_log_error("protocol-jack %p: kDeactivateClient %d", client->impl, + ref_num); + + CheckWrite(&result, sizeof(int)); + return 0; +} + +static int +handle_client_check(struct client *client) +{ + char name[JACK_CLIENT_NAME_SIZE+1]; + int protocol; + int options; + int UUID; + int open; + int result = 0; + int status; + + CheckSize(kClientCheck_size); + CheckRead(name, sizeof(name)); + CheckRead(&protocol, sizeof(int)); + CheckRead(&options, sizeof(int)); + CheckRead(&UUID, sizeof(int)); + CheckRead(&open, sizeof(int)); + + pw_log_error("protocol-jack %p: kClientCheck %s %d %d %d %d", client->impl, + name, protocol, options, UUID, open); + + status = 0; + if (protocol != JACK_PROTOCOL_VERSION) { + status |= (JackFailure | JackVersionError); + pw_log_error("protocol-jack: protocol mismatch (%d vs %d)", protocol, JACK_PROTOCOL_VERSION); + result = -1; + goto reply; + } + /* TODO check client name and uuid */ + + reply: + CheckWrite(&result, sizeof(int)); + CheckWrite(name, sizeof(name)); + CheckWrite(&status, sizeof(int)); + + if (open) + return process_messages(client); + + return 0; +} + +static int +handle_client_open(struct client *client) +{ + struct impl *impl = client->impl; + struct jack_server *server = &impl->server; + int PID, UUID; + char name[JACK_CLIENT_NAME_SIZE+1]; + int result, ref_num, shared_engine, shared_client, shared_graph; + struct jack_client *jc; + + CheckSize(kClientOpen_size); + CheckRead(&PID, sizeof(int)); + CheckRead(&UUID, sizeof(int)); + CheckRead(name, sizeof(name)); + + ref_num = jack_server_allocate_ref_num(server); + if (ref_num == -1) { + result = -1; + goto reply; + } + + jc = calloc(1,sizeof(struct jack_client)); + jc->owner = client; + jc->ref_num = ref_num; + + if (jack_synchro_init(&server->synchro_table[ref_num], + name, + server->engine_control->server_name, + 0, + false, + server->promiscuous) < 0) { + result = -1; + goto reply; + } + + jc->control = jack_client_control_alloc(name, client->client->ucred.pid, ref_num, -1); + if (jc->control == NULL) { + result = -1; + goto reply; + } + + server->client_table[ref_num] = jc; + + result = 0; + shared_engine = impl->server.engine_control->info.index; + shared_client = jc->control->info.index; + shared_graph = impl->server.graph_manager->info.index; + + reply: + CheckWrite(&result, sizeof(int)); + CheckWrite(&shared_engine, sizeof(int)); + CheckWrite(&shared_client, sizeof(int)); + CheckWrite(&shared_graph, sizeof(int)); + + return 0; +} + +static int +handle_client_close(struct client *client) +{ + int ref_num; + CheckSize(kClientClose_size); + CheckRead(&ref_num, sizeof(int)); + int result = 0; + + CheckWrite(&result, sizeof(int)); + return 0; +} + +static int +handle_connect_name_ports(struct client *client) +{ + int ref_num; + char src[REAL_JACK_PORT_NAME_SIZE+1]; + char dst[REAL_JACK_PORT_NAME_SIZE+1]; + int result = 0; + + CheckSize(kConnectNamePorts_size); + CheckRead(&ref_num, sizeof(int)); + CheckRead(src, sizeof(src)); + CheckRead(dst, sizeof(dst)); + + CheckWrite(&result, sizeof(int)); + return 0; +} + +static int +handle_get_UUID_by_client(struct client *client) +{ + char name[JACK_CLIENT_NAME_SIZE+1]; + char UUID[JACK_UUID_SIZE]; + int result = 0; + + CheckSize(kGetUUIDByClient_size); + CheckRead(name, sizeof(name)); + + CheckWrite(&result, sizeof(int)); + CheckWrite(UUID, sizeof(UUID)); + + return 0; +} + +static int +process_messages(struct client *client) +{ + struct pw_client *c = client->client; + int type, res = -1; + + if (read(client->fd, &type, sizeof(enum jack_request_type)) != sizeof(enum jack_request_type)) { + pw_log_error("protocol-jack %p: failed to read type", client->impl); + goto error; + } + pw_log_info("protocol-jack %p: got type %d", client->impl, type); + + switch(type) { + case jack_request_RegisterPort: + res = handle_register_port(client); + break; + case jack_request_UnRegisterPort: + case jack_request_ConnectPorts: + case jack_request_DisconnectPorts: + case jack_request_SetTimeBaseClient: + case jack_request_ActivateClient: + res = handle_activate_client(client); + break; + case jack_request_DeactivateClient: + res = handle_deactivate_client(client); + break; + case jack_request_DisconnectPort: + case jack_request_SetClientCapabilities: + case jack_request_GetPortConnections: + case jack_request_GetPortNConnections: + case jack_request_ReleaseTimebase: + case jack_request_SetTimebaseCallback: + case jack_request_SetBufferSize: + case jack_request_SetFreeWheel: + break; + case jack_request_ClientCheck: + res = handle_client_check(client); + break; + case jack_request_ClientOpen: + res = handle_client_open(client); + break; + case jack_request_ClientClose: + res = handle_client_close(client); + break; + case jack_request_ConnectNamePorts: + res = handle_connect_name_ports(client); + break; + case jack_request_DisconnectNamePorts: + case jack_request_GetInternalClientName: + case jack_request_InternalClientHandle: + case jack_request_InternalClientLoad: + case jack_request_InternalClientUnload: + case jack_request_PortRename: + case jack_request_Notification: + case jack_request_SessionNotify: + case jack_request_SessionReply: + case jack_request_GetClientByUUID: + case jack_request_ReserveClientName: + break; + case jack_request_GetUUIDByClient: + res = handle_get_UUID_by_client(client); + break; + case jack_request_ClientHasSessionCallback: + case jack_request_ComputeTotalLatencies: + break; + default: + pw_log_error("protocol-jack %p: invalid type %d", client->impl, type); + goto error; + } + if (res != 0) + goto error; + + return res; + + error: + pw_log_error("protocol-jack %p: error handling type %d", client->impl, type); + pw_client_destroy(c); + return -1; + +} + +static void +on_busy_changed(struct pw_listener *listener, + struct pw_client *client) +{ + struct client *c = SPA_CONTAINER_OF(listener, struct client, busy_changed); + enum spa_io mask = SPA_IO_ERR | SPA_IO_HUP; + + if (!client->busy) + mask |= SPA_IO_IN; + + pw_loop_update_io(c->impl->core->main_loop, c->source, mask); + + if (!client->busy) + process_messages(c); +} + +static void +connection_data(struct spa_loop_utils *utils, + struct spa_source *source, int fd, enum spa_io mask, void *data) +{ + struct client *client = data; + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_error("protocol-native %p: got connection error", client->impl); + pw_client_destroy(client->client); + return; + } + + if (mask & SPA_IO_IN) + process_messages(client); +} + +static struct client *client_new(struct impl *impl, int fd) +{ + struct client *this; + struct pw_client *client; + socklen_t len; + struct ucred ucred, *ucredp; + + len = sizeof(ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) { + pw_log_error("no peercred: %m"); + ucredp = NULL; + } else { + ucredp = &ucred; + } + + client = pw_client_new(impl->core, ucredp, NULL, sizeof(struct client)); + if (client == NULL) + goto no_client; + + client->destroy = client_destroy; + + this = client->user_data; + this->impl = impl; + this->fd = fd; + this->source = pw_loop_add_io(impl->core->main_loop, + this->fd, + SPA_IO_ERR | SPA_IO_HUP, false, connection_data, this); + if (this->source == NULL) + goto no_source; + + this->client = client; + + spa_list_insert(impl->client_list.prev, &this->link); + + pw_signal_add(&client->busy_changed, &this->busy_changed, on_busy_changed); + + pw_log_error("module-jack %p: added new client", impl); + + return this; + + no_source: + free(this); + no_client: + return NULL; +} + +static int +make_int_client(struct impl *impl, struct pw_node *node) +{ + struct jack_server *server = &impl->server; + struct jack_connection_manager *conn; + int ref_num; + struct jack_client *jc; + jack_port_id_t port_id; + + ref_num = jack_server_allocate_ref_num(server); + if (ref_num == -1) + return -1; + + if (jack_synchro_init(&server->synchro_table[ref_num], + "system", + server->engine_control->server_name, + 0, + false, + server->promiscuous) < 0) { + return -1; + } + + jc = calloc(1,sizeof(struct jack_client)); + jc->ref_num = ref_num; + jc->node = node; + jc->control = jack_client_control_alloc("system", -1, ref_num, -1); + jc->control->active = true; + + server->client_table[ref_num] = jc; + + impl->server.engine_control->driver_num++; + + conn = jack_graph_manager_next_start(server->graph_manager); + + jack_connection_manager_init_ref_num(conn, ref_num); + + port_id = jack_graph_manager_allocate_port(server->graph_manager, + ref_num, "system:playback_1", 2, + JackPortIsInput | JackPortIsPhysical | JackPortIsTerminal); + + jack_connection_manager_add_port(conn, true, ref_num, port_id); + + jack_graph_manager_next_stop(server->graph_manager); + + return 0; +} + +static bool init_nodes(struct impl *impl) +{ + struct pw_core *core = impl->core; + struct pw_node *n; + + spa_list_for_each(n, &core->node_list, link) { + const char *str; + + if (n->global == NULL) + continue; + + if (n->properties == NULL) + continue; + + if ((str = pw_properties_get(n->properties, "media.class")) == NULL) + continue; + + if (strcmp(str, "Audio/Sink") != 0) + continue; + + make_int_client(impl, n); + } + return true; +} + +static struct socket *create_socket(void) +{ + struct socket *s; + + if ((s = calloc(1, sizeof(struct socket))) == NULL) + return NULL; + + s->fd = -1; + return s; +} + +static void destroy_socket(struct socket *s) +{ + if (s->source) + pw_loop_destroy_source(s->loop, s->source); + if (s->addr.sun_path[0]) + unlink(s->addr.sun_path); + if (s->fd >= 0) + close(s->fd); + if (s->lock_addr[0]) + unlink(s->lock_addr); + free(s); +} + +static bool init_socket_name(struct socket *s, const char *name, bool promiscuous, int which) +{ + int name_size; + const char *runtime_dir; + + runtime_dir = JACK_SOCKET_DIR; + + s->addr.sun_family = AF_UNIX; + if (promiscuous) { + name_size = snprintf(s->addr.sun_path, sizeof(s->addr.sun_path), + "%s/jack_%s_%d", runtime_dir, name, which) + 1; + } else { + name_size = snprintf(s->addr.sun_path, sizeof(s->addr.sun_path), + "%s/jack_%s_%d_%d", runtime_dir, name, getuid(), which) + 1; + } + + s->core_name = (s->addr.sun_path + name_size - 1) - strlen(name); + + if (name_size > (int) sizeof(s->addr.sun_path)) { + pw_log_error("socket path \"%s/%s\" plus null terminator exceeds 108 bytes", + runtime_dir, name); + *s->addr.sun_path = 0; + return false; + } + return true; +} + +static void +socket_data(struct spa_loop_utils *utils, + struct spa_source *source, int fd, enum spa_io mask, void *data) +{ + struct impl *impl = data; + struct client *client; + struct sockaddr_un name; + socklen_t length; + int client_fd; + + length = sizeof(name); + client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC); + if (client_fd < 0) { + pw_log_error("failed to accept: %m"); + return; + } + + client = client_new(impl, client_fd); + if (client == NULL) { + pw_log_error("failed to create client"); + close(client_fd); + return; + } + + pw_loop_update_io(impl->core->main_loop, + client->source, SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP); +} + +static bool add_socket(struct impl *impl, struct socket *s) +{ + socklen_t size; + + if ((s->fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) + return false; + + size = offsetof(struct sockaddr_un, sun_path) + strlen(s->addr.sun_path); + if (bind(s->fd, (struct sockaddr *) &s->addr, size) < 0) { + pw_log_error("bind() failed with error: %m"); + return false; + } + + if (listen(s->fd, 100) < 0) { + pw_log_error("listen() failed with error: %m"); + return false; + } + + s->loop = impl->core->main_loop; + s->source = pw_loop_add_io(s->loop, s->fd, SPA_IO_IN, false, socket_data, impl); + if (s->source == NULL) + return false; + + spa_list_insert(impl->socket_list.prev, &s->link); + + return true; +} + +static int init_server(struct impl *impl, const char *name, bool promiscuous) +{ + struct jack_server *server = &impl->server; + int i; + struct socket *s; + + pthread_mutex_init(&server->lock, NULL); + + if (jack_register_server(name, 1) != 0) + return -1; + + jack_cleanup_shm(); + + /* graph manager */ + server->graph_manager = jack_graph_manager_alloc(2048); + + /* engine control */ + server->engine_control = jack_engine_control_alloc(name); + + for (i = 0; i < CLIENT_NUM; i++) + server->synchro_table[i] = JACK_SYNCHRO_INIT; + + if (!init_nodes(impl)) + return -1; + + s = create_socket(); + + if (!init_socket_name(s, name, promiscuous, 0)) + goto error; + + if (!add_socket(impl, s)) + goto error; + + return 0; + + error: + destroy_socket(s); + return -1; +} + + +static struct impl *module_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + const char *name, *str; + bool promiscuous; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("protocol-jack %p: new", impl); + + impl->core = core; + impl->properties = properties; + + spa_list_init(&impl->socket_list); + spa_list_init(&impl->client_list); + + str = NULL; + if (impl->properties) + str = pw_properties_get(impl->properties, "jack.default.server"); + if (str == NULL) + str = getenv("JACK_DEFAULT_SERVER"); + + name = str ? str : JACK_DEFAULT_SERVER_NAME; + + str = NULL; + if (impl->properties) + str = pw_properties_get(impl->properties, "jack.promiscuous.server"); + if (str == NULL) + str = getenv("JACK_PROMISCUOUS_SERVER"); + + promiscuous = str ? atoi(str) != 0 : false; + + if (init_server(impl, name, promiscuous) < 0) + goto error; + + return impl; + + error: + free(impl); + return NULL; +} + +#if 0 +static void module_destroy(struct impl *impl) +{ + struct impl *object, *tmp; + + pw_log_debug("module %p: destroy", impl); + + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + module_new(module->core, NULL); + return true; +} diff --git a/src/modules/module-jack/defs.h b/src/modules/module-jack/defs.h new file mode 100644 index 00000000..5ecb9724 --- /dev/null +++ b/src/modules/module-jack/defs.h @@ -0,0 +1,143 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "pipewire/log.h" + +#define USE_POSIX_SHM +#undef JACK_MONITOR + +#define JACK_DEFAULT_SERVER_NAME "default" +#define JACK_SOCKET_DIR "/dev/shm" +#define JACK_SHM_DIR "/dev/shm" +#define JACK_SERVER_NAME_SIZE 256 +#define JACK_CLIENT_NAME_SIZE 64 +#define JACK_PORT_NAME_SIZE 256 +#define JACK_PORT_TYPE_SIZE 32 +#define JACK_PROTOCOL_VERSION 8 + +#define PORT_NUM_MAX 4096 +#define PORT_NUM_FOR_CLIENT 2048 +#define CONNECTION_NUM_FOR_PORT PORT_NUM_FOR_CLIENT + +#define REAL_JACK_PORT_NAME_SIZE JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE + +#define BUFFER_SIZE_MAX 8192 + +#define CLIENT_NUM 256 + +#define JACK_ENGINE_ROLLING_COUNT 32 +#define JACK_ENGINE_ROLLING_INTERVAL 1024 + +#define TIME_POINTS 100000 +#define FAILURE_TIME_POINTS 10000 +#define FAILURE_WINDOW 10 +#define MEASURED_CLIENTS 32 + +#define SYNC_MAX_NAME_SIZE 256 + +#define JACK_UUID_SIZE 36 +#define JACK_UUID_STRING_SIZE (JACK_UUID_SIZE+1) + +#define JACK_SESSION_COMMAND_SIZE 256 + +#define NO_PORT 0xFFFE +#define EMPTY 0xFFFD +#define FREE 0xFFFC + + + +typedef enum { + JACK_TIMER_SYSTEM_CLOCK, + JACK_TIMER_HPET, +} jack_timer_type_t; + +enum jack_request_type { + jack_request_RegisterPort = 1, + jack_request_UnRegisterPort = 2, + jack_request_ConnectPorts = 3, + jack_request_DisconnectPorts = 4, + jack_request_SetTimeBaseClient = 5, + jack_request_ActivateClient = 6, + jack_request_DeactivateClient = 7, + jack_request_DisconnectPort = 8, + jack_request_SetClientCapabilities = 9, + jack_request_GetPortConnections = 10, + jack_request_GetPortNConnections = 11, + jack_request_ReleaseTimebase = 12, + jack_request_SetTimebaseCallback = 13, + jack_request_SetBufferSize = 20, + jack_request_SetFreeWheel = 21, + jack_request_ClientCheck = 22, + jack_request_ClientOpen = 23, + jack_request_ClientClose = 24, + jack_request_ConnectNamePorts = 25, + jack_request_DisconnectNamePorts = 26, + jack_request_GetInternalClientName = 27, + jack_request_InternalClientHandle = 28, + jack_request_InternalClientLoad = 29, + jack_request_InternalClientUnload = 30, + jack_request_PortRename = 31, + jack_request_Notification = 32, + jack_request_SessionNotify = 33, + jack_request_SessionReply = 34, + jack_request_GetClientByUUID = 35, + jack_request_ReserveClientName = 36, + jack_request_GetUUIDByClient = 37, + jack_request_ClientHasSessionCallback = 38, + jack_request_ComputeTotalLatencies = 39 +}; + +enum jack_notification_type { + jack_notify_AddClient = 0, + jack_notify_RemoveClient = 1, + jack_notify_ActivateClient = 2, + jack_notify_XRunCallback = 3, + jack_notify_GraphOrderCallback = 4, + jack_notify_BufferSizeCallback = 5, + jack_notify_SampleRateCallback = 6, + jack_notify_StartFreewheelCallback = 7, + jack_notify_StopFreewheelCallback = 8, + jack_notify_PortRegistrationOnCallback = 9, + jack_notify_PortRegistrationOffCallback = 10, + jack_notify_PortConnectCallback = 11, + jack_notify_PortDisconnectCallback = 12, + jack_notify_PortRenameCallback = 13, + jack_notify_RealTimeCallback = 14, + jack_notify_ShutDownCallback = 15, + jack_notify_QUIT = 16, + jack_notify_SessionCallback = 17, + jack_notify_LatencyCallback = 18, + jack_notify_max = 64 // To keep some room in JackClientControl fCallback table +}; + +#define kActivateClient_size (2*sizeof(int)) +#define kDeactivateClient_size (sizeof(int)) +#define kRegisterPort_size (sizeof(int) + JACK_PORT_NAME_SIZE+1 + JACK_PORT_TYPE_SIZE+1 + 2*sizeof(unsigned int)) +#define kClientCheck_size (JACK_CLIENT_NAME_SIZE+1 + 4 * sizeof(int)) +#define kClientOpen_size (JACK_CLIENT_NAME_SIZE+1 + 2 * sizeof(int)) +#define kClientClose_size (sizeof(int)) +#define kConnectNamePorts_size (sizeof(int) + REAL_JACK_PORT_NAME_SIZE+1 + REAL_JACK_PORT_NAME_SIZE+1) +#define kGetUUIDByClient_size (JACK_CLIENT_NAME_SIZE+1) + +#define CheckRead(var,size) if(read(client->fd,var,size)!=size) {pw_log_error("read error"); return -1; } +#define CheckWrite(var,size) if(write(client->fd,var,size)!=size) {pw_log_error("write error"); return -1; } +#define CheckSize(expected) { int __size; CheckRead(&__size, sizeof(int)); if (__size != expected) { pw_log_error("CheckSize error size %d != %d", __size, (int)expected); return -1; } } + +#define jack_error pw_log_error +#define jack_log pw_log_info diff --git a/src/modules/module-jack/server.h b/src/modules/module-jack/server.h new file mode 100644 index 00000000..c5f6c0c9 --- /dev/null +++ b/src/modules/module-jack/server.h @@ -0,0 +1,54 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +struct jack_client { + int ref_num; + struct client *owner; + struct jack_client_control *control; + struct pw_node *node; +}; + +struct jack_server { + pthread_mutex_t lock; + + bool promiscuous; + + struct jack_graph_manager *graph_manager; + struct jack_engine_control *engine_control; + + struct jack_client* client_table[CLIENT_NUM]; + struct jack_synchro synchro_table[CLIENT_NUM]; +}; + +static inline int +jack_server_allocate_ref_num(struct jack_server *server) +{ + int i; + + for (i = 0; i < CLIENT_NUM; i++) + if (server->client_table[i] == NULL) + return i; + return -1; +} + +static inline void +jack_server_free_ref_num(struct jack_server *server, int ref_num) +{ + server->client_table[ref_num] = NULL; +} diff --git a/src/modules/module-jack/shared.h b/src/modules/module-jack/shared.h new file mode 100644 index 00000000..2d4cfaf8 --- /dev/null +++ b/src/modules/module-jack/shared.h @@ -0,0 +1,601 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <math.h> + +extern int segment_num; + +static inline int jack_shm_alloc(size_t size, jack_shm_info_t *info, int num) +{ + char name[64]; + + snprintf(name, sizeof(name), "/jack_shared%d", num); + + if (jack_shmalloc(name, size, info)) { + pw_log_error("Cannot create shared memory segment of size = %zd (%s)", size, strerror(errno)); + return -1; + } + + if (jack_attach_shm(info)) { + jack_error("Cannot attach shared memory segment name = %s err = %s", name, strerror(errno)); + jack_destroy_shm(info); + return -1; + } + info->size = size; + return 0; +} + +typedef uint16_t jack_int_t; // Internal type for ports and refnum + +typedef enum { + NotTriggered, + Triggered, + Running, + Finished, +} jack_client_state_t; + +PRE_PACKED_STRUCTURE +struct jack_client_timing { + jack_time_t signaled_at; + jack_time_t awake_at; + jack_time_t finished_at; + jack_client_state_t status; +} POST_PACKED_STRUCTURE; + +#define JACK_CLIENT_TIMING_INIT (struct jack_client_timing) { 0, 0, 0, NotTriggered } + +PRE_PACKED_STRUCTURE +struct jack_port { + int type_id; + enum JackPortFlags flags; + char name[REAL_JACK_PORT_NAME_SIZE]; + char alias1[REAL_JACK_PORT_NAME_SIZE]; + char alias2[REAL_JACK_PORT_NAME_SIZE]; + int ref_num; + + jack_nframes_t latency; + jack_nframes_t total_latency; + jack_latency_range_t playback_latency; + jack_latency_range_t capture_latency; + uint8_t monitor_requests; + + bool in_use; + jack_port_id_t tied; + jack_default_audio_sample_t buffer[BUFFER_SIZE_MAX + 8]; +} POST_PACKED_STRUCTURE; + +static inline void jack_port_init(struct jack_port *port, int ref_num, + const char* port_name, int type_id, enum JackPortFlags flags) +{ + port->type_id = type_id; + port->flags = flags; + strcpy(port->name, port_name); + port->alias1[0] = '\0'; + port->alias2[0] = '\0'; + port->ref_num = ref_num; + port->latency = 0; + port->total_latency = 0; + port->playback_latency.min = port->playback_latency.max = 0; + port->capture_latency.min = port->capture_latency.max = 0; + port->monitor_requests = 0; + port->in_use = true; + port->tied = NO_PORT; +} + +#define MAKE_FIXED_ARRAY(size) \ +PRE_PACKED_STRUCTURE \ +struct { \ + jack_int_t table[size]; \ + uint32_t counter; \ +} POST_PACKED_STRUCTURE + +#define INIT_FIXED_ARRAY(arr) ({ \ + int i; \ + for (i = 0; i < SPA_N_ELEMENTS(arr.table); i++) \ + arr.table[i] = EMPTY; \ + arr.counter = 0; \ +}) + +#define ADD_FIXED_ARRAY(arr,item) ({ \ + int i,ret = -1; \ + for (i = 0; i < SPA_N_ELEMENTS(arr.table); i++) { \ + if (arr.table[i] == EMPTY) { \ + arr.table[i] = item; \ + arr.counter++; \ + ret = 0; \ + break; \ + } \ + } \ + ret; \ +}) + +#define MAKE_FIXED_ARRAY1(size) \ +PRE_PACKED_STRUCTURE \ +struct { \ + MAKE_FIXED_ARRAY(size) array; \ + bool used; \ +} POST_PACKED_STRUCTURE + +#define INIT_FIXED_ARRAY1(arr) ({ \ + INIT_FIXED_ARRAY(arr.array); \ + arr.used = false; \ +}) + +#define ADD_FIXED_ARRAY1(arr,item) ADD_FIXED_ARRAY(arr.array,item) + +#define MAKE_FIXED_MATRIX(size) \ +PRE_PACKED_STRUCTURE \ +struct { \ + jack_int_t table[size][size]; \ +} POST_PACKED_STRUCTURE + +#define INIT_FIXED_MATRIX(mat,idx) ({ \ + int i; \ + for (i = 0; i < SPA_N_ELEMENTS(mat.table[0]); i++){ \ + mat.table[idx][i] = 0; \ + mat.table[i][idx] = 0; \ + } \ +}) + +PRE_PACKED_STRUCTURE +struct jack_activation_count { + int32_t value; + int32_t count; +} POST_PACKED_STRUCTURE; + +static inline void jack_activation_count_set_value(struct jack_activation_count *cnt, int32_t val) { + cnt->value = val; +} + +#define MAKE_LOOP_FEEDBACK(size) \ +PRE_PACKED_STRUCTURE \ +struct { \ + int table[size][3]; \ +} POST_PACKED_STRUCTURE + +#define INIT_LOOP_FEEDBACK(arr,size) ({ \ + int i; \ + for (i = 0; i < size; i++) { \ + arr.table[i][0] = EMPTY; \ + arr.table[i][1] = EMPTY; \ + arr.table[i][2] = 0; \ + } \ +}) + +PRE_PACKED_STRUCTURE +struct jack_connection_manager { + MAKE_FIXED_ARRAY(CONNECTION_NUM_FOR_PORT) connection[PORT_NUM_MAX]; + MAKE_FIXED_ARRAY1(PORT_NUM_FOR_CLIENT) input_port[CLIENT_NUM]; + MAKE_FIXED_ARRAY(PORT_NUM_FOR_CLIENT) output_port[CLIENT_NUM]; + MAKE_FIXED_MATRIX(CLIENT_NUM) connection_ref; + struct jack_activation_count input_counter[CLIENT_NUM]; + MAKE_LOOP_FEEDBACK(CONNECTION_NUM_FOR_PORT) loop_feedback; +} POST_PACKED_STRUCTURE; + +static inline void +jack_connection_manager_init_ref_num(struct jack_connection_manager *conn, int ref_num) +{ + INIT_FIXED_ARRAY1(conn->input_port[ref_num]); + INIT_FIXED_ARRAY(conn->output_port[ref_num]); + INIT_FIXED_MATRIX(conn->connection_ref, ref_num); + jack_activation_count_set_value (&conn->input_counter[ref_num], 0); +} + +static inline void +jack_connection_manager_init(struct jack_connection_manager *conn) +{ + int i; + for (i = 0; i < PORT_NUM_MAX; i++) + INIT_FIXED_ARRAY(conn->connection[i]); + + INIT_LOOP_FEEDBACK(conn->loop_feedback, CONNECTION_NUM_FOR_PORT); + + for (i = 0; i < CLIENT_NUM; i++) + jack_connection_manager_init_ref_num(conn, i); +} + +static inline int +jack_connection_manager_add_port(struct jack_connection_manager *conn, bool input, + int ref_num, jack_port_id_t port_id) +{ + if (input) + return ADD_FIXED_ARRAY1(conn->input_port[ref_num], port_id); + else + return ADD_FIXED_ARRAY(conn->output_port[ref_num], port_id); +} + +PRE_PACKED_STRUCTURE +struct jack_atomic_counter { + union { + struct { + uint16_t short_val1; // Cur + uint16_t short_val2; // Next + } scounter; + uint32_t long_val; + } info; +} POST_PACKED_STRUCTURE; + +#define Counter(e) (e).info.long_val +#define CurIndex(e) (e).info.scounter.short_val1 +#define NextIndex(e) (e).info.scounter.short_val2 + +#define CurArrayIndex(e) (CurIndex(e) & 0x0001) +#define NextArrayIndex(e) ((CurIndex(e) + 1) & 0x0001) + +#define MAKE_ATOMIC_STATE(type) \ +PRE_PACKED_STRUCTURE \ +struct { \ + type state[2]; \ + volatile struct jack_atomic_counter counter; \ + int32_t call_write_counter; \ +} POST_PACKED_STRUCTURE + +PRE_PACKED_STRUCTURE +struct jack_atomic_array_counter { + union { + struct { + unsigned char byte_val[4]; + } scounter; + uint32_t long_val; + } info; +} POST_PACKED_STRUCTURE; + +#define MAKE_ATOMIC_ARRAY_STATE(type) \ +PRE_PACKED_STRUCTURE \ +struct { \ + type state[3]; \ + volatile struct jack_atomic_array_counter counter; \ +} POST_PACKED_STRUCTURE + +PRE_PACKED_STRUCTURE +struct jack_graph_manager { + jack_shm_info_t info; + MAKE_ATOMIC_STATE(struct jack_connection_manager) state; + unsigned int port_max; + struct jack_client_timing client_timing[CLIENT_NUM]; + struct jack_port port_array[0]; +} POST_PACKED_STRUCTURE; + +static inline struct jack_graph_manager * +jack_graph_manager_alloc(int port_max) +{ + struct jack_graph_manager *mgr; + jack_shm_info_t info; + size_t i, size; + + size = sizeof(struct jack_graph_manager) + port_max * sizeof(struct jack_port); + + if (jack_shm_alloc(size, &info, segment_num++) < 0) + return NULL; + + mgr = (struct jack_graph_manager *)jack_shm_addr(&info); + mgr->info = info; + + jack_connection_manager_init(&mgr->state.state[0]); + jack_connection_manager_init(&mgr->state.state[1]); + mgr->port_max = port_max; + + for (i = 0; i < port_max; i++) { + mgr->port_array[i].in_use = false; + mgr->port_array[i].ref_num = -1; + } + return mgr; +} + +static inline jack_port_id_t +jack_graph_manager_allocate_port(struct jack_graph_manager *mgr, + int ref_num, const char* port_name, int type_id, + enum JackPortFlags flags) +{ + int i; + for (i = 1; i < mgr->port_max; i++) { + if (!mgr->port_array[i].in_use) { + jack_port_init(&mgr->port_array[i], ref_num, port_name, type_id, flags); + return i; + } + } + return NO_PORT; +} + + +static inline struct jack_connection_manager * +jack_graph_manager_next_start(struct jack_graph_manager *manager) +{ + uint32_t next_index; + + if (manager->state.call_write_counter++ == 0) { + struct jack_atomic_counter old_val; + struct jack_atomic_counter new_val; + uint32_t cur_index; + bool need_copy; + do { + old_val = manager->state.counter; + new_val = old_val; + cur_index = CurArrayIndex(new_val); + next_index = NextArrayIndex(new_val); + need_copy = (CurIndex(new_val) == NextIndex(new_val)); + NextIndex(new_val) = CurIndex(new_val); // Invalidate next index + } + while (!__atomic_compare_exchange_n((uint32_t*)&manager->state.counter, + (uint32_t*)&Counter(old_val), + Counter(new_val), + false, + __ATOMIC_SEQ_CST, + __ATOMIC_SEQ_CST)); + + if (need_copy) + memcpy(&manager->state.state[next_index], + &manager->state.state[cur_index], + sizeof(struct jack_connection_manager)); + } + else { + next_index = NextArrayIndex(manager->state.counter); + } + return &manager->state.state[next_index]; +} + +static inline void +jack_graph_manager_next_stop(struct jack_graph_manager *manager) +{ + if (--manager->state.call_write_counter == 0) { + struct jack_atomic_counter old_val; + struct jack_atomic_counter new_val; + do { + old_val = manager->state.counter; + new_val = old_val; + NextIndex(new_val)++; // Set next index + } + while (!__atomic_compare_exchange_n((uint32_t*)&manager->state.counter, + (uint32_t*)&Counter(old_val), + Counter(new_val), + false, + __ATOMIC_SEQ_CST, + __ATOMIC_SEQ_CST)); + } +} + +typedef enum { + TransportCommandNone = 0, + TransportCommandStart = 1, + TransportCommandStop = 2, +} transport_command_t; + +PRE_PACKED_STRUCTURE +struct jack_transport_engine { + MAKE_ATOMIC_ARRAY_STATE(jack_position_t) state; + jack_transport_state_t transport_state; + volatile transport_command_t transport_cmd; + transport_command_t previous_cmd; /* previous transport_cmd */ + jack_time_t sync_timeout; + int sync_time_left; + int time_base_master; + bool pending_pos; + bool network_sync; + bool conditionnal; + int32_t write_counter; +} POST_PACKED_STRUCTURE; + +PRE_PACKED_STRUCTURE +struct jack_timer { + jack_nframes_t frames; + jack_time_t current_wakeup; + jack_time_t current_callback; + jack_time_t next_wakeup; + float period_usecs; + float filter_omega; /* set once, never altered */ + bool initialized; +} POST_PACKED_STRUCTURE; + +PRE_PACKED_STRUCTURE +struct jack_frame_timer { + MAKE_ATOMIC_STATE(struct jack_timer) state; + bool first_wakeup; +} POST_PACKED_STRUCTURE; + +#ifdef JACK_MONITOR +PRE_PACKED_STRUCTURE +struct jack_timing_measure_client { + int ref_num; + jack_time_t signaled_at; + jack_time_t awake_at; + jack_time_t finished_at; + jack_client_state_t status; +} POST_PACKED_STRUCTURE; + +PRE_PACKED_STRUCTURE +struct jack_timing_client_interval { + int ref_num; + char name[JACK_CLIENT_NAME_SIZE+1]; + int begin_interval; + int end_interval; +} POST_PACKED_STRUCTURE; + +PRE_PACKED_STRUCTURE +struct jack_timing_measure { + unsigned int audio_cycle; + jack_time_t period_usecs; + jack_time_t cur_cycle_begin; + jack_time_t prev_cycle_end; + struct jack_timing_measure_client client_table[CLIENT_NUM]; +} POST_PACKED_STRUCTURE; + +PRE_PACKED_STRUCTURE +struct jack_engine_profiling { + struct jack_timing_measure profile_table[TIME_POINTS]; + struct jack_timing_client_interval interval_table[MEASURED_CLIENTS]; + + unsigned int audio_cycle; + unsigned int measured_client; +} POST_PACKED_STRUCTURE; +#endif + +PRE_PACKED_STRUCTURE +struct jack_engine_control { + jack_shm_info_t info; + jack_nframes_t buffer_size; + jack_nframes_t sample_rate; + bool sync_mode; + bool temporary; + jack_time_t period_usecs; + jack_time_t timeout_usecs; + float max_delayed_usecs; + float xrun_delayed_usecs; + bool timeout; + bool real_time; + bool saved_real_time; + int server_priority; + int client_priority; + int max_client_priority; + char server_name[JACK_SERVER_NAME_SIZE]; + struct jack_transport_engine transport; + jack_timer_type_t clock_source; + int driver_num; + bool verbose; + + jack_time_t prev_cycle_time; + jack_time_t cur_cycle_time; + jack_time_t spare_usecs; + jack_time_t max_usecs; + jack_time_t rolling_client_usecs[JACK_ENGINE_ROLLING_COUNT]; + unsigned int rolling_client_usecs_cnt; + int rolling_client_usecs_index; + int rolling_interval; + float CPU_load; + + uint64_t period; + uint64_t computation; + uint64_t constraint; + + struct jack_frame_timer frame_timer; + +#ifdef JACK_MONITOR + struct jack_engine_profiling profiler; +#endif +} POST_PACKED_STRUCTURE; + +static inline void +jack_engine_control_reset_rolling_usecs(struct jack_engine_control *ctrl) +{ + memset(ctrl->rolling_client_usecs, 0, sizeof(ctrl->rolling_client_usecs)); + ctrl->rolling_client_usecs_index = 0; + ctrl->rolling_client_usecs_cnt = 0; + ctrl->spare_usecs = 0; + ctrl->rolling_interval = floor((JACK_ENGINE_ROLLING_INTERVAL * 1000.f) / ctrl->period_usecs); +} + +static inline struct jack_engine_control * +jack_engine_control_alloc(const char* name) +{ + struct jack_engine_control *ctrl; + jack_shm_info_t info; + size_t size; + + size = sizeof(struct jack_engine_control); + if (jack_shm_alloc(size, &info, segment_num++) < 0) + return NULL; + + ctrl = (struct jack_engine_control *)jack_shm_addr(&info); + ctrl->info = info; + + ctrl->buffer_size = 512; + ctrl->sample_rate = 48000; + ctrl->sync_mode = false; + ctrl->temporary = false; + ctrl->period_usecs = 1000000.f / ctrl->sample_rate * ctrl->buffer_size; + ctrl->timeout_usecs = 0; + ctrl->max_delayed_usecs = 0.f; + ctrl->xrun_delayed_usecs = 0.f; + ctrl->timeout = false; + ctrl->real_time = true; + ctrl->saved_real_time = false; + ctrl->server_priority = 20; + ctrl->client_priority = 15; + ctrl->max_client_priority = 19; + strcpy(ctrl->server_name, name); + ctrl->clock_source = 0; + ctrl->driver_num = 0; + ctrl->verbose = true; + + ctrl->prev_cycle_time = 0; + ctrl->cur_cycle_time = 0; + ctrl->spare_usecs = 0; + ctrl->max_usecs = 0; + jack_engine_control_reset_rolling_usecs(ctrl); + ctrl->CPU_load = 0.f; + + ctrl->period = 0; + ctrl->computation = 0; + ctrl->constraint = 0; + + return ctrl; +} + +PRE_PACKED_STRUCTURE +struct jack_client_control { + jack_shm_info_t info; + char name[JACK_CLIENT_NAME_SIZE+1]; + bool callback[jack_notify_max]; + volatile jack_transport_state_t transport_state; + volatile bool transport_sync; + volatile bool transport_timebase; + int ref_num; + int PID; + bool active; + + int session_ID; + char session_command[JACK_SESSION_COMMAND_SIZE]; + jack_session_flags_t session_flags; +} POST_PACKED_STRUCTURE; + +static inline struct jack_client_control * +jack_client_control_alloc(const char* name, int pid, int ref_num, int uuid) +{ + struct jack_client_control *ctrl; + jack_shm_info_t info; + size_t size; + + size = sizeof(struct jack_client_control); + if (jack_shm_alloc(size, &info, segment_num++) < 0) + return NULL; + + ctrl = (struct jack_client_control *)jack_shm_addr(&info); + ctrl->info = info; + + strcpy(ctrl->name, name); + for (int i = 0; i < jack_notify_max; i++) + ctrl->callback[i] = false; + + // Always activated + ctrl->callback[jack_notify_AddClient] = true; + ctrl->callback[jack_notify_RemoveClient] = true; + ctrl->callback[jack_notify_ActivateClient] = true; + ctrl->callback[jack_notify_LatencyCallback] = true; + // So that driver synchro are correctly setup in "flush" or "normal" mode + ctrl->callback[jack_notify_StartFreewheelCallback] = true; + ctrl->callback[jack_notify_StopFreewheelCallback] = true; + ctrl->ref_num = ref_num; + ctrl->PID = pid; + ctrl->transport_state = JackTransportStopped; + ctrl->transport_sync = false; + ctrl->transport_timebase = false; + ctrl->active = false; + ctrl->session_ID = uuid; + + return ctrl; +} diff --git a/src/modules/module-jack/shm.c b/src/modules/module-jack/shm.c new file mode 100644 index 00000000..c3989bcf --- /dev/null +++ b/src/modules/module-jack/shm.c @@ -0,0 +1,1303 @@ +/* This module provides a set of abstract shared memory interfaces + * with support using both System V and POSIX shared memory + * implementations. The code is divided into three sections: + * + * - common (interface-independent) code + * - POSIX implementation + * - System V implementation + * - Windows implementation + * + * The implementation used is determined by whether USE_POSIX_SHM was + * set in the ./configure step. + */ + +/* + Copyright (C) 2001-2003 Paul Davis + Copyright (C) 2005-2012 Grame + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + */ + +//#include "JackConstants.h" + +#ifdef WIN32 +#include <process.h> +#include <stdio.h> +#else + +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <limits.h> +#include <errno.h> +#include <dirent.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/shm.h> +#include <sys/sem.h> +#include <stdlib.h> + +#endif + +#include "defs.h" +#include "shm.h" + +static int GetUID() +{ +#ifdef WIN32 + return _getpid(); + //#error "No getuid function available" +#else + return getuid(); +#endif +} + +static int GetPID() +{ +#ifdef WIN32 + return _getpid(); +#else + return getpid(); +#endif +} + +#ifdef USE_POSIX_SHM +static jack_shmtype_t jack_shmtype = shm_POSIX; +#elif WIN32 +static jack_shmtype_t jack_shmtype = shm_WIN32; +#else +static jack_shmtype_t jack_shmtype = shm_SYSV; +#endif + +/* interface-dependent forward declarations */ +static int jack_access_registry (jack_shm_info_t *ri); +static int jack_create_registry (jack_shm_info_t *ri); +static void jack_remove_shm (jack_shm_id_t *id); + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * common interface-independent section + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* The JACK SHM registry is a chunk of memory for keeping track of the + * shared memory used by each active JACK server. This allows the + * server to clean up shared memory when it exits. To avoid memory + * leakage due to kill -9, crashes or debugger-driven exits, this + * cleanup is also done when a new instance of that server starts. + */ + +/* per-process global data for the SHM interfaces */ +static jack_shm_id_t registry_id; /* SHM id for the registry */ + +#ifdef WIN32 +static jack_shm_info_t registry_info = {/* SHM info for the registry */ + JACK_SHM_NULL_INDEX, + NULL +}; +#else +static jack_shm_info_t registry_info = { /* SHM info for the registry */ + .index = JACK_SHM_NULL_INDEX, + .ptr.attached_at = MAP_FAILED +}; +#endif + +/* pointers to registry header and array */ +static jack_shm_header_t *jack_shm_header = NULL; +static jack_shm_registry_t *jack_shm_registry = NULL; +static char jack_shm_server_prefix[JACK_SERVER_NAME_SIZE+1] = ""; + +/* jack_shm_lock_registry() serializes updates to the shared memory + * segment JACK uses to keep track of the SHM segments allocated to + * all its processes, including multiple servers. + * + * This is not a high-contention lock, but it does need to work across + * multiple processes. High transaction rates and realtime safety are + * not required. Any solution needs to at least be portable to POSIX + * and POSIX-like systems. + * + * We must be particularly careful to ensure that the lock be released + * if the owning process terminates abnormally. Otherwise, a segfault + * or kill -9 at the wrong moment could prevent JACK from ever running + * again on that machine until after a reboot. + */ + +#define JACK_SEMAPHORE_KEY 0x282929 +#ifndef USE_POSIX_SHM +#define JACK_SHM_REGISTRY_KEY JACK_SEMAPHORE_KEY +#endif + +static int semid = -1; + +#ifdef WIN32 + +#include <psapi.h> +#include <lmcons.h> + +static BOOL check_process_running(DWORD process_id) +{ + DWORD aProcesses[2048], cbNeeded, cProcesses; + unsigned int i; + + // Enumerate all processes + if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) { + return FALSE; + } + + // Calculate how many process identifiers were returned. + cProcesses = cbNeeded / sizeof(DWORD); + + for (i = 0; i < cProcesses; i++) { + if (aProcesses[i] == process_id) { + // Process process_id is running... + return TRUE; + } + } + return FALSE; +} + +static int +semaphore_init () {return 0;} + +static int +semaphore_add (int value) {return 0;} + +#else +/* all semaphore errors are fatal -- issue message, but do not return */ +static void +semaphore_error (char *msg) +{ + jack_error ("JACK semaphore error: %s (%s)", + msg, strerror (errno)); +} + +static int +semaphore_init () +{ + key_t semkey = JACK_SEMAPHORE_KEY; + struct sembuf sbuf; + int create_flags = IPC_CREAT | IPC_EXCL + | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + /* Get semaphore ID associated with this key. */ + if ((semid = semget(semkey, 0, 0)) == -1) { + + /* Semaphore does not exist - Create. */ + if ((semid = semget(semkey, 1, create_flags)) != -1) { + + /* Initialize the semaphore, allow one owner. */ + sbuf.sem_num = 0; + sbuf.sem_op = 1; + sbuf.sem_flg = 0; + if (semop(semid, &sbuf, 1) == -1) { + semaphore_error ("semop"); + return -1; + } + + } else if (errno == EEXIST) { + if ((semid = semget(semkey, 0, 0)) == -1) { + semaphore_error ("semget"); + return -1; + } + + } else { + semaphore_error ("semget creation"); + return -1; + } + } + + return 0; +} + +static inline int +semaphore_add (int value) +{ + struct sembuf sbuf; + + sbuf.sem_num = 0; + sbuf.sem_op = value; + sbuf.sem_flg = SEM_UNDO; + + if (semop(semid, &sbuf, 1) == -1) { + semaphore_error ("semop"); + return -1; + } + + return 0; +} + +#endif + +static int +jack_shm_lock_registry (void) +{ + if (semid == -1) { + if (semaphore_init () < 0) + return -1; + } + + return semaphore_add (-1); +} + +static void +jack_shm_unlock_registry (void) +{ + semaphore_add (1); +} + +static void +jack_shm_init_registry () +{ + /* registry must be locked */ + int i; + + memset (jack_shm_header, 0, JACK_SHM_REGISTRY_SIZE); + + jack_shm_header->magic = JACK_SHM_MAGIC; + //jack_shm_header->protocol = JACK_PROTOCOL_VERSION; + jack_shm_header->type = jack_shmtype; + jack_shm_header->size = JACK_SHM_REGISTRY_SIZE; + jack_shm_header->hdr_len = sizeof (jack_shm_header_t); + jack_shm_header->entry_len = sizeof (jack_shm_registry_t); + + for (i = 0; i < MAX_SHM_ID; ++i) { + jack_shm_registry[i].index = i; + } +} + +static int +jack_shm_validate_registry () +{ + /* registry must be locked */ + + if ((jack_shm_header->magic == JACK_SHM_MAGIC) + //&& (jack_shm_header->protocol == JACK_PROTOCOL_VERSION) + && (jack_shm_header->type == jack_shmtype) + && (jack_shm_header->size == JACK_SHM_REGISTRY_SIZE) + && (jack_shm_header->hdr_len == sizeof (jack_shm_header_t)) + && (jack_shm_header->entry_len == sizeof (jack_shm_registry_t))) { + + return 0; /* registry OK */ + } + + return -1; +} + +/* set a unique per-user, per-server shm prefix string + * + * According to the POSIX standard: + * + * "The name argument conforms to the construction rules for a + * pathname. If name begins with the slash character, then processes + * calling shm_open() with the same value of name refer to the same + * shared memory object, as long as that name has not been + * removed. If name does not begin with the slash character, the + * effect is implementation-defined. The interpretation of slash + * characters other than the leading slash character in name is + * implementation-defined." + * + * Since the Linux implementation does not allow slashes *within* the + * name, in the interest of portability we use colons instead. + */ +static void +jack_set_server_prefix (const char *server_name) +{ +#ifdef WIN32 + char buffer[UNLEN+1]={0}; + DWORD len = UNLEN+1; + GetUserName(buffer, &len); + snprintf (jack_shm_server_prefix, sizeof (jack_shm_server_prefix), + "jack-%s:%s:", buffer, server_name); +#else + snprintf (jack_shm_server_prefix, sizeof (jack_shm_server_prefix), + "jack-%d:%s:", GetUID(), server_name); +#endif +} + +/* gain server addressability to shared memory registration segment + * + * returns: 0 if successful + */ +static int +jack_server_initialize_shm (int new_registry) +{ + int rc; + + if (jack_shm_header) + return 0; /* already initialized */ + + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + rc = jack_access_registry (®istry_info); + + if (new_registry) { + jack_remove_shm (®istry_id); + rc = ENOENT; + } + + switch (rc) { + case ENOENT: /* registry does not exist */ + rc = jack_create_registry (®istry_info); + break; + case 0: /* existing registry */ + if (jack_shm_validate_registry () == 0) + break; + /* else it was invalid, so fall through */ + case EINVAL: /* bad registry */ + /* Apparently, this registry was created by an older + * JACK version. Delete it so we can try again. */ + jack_release_shm (®istry_info); + jack_remove_shm (®istry_id); + if ((rc = jack_create_registry (®istry_info)) != 0) { + jack_error ("incompatible shm registry (%s)", + strerror (errno)); +#ifndef USE_POSIX_SHM + jack_error ("to delete, use `ipcrm -M 0x%8x'", + JACK_SHM_REGISTRY_KEY); +#endif + } + break; + default: /* failure return code */ + break; + } + + jack_shm_unlock_registry (); + return rc; +} + +/* gain client addressability to shared memory registration segment + * + * NOTE: this function is no longer used for server initialization, + * instead it calls jack_register_server(). + * + * returns: 0 if successful + */ +int +jack_initialize_shm (const char *server_name) +{ + int rc; + + if (jack_shm_header) + return 0; /* already initialized */ + + jack_set_server_prefix (server_name); + + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + if ((rc = jack_access_registry (®istry_info)) == 0) { + if ((rc = jack_shm_validate_registry ()) != 0) { + jack_error ("Incompatible shm registry, " + "are jackd and libjack in sync?"); + } + } + jack_shm_unlock_registry (); + + return rc; +} + + +char* jack_shm_addr (jack_shm_info_t* si) +{ + return (char*)si->ptr.attached_at; +} + +void +jack_destroy_shm (jack_shm_info_t* si) +{ + /* must NOT have the registry locked */ + if (si->index == JACK_SHM_NULL_INDEX) + return; /* segment not allocated */ + + jack_remove_shm (&jack_shm_registry[si->index].id); + jack_release_shm_info (si->index); +} + +jack_shm_registry_t * +jack_get_free_shm_info () +{ + /* registry must be locked */ + jack_shm_registry_t* si = NULL; + int i; + + for (i = 0; i < MAX_SHM_ID; ++i) { + if (jack_shm_registry[i].size == 0) { + break; + } + } + + if (i < MAX_SHM_ID) { + si = &jack_shm_registry[i]; + } + + return si; +} + +static void +jack_release_shm_entry (jack_shm_registry_index_t index) +{ + /* the registry must be locked */ + jack_shm_registry[index].size = 0; + jack_shm_registry[index].allocator = 0; + memset (&jack_shm_registry[index].id, 0, + sizeof (jack_shm_registry[index].id)); +} + +int +jack_release_shm_info (jack_shm_registry_index_t index) +{ + /* must NOT have the registry locked */ + if (jack_shm_registry[index].allocator == GetPID()) { + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + jack_release_shm_entry (index); + jack_shm_unlock_registry (); + } + + return 0; +} + +/* Claim server_name for this process. + * + * returns 0 if successful + * EEXIST if server_name was already active for this user + * ENOSPC if server registration limit reached + * ENOMEM if unable to access shared memory registry + */ +int +jack_register_server (const char *server_name, int new_registry) +{ + int i, res = 0; + + jack_set_server_prefix (server_name); + + if (jack_server_initialize_shm (new_registry)) + return ENOMEM; + + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + /* See if server_name already registered. Since server names + * are per-user, we register the unique server prefix string. + */ + for (i = 0; i < MAX_SERVERS; i++) { + + if (strncmp (jack_shm_header->server[i].name, + jack_shm_server_prefix, + JACK_SERVER_NAME_SIZE) != 0) + continue; /* no match */ + + if (jack_shm_header->server[i].pid == GetPID()){ + res = 0; /* it's me */ + goto unlock; + } + + /* see if server still exists */ + #ifdef WIN32 + if (check_process_running(jack_shm_header->server[i].pid)) { + res = EEXIST; /* other server running */ + goto unlock; + } + #else + if (kill (jack_shm_header->server[i].pid, 0) == 0) { + res = EEXIST; /* other server running */ + goto unlock; + } + #endif + + /* it's gone, reclaim this entry */ + memset (&jack_shm_header->server[i], 0, + sizeof (jack_shm_server_t)); + } + + /* find a free entry */ + for (i = 0; i < MAX_SERVERS; i++) { + if (jack_shm_header->server[i].pid == 0) + break; + } + + if (i >= MAX_SERVERS){ + res = ENOSPC; /* out of space */ + goto unlock; + } + + /* claim it */ + jack_shm_header->server[i].pid = GetPID(); + strncpy (jack_shm_header->server[i].name, + jack_shm_server_prefix, + JACK_SERVER_NAME_SIZE); + + unlock: + jack_shm_unlock_registry (); + return res; +} + +/* release server_name registration */ +int +jack_unregister_server (const char *server_name /* unused */) +{ + int i; + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + for (i = 0; i < MAX_SERVERS; i++) { + if (jack_shm_header->server[i].pid == GetPID()) { + memset (&jack_shm_header->server[i], 0, + sizeof (jack_shm_server_t)); + } + } + + jack_shm_unlock_registry (); + return 0; +} + +/* called for server startup and termination */ +int +jack_cleanup_shm () +{ + int i; + int destroy; + jack_shm_info_t copy; + + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + for (i = 0; i < MAX_SHM_ID; i++) { + jack_shm_registry_t* r; + + r = &jack_shm_registry[i]; + memcpy (©, r, sizeof (jack_shm_info_t)); + destroy = FALSE; + + /* ignore unused entries */ + if (r->allocator == 0) + continue; + + /* is this my shm segment? */ + if (r->allocator == GetPID()) { + + /* allocated by this process, so unattach + and destroy. */ + jack_release_shm (©); + destroy = TRUE; + + } else { + + /* see if allocator still exists */ + #ifdef WIN32 + //jack_info("TODO: kill API not available !!"); + #else + if (kill (r->allocator, 0)) { + if (errno == ESRCH) { + /* allocator no longer exists, + * so destroy */ + destroy = TRUE; + } + } + #endif + } + + if (destroy) { + + int index = copy.index; + + if ((index >= 0) && (index < MAX_SHM_ID)) { + jack_remove_shm (&jack_shm_registry[index].id); + jack_release_shm_entry (index); + } + r->size = 0; + r->allocator = 0; + } + } + + jack_shm_unlock_registry (); + return TRUE; +} + +/* resize a shared memory segment + * + * There is no way to resize a System V shm segment. Resizing is + * possible with POSIX shm, but not with the non-conformant Mac OS X + * implementation. Since POSIX shm is mainly used on that platform, + * it's simpler to treat them both the same. + * + * So, we always resize by deleting and reallocating. This is + * tricky, because the old segment will not disappear until + * all the clients have released it. We only do what we can + * from here. + * + * This is not done under a single lock. I don't even want to think + * about all the things that could possibly go wrong if multple + * processes tried to resize the same segment concurrently. That + * probably doesn't happen. + */ +int +jack_resize_shm (jack_shm_info_t* si, jack_shmsize_t size) +{ + jack_shm_id_t id; + + /* The underlying type of `id' differs for SYSV and POSIX */ + memcpy (&id, &jack_shm_registry[si->index].id, sizeof (id)); + + jack_release_shm (si); + jack_destroy_shm (si); + + if (jack_shmalloc ((char *) id, size, si)) { + return -1; + } + + return jack_attach_shm (si); +} + +int +jack_attach_lib_shm (jack_shm_info_t* si) +{ + int res = jack_attach_shm(si); + if (res == 0) + si->size = jack_shm_registry[si->index].size; // Keep size in si struct + return res; +} + +int +jack_attach_lib_shm_read (jack_shm_info_t* si) +{ + int res = jack_attach_shm_read(si); + if (res == 0) + si->size = jack_shm_registry[si->index].size; // Keep size in si struct + return res; +} + +#ifdef USE_POSIX_SHM + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * POSIX interface-dependent functions + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* gain addressability to existing SHM registry segment + * + * sets up global registry pointers, if successful + * + * returns: 0 if existing registry accessed successfully + * ENOENT if registry does not exist + * EINVAL if registry exists, but has the wrong size + */ +static int +jack_access_registry (jack_shm_info_t *ri) +{ + /* registry must be locked */ + int shm_fd; + + strncpy (registry_id, "/jack-shm-registry", sizeof (registry_id)); + + /* try to open an existing segment */ + if ((shm_fd = shm_open (registry_id, O_RDWR, 0666)) < 0) { + int rc = errno; + if (errno != ENOENT) { + jack_error ("Cannot open existing shm registry segment" + " (%s)", strerror (errno)); + } + close (shm_fd); + return rc; + } + + if ((ri->ptr.attached_at = mmap (0, JACK_SHM_REGISTRY_SIZE, + PROT_READ|PROT_WRITE, + MAP_SHARED, shm_fd, 0)) == MAP_FAILED) { + jack_error ("Cannot mmap shm registry segment (%s)", + strerror (errno)); + close (shm_fd); + return EINVAL; + } + + /* set up global pointers */ + ri->index = JACK_SHM_REGISTRY_INDEX; + jack_shm_header = ri->ptr.attached_at; + jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1); + + close (shm_fd); + return 0; +} + +/* create a new SHM registry segment + * + * sets up global registry pointers, if successful + * + * returns: 0 if registry created successfully + * nonzero error code if unable to allocate a new registry + */ +static int +jack_create_registry (jack_shm_info_t *ri) +{ + /* registry must be locked */ + int shm_fd; + + strncpy (registry_id, "/jack-shm-registry", sizeof (registry_id)); + + if ((shm_fd = shm_open (registry_id, O_RDWR|O_CREAT, 0666)) < 0) { + int rc = errno; + jack_error ("Cannot create shm registry segment (%s)", + strerror (errno)); + return rc; + } + + /* Previous shm_open result depends of the actual value of umask, force correct file permisssion here */ + if (fchmod(shm_fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) < 0) { + jack_log("Cannot chmod jack-shm-registry (%s)", strerror (errno)); + } + + /* Set the desired segment size. NOTE: the non-conformant Mac + * OS X POSIX shm only allows ftruncate() on segment creation. + */ + if (ftruncate (shm_fd, JACK_SHM_REGISTRY_SIZE) < 0) { + int rc = errno; + jack_error ("Cannot set registry size (%s)", strerror (errno)); + jack_remove_shm (®istry_id); + close (shm_fd); + return rc; + } + + if ((ri->ptr.attached_at = mmap (0, JACK_SHM_REGISTRY_SIZE, + PROT_READ|PROT_WRITE, + MAP_SHARED, shm_fd, 0)) == MAP_FAILED) { + jack_error ("Cannot mmap shm registry segment (%s)", + strerror (errno)); + jack_remove_shm (®istry_id); + close (shm_fd); + return EINVAL; + } + + /* set up global pointers */ + ri->index = JACK_SHM_REGISTRY_INDEX; + jack_shm_header = ri->ptr.attached_at; + jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1); + + /* initialize registry contents */ + jack_shm_init_registry (); + close (shm_fd); + return 0; +} + +static void +jack_remove_shm (jack_shm_id_t *id) +{ + /* registry may or may not be locked */ + shm_unlink ((char *) id); +} + +void +jack_release_shm (jack_shm_info_t* si) +{ + /* registry may or may not be locked */ + if (si->ptr.attached_at != MAP_FAILED) { + munmap (si->ptr.attached_at, jack_shm_registry[si->index].size); + } +} + +void +jack_release_lib_shm (jack_shm_info_t* si) +{ + /* registry may or may not be locked */ + if (si->ptr.attached_at != MAP_FAILED) { + munmap (si->ptr.attached_at, si->size); + } +} + +/* allocate a POSIX shared memory segment */ +int +jack_shmalloc (const char *shm_name, jack_shmsize_t size, jack_shm_info_t* si) +{ + jack_shm_registry_t* registry; + int shm_fd; + int rc = -1; + char name[SHM_NAME_MAX+1]; + + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + if ((registry = jack_get_free_shm_info ()) == NULL) { + jack_error ("shm registry full"); + goto unlock; + } + + /* On Mac OS X, the maximum length of a shared memory segment + * name is SHM_NAME_MAX (instead of NAME_MAX or PATH_MAX as + * defined by the standard). Unfortunately, Apple sets this + * value so small (about 31 bytes) that it is useless for + * actual names. So, we construct a short name from the + * registry index for uniqueness and ignore the shm_name + * parameter. Bah! + */ + snprintf (name, sizeof (name), "/jack-%d-%d", GetUID(), registry->index); + + if (strlen (name) >= sizeof (registry->id)) { + jack_error ("shm segment name too long %s", name); + goto unlock; + } + + if ((shm_fd = shm_open (name, O_RDWR|O_CREAT, 0666)) < 0) { + jack_error ("Cannot create shm segment %s (%s)", + name, strerror (errno)); + goto unlock; + } + + if (ftruncate (shm_fd, size) < 0) { + jack_error ("Cannot set size of engine shm " + "registry 0 (%s)", + strerror (errno)); + close (shm_fd); + goto unlock; + } + + close (shm_fd); + registry->size = size; + strncpy (registry->id, name, sizeof (registry->id)); + registry->allocator = GetPID(); + si->index = registry->index; + si->ptr.attached_at = MAP_FAILED; /* not attached */ + rc = 0; /* success */ + + unlock: + jack_shm_unlock_registry (); + return rc; +} + +int +jack_attach_shm (jack_shm_info_t* si) +{ + int shm_fd; + jack_shm_registry_t *registry = &jack_shm_registry[si->index]; + + if ((shm_fd = shm_open (registry->id, + O_RDWR, 0666)) < 0) { + jack_error ("Cannot open shm segment %s (%s)", registry->id, + strerror (errno)); + return -1; + } + + if ((si->ptr.attached_at = mmap (0, registry->size, PROT_READ|PROT_WRITE, + MAP_SHARED, shm_fd, 0)) == MAP_FAILED) { + jack_error ("Cannot mmap shm segment %s (%s)", + registry->id, + strerror (errno)); + close (shm_fd); + return -1; + } + + close (shm_fd); + return 0; +} + +int +jack_attach_shm_read (jack_shm_info_t* si) +{ + int shm_fd; + jack_shm_registry_t *registry = &jack_shm_registry[si->index]; + + if ((shm_fd = shm_open (registry->id, + O_RDONLY, 0666)) < 0) { + jack_error ("Cannot open shm segment %s (%s)", registry->id, + strerror (errno)); + return -1; + } + + if ((si->ptr.attached_at = mmap (0, registry->size, PROT_READ, + MAP_SHARED, shm_fd, 0)) == MAP_FAILED) { + jack_error ("Cannot mmap shm segment %s (%s)", + registry->id, + strerror (errno)); + close (shm_fd); + return -1; + } + + close (shm_fd); + return 0; +} + +#elif WIN32 + +static int +jack_access_registry (jack_shm_info_t *ri) +{ + /* registry must be locked */ + HANDLE shm_fd; + strncpy (registry_id, "jack-shm-registry", sizeof (registry_id)); + + /* try to open an existing segment */ + + if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry_id)) == NULL) { + int rc = GetLastError(); + if (rc != ERROR_FILE_NOT_FOUND) { + jack_error ("Cannot open existing shm registry segment (%ld)", rc); + } + return rc; + } + + if ((ri->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, JACK_SHM_REGISTRY_SIZE)) == NULL) { + jack_error ("Cannot mmap shm registry segment (%ld)", GetLastError()); + jack_remove_shm (®istry_id); + CloseHandle (shm_fd); + return EINVAL; + } + + /* set up global pointers */ + ri->index = JACK_SHM_REGISTRY_INDEX; + jack_shm_header = ri->ptr.attached_at; + jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1); + + //CloseHandle(shm_fd); // TO CHECK + return 0; +} + +static int +jack_create_registry (jack_shm_info_t *ri) +{ + /* registry must be locked */ + HANDLE shm_fd; + + strncpy (registry_id, "jack-shm-registry", sizeof (registry_id)); + + if ((shm_fd = CreateFileMapping(INVALID_HANDLE_VALUE, + 0, PAGE_READWRITE, + 0, JACK_SHM_REGISTRY_SIZE, + registry_id)) == NULL || (shm_fd == INVALID_HANDLE_VALUE)) { + int rc = GetLastError(); + jack_error ("Cannot create shm registry segment (%ld)", rc); + return rc; + } + + if ((ri->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, JACK_SHM_REGISTRY_SIZE)) == NULL) { + jack_error ("Cannot mmap shm registry segment (%ld)", GetLastError()); + jack_remove_shm (®istry_id); + CloseHandle (shm_fd); + return EINVAL; + } + + /* set up global pointers */ + ri->index = JACK_SHM_REGISTRY_INDEX; + jack_shm_header = ri->ptr.attached_at; + jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1); + + /* initialize registry contents */ + jack_shm_init_registry (); + + //CloseHandle(shm_fd); // TO CHECK + return 0; +} + +static void +jack_remove_shm (jack_shm_id_t *id) +{ + /* nothing to do */ +} + +void +jack_release_shm (jack_shm_info_t* si) +{ + /* registry may or may not be locked */ + if (si->ptr.attached_at != NULL) { + UnmapViewOfFile (si->ptr.attached_at); + } +} + +void +jack_release_lib_shm (jack_shm_info_t* si) +{ + jack_release_shm(si); +} + +int +jack_shmalloc (const char *shm_name, jack_shmsize_t size, jack_shm_info_t* si) +{ + jack_shm_registry_t* registry; + HANDLE shm_fd; + int rc = -1; + char name[SHM_NAME_MAX+1]; + + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + if ((registry = jack_get_free_shm_info ()) == NULL) { + jack_error ("shm registry full"); + goto unlock; + } + + snprintf (name, sizeof (name), "jack-%d-%d", GetUID(), registry->index); + + if (strlen (name) >= sizeof (registry->id)) { + jack_error ("shm segment name too long %s", name); + goto unlock; + } + + if ((shm_fd = CreateFileMapping(INVALID_HANDLE_VALUE, + 0, PAGE_READWRITE, + 0, size, + name)) == NULL || (shm_fd == INVALID_HANDLE_VALUE)) { + int rc = GetLastError(); + jack_error ("Cannot create shm segment (%ld)",rc); + goto unlock; + } + + //CloseHandle (shm_fd); // TO CHECK + + registry->size = size; + strncpy (registry->id, name, sizeof (registry->id)); + registry->allocator = _getpid(); + si->index = registry->index; + si->ptr.attached_at = NULL; /* not attached */ + rc = 0; /* success */ + + unlock: + jack_shm_unlock_registry (); + return rc; +} + +int +jack_attach_shm (jack_shm_info_t* si) +{ + HANDLE shm_fd; + jack_shm_registry_t *registry = &jack_shm_registry[si->index]; + + if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry->id)) == NULL) { + int rc = GetLastError(); + jack_error ("Cannot open shm segment (%ld)",rc); + return -1; + } + + if ((si->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_ALL_ACCESS, 0, 0, registry->size)) == NULL) { + jack_error ("Cannot mmap shm segment (%ld)", GetLastError()); + jack_remove_shm (®istry_id); + CloseHandle (shm_fd); + return -1; + } + + //CloseHandle (shm_fd); // TO CHECK + return 0; +} + +int +jack_attach_shm_read (jack_shm_info_t* si) +{ + HANDLE shm_fd; + jack_shm_registry_t *registry = &jack_shm_registry[si->index]; + + if ((shm_fd = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, registry->id)) == NULL) { + int rc = GetLastError(); + jack_error ("Cannot open shm segment (%ld)",rc); + return -1; + } + + if ((si->ptr.attached_at = MapViewOfFile (shm_fd, FILE_MAP_READ, 0, 0, registry->size)) == NULL) { + jack_error("Cannot mmap shm segment (%ld)", GetLastError()); + jack_remove_shm(®istry_id); + CloseHandle(shm_fd); + return -1; + } + + //CloseHandle (shm_fd); // TO CHECK + return 0; +} + +#else + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * System V interface-dependent functions + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* gain addressability to existing SHM registry segment + * + * sets up global registry pointers, if successful + * + * returns: 0 if existing registry accessed successfully + * ENOENT if registry does not exist + * EINVAL if registry exists, but has the wrong size + * other nonzero error code if unable to access registry + */ +static int +jack_access_registry (jack_shm_info_t *ri) +{ + /* registry must be locked */ + + /* try without IPC_CREAT to get existing segment */ + if ((registry_id = shmget (JACK_SHM_REGISTRY_KEY, + JACK_SHM_REGISTRY_SIZE, 0666)) < 0) { + + switch (errno) { + + case ENOENT: /* segment does not exist */ + return ENOENT; + + case EINVAL: /* segment exists, but too small */ + /* attempt minimum size access */ + registry_id = shmget (JACK_SHM_REGISTRY_KEY, 1, 0666); + return EINVAL; + + default: /* or other error */ + jack_error ("unable to access shm registry (%s)", + strerror (errno)); + return errno; + } + } + + if ((ri->ptr.attached_at = shmat (registry_id, 0, 0)) < 0) { + jack_error ("Cannot attach shm registry segment (%s)", + strerror (errno)); + return EINVAL; + } + + /* set up global pointers */ + ri->index = JACK_SHM_REGISTRY_INDEX; + jack_shm_header = ri->ptr.attached_at; + jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1); + return 0; +} + +/* create a new SHM registry segment + * + * sets up global registry pointers, if successful + * + * returns: 0 if registry created successfully + * nonzero error code if unable to allocate a new registry + */ +static int +jack_create_registry (jack_shm_info_t *ri) +{ + /* registry must be locked */ + if ((registry_id = shmget (JACK_SHM_REGISTRY_KEY, + JACK_SHM_REGISTRY_SIZE, + 0666|IPC_CREAT)) < 0) { + jack_error ("Cannot create shm registry segment (%s)", + strerror (errno)); + return errno; + } + + if ((ri->ptr.attached_at = shmat (registry_id, 0, 0)) < 0) { + jack_error ("Cannot attach shm registry segment (%s)", + strerror (errno)); + return EINVAL; + } + + /* set up global pointers */ + ri->index = JACK_SHM_REGISTRY_INDEX; + jack_shm_header = ri->ptr.attached_at; + jack_shm_registry = (jack_shm_registry_t *) (jack_shm_header + 1); + + /* initialize registry contents */ + jack_shm_init_registry (); + return 0; +} + +static void +jack_remove_shm (jack_shm_id_t *id) +{ + /* registry may or may not be locked */ + shmctl (*id, IPC_RMID, NULL); +} + +void +jack_release_shm (jack_shm_info_t* si) +{ + /* registry may or may not be locked */ + if (si->ptr.attached_at != MAP_FAILED) { + shmdt (si->ptr.attached_at); + } +} + +void +jack_release_lib_shm (jack_shm_info_t* si) +{ + jack_release_shm(si); +} + +int +jack_shmalloc (const char* name_not_used, jack_shmsize_t size, + jack_shm_info_t* si) +{ + int shmflags; + int shmid; + int rc = -1; + jack_shm_registry_t* registry; + + if (jack_shm_lock_registry () < 0) { + jack_error ("jack_shm_lock_registry fails..."); + return -1; + } + + if ((registry = jack_get_free_shm_info ())) { + + shmflags = 0666 | IPC_CREAT | IPC_EXCL; + + if ((shmid = shmget (IPC_PRIVATE, size, shmflags)) >= 0) { + + registry->size = size; + registry->id = shmid; + registry->allocator = getpid(); + si->index = registry->index; + si->ptr.attached_at = MAP_FAILED; /* not attached */ + rc = 0; + + } else { + jack_error ("Cannot create shm segment %s (%s)", + name_not_used, strerror (errno)); + } + } + + jack_shm_unlock_registry (); + return rc; +} + +int +jack_attach_shm (jack_shm_info_t* si) +{ + if ((si->ptr.attached_at = shmat (jack_shm_registry[si->index].id, 0, 0)) < 0) { + jack_error ("Cannot attach shm segment (%s)", + strerror (errno)); + jack_release_shm_info (si->index); + return -1; + } + return 0; +} + +int +jack_attach_shm_read (jack_shm_info_t* si) +{ + if ((si->ptr.attached_at = shmat (jack_shm_registry[si->index].id, 0, SHM_RDONLY)) < 0) { + jack_error ("Cannot attach shm segment (%s)", + strerror (errno)); + jack_release_shm_info (si->index); + return -1; + } + return 0; +} + +#endif /* !USE_POSIX_SHM */ diff --git a/src/modules/module-jack/shm.h b/src/modules/module-jack/shm.h new file mode 100644 index 00000000..90336eea --- /dev/null +++ b/src/modules/module-jack/shm.h @@ -0,0 +1,216 @@ +/* This module provides a set of abstract shared memory interfaces + * with support using both System V and POSIX shared memory + * implementations. The code is divided into three sections: + * + * - common (interface-independent) code + * - POSIX implementation + * - System V implementation + * - Windows implementation + * + * The implementation used is determined by whether USE_POSIX_SHM was + * set in the ./configure step. + */ + +/* + Copyright (C) 2001-2003 Paul Davis + Copyright (C) 2005-2012 Grame + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + */ + +#ifndef __jack_shm_h__ +#define __jack_shm_h__ + +#include <limits.h> +#include <sys/types.h> + +#include <jack/types.h> + +#define TRUE 1 +#define FALSE 0 + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define MAX_SERVERS 8 /* maximum concurrent servers */ +#define MAX_SHM_ID 256 /* generally about 16 per server */ +#define JACK_SHM_MAGIC 0x4a41434b /* shm magic number: "JACK" */ +#define JACK_SHM_NULL_INDEX -1 /* NULL SHM index */ +#define JACK_SHM_REGISTRY_INDEX -2 /* pseudo SHM index for registry */ + + + /* On Mac OS X, SHM_NAME_MAX is the maximum length of a shared memory + * segment name (instead of NAME_MAX or PATH_MAX as defined by the + * standard). + */ +#ifdef USE_POSIX_SHM + +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + +#ifndef SHM_NAME_MAX +#define SHM_NAME_MAX NAME_MAX +#endif + typedef char shm_name_t[SHM_NAME_MAX]; + typedef shm_name_t jack_shm_id_t; + +#elif WIN32 +#define NAME_MAX 255 +#ifndef SHM_NAME_MAX +#define SHM_NAME_MAX NAME_MAX +#endif + typedef char shm_name_t[SHM_NAME_MAX]; + typedef shm_name_t jack_shm_id_t; + +#elif __ANDROID__ + +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + +#ifndef SHM_NAME_MAX +#define SHM_NAME_MAX NAME_MAX +#endif + typedef char shm_name_t[SHM_NAME_MAX]; + typedef shm_name_t jack_shm_id_t; + typedef int jack_shm_fd_t; + +#else + /* System V SHM */ + typedef int jack_shm_id_t; +#endif /* SHM type */ + + /* shared memory type */ + typedef enum { + shm_POSIX = 1, /* POSIX shared memory */ + shm_SYSV = 2, /* System V shared memory */ + shm_WIN32 = 3, /* Windows 32 shared memory */ + shm_ANDROID = 4 /* Android shared memory */ + } jack_shmtype_t; + + typedef int16_t jack_shm_registry_index_t; + + /** + * A structure holding information about shared memory allocated by + * JACK. this persists across invocations of JACK, and can be used by + * multiple JACK servers. It contains no pointers and is valid across + * address spaces. + * + * The registry consists of two parts: a header including an array of + * server names, followed by an array of segment registry entries. + */ + typedef struct _jack_shm_server { +#ifdef WIN32 + int pid; /* process ID */ +#else + pid_t pid; /* process ID */ +#endif + + char name[JACK_SERVER_NAME_SIZE]; + } + jack_shm_server_t; + + typedef struct _jack_shm_header { + uint32_t magic; /* magic number */ + uint16_t protocol; /* JACK protocol version */ + jack_shmtype_t type; /* shm type */ + jack_shmsize_t size; /* total registry segment size */ + jack_shmsize_t hdr_len; /* size of header */ + jack_shmsize_t entry_len; /* size of registry entry */ + jack_shm_server_t server[MAX_SERVERS]; /* current server array */ + } + jack_shm_header_t; + + typedef struct _jack_shm_registry { + jack_shm_registry_index_t index; /* offset into the registry */ + +#ifdef WIN32 + int allocator; /* PID that created shm segment */ +#else + pid_t allocator; /* PID that created shm segment */ +#endif + + jack_shmsize_t size; /* for POSIX unattach */ + jack_shm_id_t id; /* API specific, see above */ +#ifdef __ANDROID__ + jack_shm_fd_t fd; +#endif + } + jack_shm_registry_t; + +#define JACK_SHM_REGISTRY_SIZE (sizeof (jack_shm_header_t) \ + + sizeof (jack_shm_registry_t) * MAX_SHM_ID) + + /** + * a structure holding information about shared memory + * allocated by JACK. this version is valid only + * for a given address space. It contains a pointer + * indicating where the shared memory has been + * attached to the address space. + */ + + PRE_PACKED_STRUCTURE + struct _jack_shm_info { + jack_shm_registry_index_t index; /* offset into the registry */ + uint32_t size; +#ifdef __ANDROID__ + jack_shm_fd_t fd; +#endif + union { + void *attached_at; /* address where attached */ + char ptr_size[8]; + } ptr; /* a "pointer" that has the same 8 bytes size when compling in 32 or 64 bits */ + } POST_PACKED_STRUCTURE; + + typedef struct _jack_shm_info jack_shm_info_t; + + /* utility functions used only within JACK */ + + void jack_shm_copy_from_registry (jack_shm_info_t*, + jack_shm_registry_index_t); + void jack_shm_copy_to_registry (jack_shm_info_t*, + jack_shm_registry_index_t*); + int jack_release_shm_info (jack_shm_registry_index_t); + char* jack_shm_addr (jack_shm_info_t* si); + + // here begin the API + int jack_register_server (const char *server_name, int new_registry); + int jack_unregister_server (const char *server_name); + + int jack_initialize_shm (const char *server_name); + int jack_initialize_shm_server (void); + int jack_initialize_shm_client (void); + int jack_cleanup_shm (void); + + int jack_shmalloc (const char *shm_name, jack_shmsize_t size, + jack_shm_info_t* result); + void jack_release_shm (jack_shm_info_t*); + void jack_release_lib_shm (jack_shm_info_t*); + void jack_destroy_shm (jack_shm_info_t*); + int jack_attach_shm (jack_shm_info_t*); + int jack_attach_lib_shm (jack_shm_info_t*); + int jack_attach_shm_read (jack_shm_info_t*); + int jack_attach_lib_shm_read (jack_shm_info_t*); + int jack_resize_shm (jack_shm_info_t*, jack_shmsize_t size); + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_shm_h__ */ diff --git a/src/modules/module-jack/synchro.h b/src/modules/module-jack/synchro.h new file mode 100644 index 00000000..e58a368d --- /dev/null +++ b/src/modules/module-jack/synchro.h @@ -0,0 +1,48 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +struct jack_synchro { + char name[SYNC_MAX_NAME_SIZE]; + bool flush; + sem_t *semaphore; +}; + +#define JACK_SYNCHRO_INIT (struct jack_synchro) { { 0, }, false, NULL } + +static inline int +jack_synchro_init(struct jack_synchro *synchro, + const char *client_name, + const char *server_name, + int value, bool internal, + bool promiscuous) +{ + if (promiscuous) + snprintf(synchro->name, sizeof(synchro->name), + "jack_sem.%s_%s", server_name, client_name); + else + snprintf(synchro->name, sizeof(synchro->name), + "jack_sem.%d_%s_%s", getuid(), server_name, client_name); + + synchro->flush = false; + if ((synchro->semaphore = sem_open(synchro->name, O_CREAT | O_RDWR, 0777, value)) == (sem_t*)SEM_FAILED) { + pw_log_error("can't check in named semaphore name = %s err = %s", synchro->name, strerror(errno)); + return -1; + } + return 0; +} diff --git a/src/modules/module-mixer.c b/src/modules/module-mixer.c new file mode 100644 index 00000000..6812c2f5 --- /dev/null +++ b/src/modules/module-mixer.c @@ -0,0 +1,183 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <dlfcn.h> + +#include "config.h" + +#include "pipewire/core.h" +#include "pipewire/module.h" +#include "modules/spa/spa-node.h" + +#define AUDIOMIXER_LIB "audiomixer/libspa-audiomixer" + +struct impl { + struct pw_core *core; + struct pw_properties *properties; + + void *hnd; + const struct spa_handle_factory *factory; +}; + +static const struct spa_handle_factory *find_factory(struct impl *impl) +{ + spa_handle_factory_enum_func_t enum_func; + uint32_t index; + const struct spa_handle_factory *factory = NULL; + int res; + char *filename; + const char *dir; + + if ((dir = getenv("SPA_PLUGIN_DIR")) == NULL) + dir = PLUGINDIR; + + asprintf(&filename, "%s/%s.so", dir, AUDIOMIXER_LIB); + + if ((impl->hnd = dlopen(filename, RTLD_NOW)) == NULL) { + pw_log_error("can't load %s: %s", AUDIOMIXER_LIB, dlerror()); + goto open_failed; + } + if ((enum_func = dlsym(impl->hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + pw_log_error("can't find enum function"); + goto no_symbol; + } + + for (index = 0;; index++) { + if ((res = enum_func(&factory, index)) < 0) { + if (res != SPA_RESULT_ENUM_END) + pw_log_error("can't enumerate factories: %d", res); + goto enum_failed; + } + if (strcmp(factory->name, "audiomixer") == 0) + break; + } + free(filename); + return factory; + + enum_failed: + no_symbol: + dlclose(impl->hnd); + impl->hnd = NULL; + open_failed: + free(filename); + return NULL; +} + +static struct pw_node *make_node(struct impl *impl) +{ + struct spa_handle *handle; + int res; + void *iface; + struct spa_node *spa_node; + struct spa_clock *spa_clock; + struct pw_node *node; + + handle = calloc(1, impl->factory->size); + if ((res = spa_handle_factory_init(impl->factory, + handle, + NULL, impl->core->support, impl->core->n_support)) < 0) { + pw_log_error("can't make factory instance: %d", res); + goto init_failed; + } + if ((res = spa_handle_get_interface(handle, impl->core->type.spa_node, &iface)) < 0) { + pw_log_error("can't get interface %d", res); + goto interface_failed; + } + spa_node = iface; + + if ((res = spa_handle_get_interface(handle, impl->core->type.spa_clock, &iface)) < 0) { + iface = NULL; + } + spa_clock = iface; + + node = pw_spa_node_new(impl->core, NULL, "audiomixer", false, spa_node, spa_clock, NULL); + + return node; + + interface_failed: + spa_handle_clear(handle); + init_failed: + free(handle); + return NULL; +} + +static struct impl *module_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + struct pw_node *n; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("module %p: new", impl); + + impl->core = core; + impl->properties = properties; + + impl->factory = find_factory(impl); + + spa_list_for_each(n, &core->node_list, link) { + const char *str; + char *error; + struct pw_node *node; + struct pw_port *ip, *op; + + if (n->global == NULL) + continue; + + if (n->properties == NULL) + continue; + + if ((str = pw_properties_get(n->properties, "media.class")) == NULL) + continue; + + if (strcmp(str, "Audio/Sink") != 0) + continue; + + if ((ip = pw_node_get_free_port(n, PW_DIRECTION_INPUT)) == NULL) + continue; + + node = make_node(impl); + op = pw_node_get_free_port(node, PW_DIRECTION_OUTPUT); + if (op == NULL) + continue; + + n->idle_used_input_links++; + node->idle_used_output_links++; + + pw_link_new(core, op, ip, NULL, NULL, &error); + } + return impl; +} + +#if 0 +static void module_destroy(struct impl *impl) +{ + pw_log_debug("module %p: destroy", impl); + + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + module_new(module->core, NULL); + return true; +} diff --git a/src/modules/module-protocol-dbus.c b/src/modules/module-protocol-dbus.c new file mode 100644 index 00000000..2134aa04 --- /dev/null +++ b/src/modules/module-protocol-dbus.c @@ -0,0 +1,632 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/socket.h> + +#include <gio/gio.h> +#include <gio/gunixfdlist.h> + +#include "config.h" + +#include "pipewire/client/pipewire.h" +#include "pipewire/client/log.h" + +#include "pipewire/server/core.h" +#include "pipewire/server/node.h" +#include "pipewire/server/module.h" +#include "pipewire/server/client-node.h" +#include "pipewire/server/client.h" +#include "pipewire/server/resource.h" +#include "pipewire/server/link.h" +#include "pipewire/server/node-factory.h" +#include "pipewire/server/data-loop.h" +#include "pipewire/server/main-loop.h" + +#include "pipewire/dbus/org-pipewire.h" + +#define PIPEWIRE_DBUS_SERVICE "org.pipewire" +#define PIPEWIRE_DBUS_OBJECT_PREFIX "/org/pipewire" +#define PIPEWIRE_DBUS_OBJECT_SERVER PIPEWIRE_DBUS_OBJECT_PREFIX "/server" +#define PIPEWIRE_DBUS_OBJECT_CLIENT PIPEWIRE_DBUS_OBJECT_PREFIX "/client" +#define PIPEWIRE_DBUS_OBJECT_NODE PIPEWIRE_DBUS_OBJECT_PREFIX "/node" +#define PIPEWIRE_DBUS_OBJECT_LINK PIPEWIRE_DBUS_OBJECT_PREFIX "/link" + +struct impl { + struct pw_core *core; + struct spa_list link; + + struct pw_properties *properties; + + GDBusConnection *connection; + GDBusObjectManagerServer *server_manager; + + struct spa_list client_list; + struct spa_list object_list; + + struct pw_listener global_added; + struct pw_listener global_removed; +}; + +struct object { + struct impl *impl; + struct spa_list link; + struct pw_global *global; + void *iface; + PipeWireObjectSkeleton *skel; + const gchar *object_path; + pw_destroy_t destroy; +}; + +struct server { + struct object parent; + struct spa_list link; + guint id; +}; + +struct client { + struct object parent; + struct spa_list link; + gchar *sender; + guint id; +}; + +struct node { + struct object parent; + struct pw_listener state_changed; +}; + +static void object_export(struct object *this) +{ + g_dbus_object_manager_server_export(this->impl->server_manager, + G_DBUS_OBJECT_SKELETON(this->skel)); + this->object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(this->skel)); + pw_log_debug("protocol-dbus %p: export object %s", this->impl, this->object_path); +} + +static void object_unexport(struct object *this) +{ + if (this->object_path) + g_dbus_object_manager_server_unexport(this->impl->server_manager, + this->object_path); +} + +static void *object_new(size_t size, + struct impl *impl, + struct pw_global *global, + void *iface, + PipeWireObjectSkeleton * skel, bool export, pw_destroy_t destroy) +{ + struct object *this; + + this = calloc(1, size); + this->impl = impl; + this->global = global; + this->iface = iface; + this->skel = skel; + this->destroy = destroy; + + spa_list_insert(impl->object_list.prev, &this->link); + + if (export) + object_export(this); + + return this; +} + +static void object_destroy(struct object *this) +{ + spa_list_remove(&this->link); + + if (this->destroy) + this->destroy(this); + + object_unexport(this); + g_clear_object(&this->iface); + g_clear_object(&this->skel); + free(this); +} + +static struct object *find_object(struct impl *impl, void *object) +{ + struct object *obj; + spa_list_for_each(obj, &impl->object_list, link) { + if (obj->global->object == object) + return obj; + } + return NULL; +} + +#if 0 +struct _struct pw_properties { + GHashTable *hashtable; +}; + +static void add_to_variant(const gchar * key, const gchar * value, GVariantBuilder * b) +{ + g_variant_builder_add(b, "{sv}", key, g_variant_new_string(value)); +} +#endif + +static void pw_properties_init_builder(struct pw_properties *properties, GVariantBuilder * builder) +{ + g_variant_builder_init(builder, G_VARIANT_TYPE("a{sv}")); +// g_hash_table_foreach (properties->hashtable, (GHFunc) add_to_variant, builder); +} + +static GVariant *pw_properties_to_variant(struct pw_properties *properties) +{ + GVariantBuilder builder; + pw_properties_init_builder(properties, &builder); + return g_variant_builder_end(&builder); +} + +static struct pw_properties *pw_properties_from_variant(GVariant * variant) +{ + struct pw_properties *props; + GVariantIter iter; + //GVariant *value; + //gchar *key; + + props = pw_properties_new(NULL, NULL); + + g_variant_iter_init(&iter, variant); +// while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) + //g_hash_table_replace (props->hashtable, + // g_strdup (key), + // g_variant_dup_string (value, NULL)); + + return props; +} + +static void +client_name_appeared_handler(GDBusConnection * connection, + const gchar * name, const gchar * name_owner, gpointer user_data) +{ + struct client *this = user_data; + pw_log_debug("client %p: appeared %s %s", this, name, name_owner); + object_export(&this->parent); +} + +static void client_destroy(struct client *this) +{ + if (this->sender) { + spa_list_remove(&this->link); + free(this->sender); + } +} + +static void +client_name_vanished_handler(GDBusConnection * connection, const gchar * name, gpointer user_data) +{ + struct client *this = user_data; + pw_log_debug("client %p: vanished %s", this, name); + g_bus_unwatch_name(this->id); + /* destroying the client here will trigger the global_removed, which + * will then destroy our wrapper */ + pw_client_destroy(this->parent.global->object); +} + + +static struct client *client_new(struct impl *impl, const char *sender) +{ + struct client *this; + struct pw_client *client; + + client = pw_client_new(impl->core, NULL, NULL); + + if ((this = (struct client *) find_object(impl, client))) { + pipewire_client1_set_sender(this->parent.iface, sender); + + this->sender = strdup(sender); + this->id = g_bus_watch_name_on_connection(impl->connection, + this->sender, + G_BUS_NAME_WATCHER_FLAGS_NONE, + client_name_appeared_handler, + client_name_vanished_handler, this, NULL); + + spa_list_insert(impl->client_list.prev, &this->link); + } + return this; +} + +static struct pw_client *sender_get_client(struct impl *impl, const char *sender, bool create) +{ + struct client *client; + + spa_list_for_each(client, &impl->client_list, link) { + if (strcmp(client->sender, sender) == 0) + return client->parent.global->object; + } + if (!create) + return NULL; + + client = client_new(impl, sender); + + return client->parent.global->object; +} + +static bool +handle_create_node(PipeWireDaemon1 * interface, + GDBusMethodInvocation * invocation, + const char *arg_factory_name, + const char *arg_name, GVariant * arg_properties, gpointer user_data) +{ + struct impl *impl = user_data; + struct pw_node_factory *factory; + struct pw_node *node; + struct pw_client *client; + const char *sender, *object_path; + struct pw_properties *props; + struct object *object; + + sender = g_dbus_method_invocation_get_sender(invocation); + client = sender_get_client(impl, sender, TRUE); + + pw_log_debug("protocol-dbus %p: create node: %s", impl, sender); + + props = pw_properties_from_variant(arg_properties); + + factory = pw_core_find_node_factory(impl->core, arg_factory_name); + if (factory == NULL) + goto no_factory; + + node = pw_node_factory_create_node(factory, client, arg_name, props); + pw_properties_free(props); + + if (node == NULL) + goto no_node; + + object = find_object(impl, node); + if (object == NULL) + goto object_failed; + + pw_resource_new(client, + SPA_ID_INVALID, + impl->core->type.node, node, (pw_destroy_t) pw_node_destroy); + + object_path = object->object_path; + pw_log_debug("protocol-dbus %p: added node %p with path %s", impl, node, object_path); + g_dbus_method_invocation_return_value(invocation, g_variant_new("(o)", object_path)); + return TRUE; + + /* ERRORS */ + no_factory: + pw_log_debug("protocol-dbus %p: could not find factory named %s", impl, + arg_factory_name); + g_dbus_method_invocation_return_dbus_error(invocation, "org.pipewire.Error", + "can't find factory"); + return TRUE; + no_node: + pw_log_debug("protocol-dbus %p: could not create node named %s from factory %s", + impl, arg_name, arg_factory_name); + g_dbus_method_invocation_return_dbus_error(invocation, "org.pipewire.Error", + "can't create node"); + return TRUE; + object_failed: + pw_log_debug("protocol-dbus %p: could not create dbus object", impl); + g_dbus_method_invocation_return_dbus_error(invocation, + "org.pipewire.Error", + "can't create object"); + return TRUE; +} + +static void +on_node_state_changed(struct pw_listener *listener, + struct pw_node *node, enum pw_node_state old, enum pw_node_state state) +{ + struct node *object = SPA_CONTAINER_OF(listener, struct node, state_changed); + + pw_log_debug("protocol-dbus %p: node %p state change %s -> %s", object->parent.impl, node, + pw_node_state_as_string(old), pw_node_state_as_string(state)); + + pipewire_node1_set_state(object->parent.iface, node->state); +} + +static bool +handle_create_client_node(PipeWireDaemon1 * interface, + GDBusMethodInvocation * invocation, + const char *arg_name, GVariant * arg_properties, gpointer user_data) +{ + struct impl *impl = user_data; + struct pw_client_node *node; + struct pw_client *client; + int res; + const char *sender, *object_path, *target_node; + struct pw_properties *props; + GError *error = NULL; + GUnixFDList *fdlist; + int ctrl_fd, data_rfd, data_wfd; + int ctrl_idx, data_ridx, data_widx; + struct object *object; + int fd[2]; + + sender = g_dbus_method_invocation_get_sender(invocation); + client = sender_get_client(impl, sender, TRUE); + + pw_log_debug("protocol-dbus %p: create client-node: %s", impl, sender); + props = pw_properties_from_variant(arg_properties); + + target_node = pw_properties_get(props, "pipewire.target.node"); + if (target_node) { + if (strncmp(target_node, "/org/pipewire/node_", strlen("/org/pipewire/node_")) == 0) { + pw_properties_setf(props, "pipewire.target.node", "%s", + target_node + strlen("/org/pipewire/node_")); + } + } + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, fd) != 0) + goto no_socket_pair; + + ctrl_fd = fd[1]; + + node = pw_client_node_new(client, SPA_ID_INVALID, arg_name, props); + + object = find_object(impl, node->node); + if (object == NULL) + goto object_failed; + + if ((res = pw_client_node_get_fds(node, &data_rfd, &data_wfd)) < 0) + goto no_socket; + + object_path = object->object_path; + pw_log_debug("protocol-dbus %p: add client-node %p, %s", impl, node, object_path); + + fdlist = g_unix_fd_list_new(); + ctrl_idx = g_unix_fd_list_append(fdlist, ctrl_fd, &error); + data_ridx = g_unix_fd_list_append(fdlist, data_rfd, &error); + data_widx = g_unix_fd_list_append(fdlist, data_wfd, &error); + + g_dbus_method_invocation_return_value_with_unix_fd_list(invocation, + g_variant_new("(ohhh)", object_path, + ctrl_idx, data_ridx, + data_widx), fdlist); + g_object_unref(fdlist); + + return TRUE; + + object_failed: + pw_log_debug("protocol-dbus %p: could not create object", impl); + goto exit_error; + no_socket_pair: + pw_log_debug("protocol-dbus %p: could not create socketpair: %s", impl, + strerror(errno)); + goto exit_error; + no_socket: + pw_log_debug("protocol-dbus %p: could not create socket: %s", impl, + strerror(errno)); + pw_client_node_destroy(node); + goto exit_error; + exit_error: + g_dbus_method_invocation_return_gerror(invocation, error); + g_clear_error(&error); + return TRUE; +} + +static void bus_acquired_handler(GDBusConnection * connection, const char *name, gpointer user_data) +{ + struct impl *impl = user_data; + GDBusObjectManagerServer *manager = impl->server_manager; + + impl->connection = connection; + g_dbus_object_manager_server_set_connection(manager, connection); +} + +static void +name_acquired_handler(GDBusConnection * connection, const char *name, gpointer user_data) +{ +} + +static void name_lost_handler(GDBusConnection * connection, const char *name, gpointer user_data) +{ + struct impl *impl = user_data; + GDBusObjectManagerServer *manager = impl->server_manager; + + g_dbus_object_manager_server_set_connection(manager, connection); + impl->connection = connection; +} + +static bool +handle_node_remove(PipeWireNode1 * interface, + GDBusMethodInvocation * invocation, gpointer user_data) +{ + struct pw_node *this = user_data; + + pw_log_debug("node %p: remove", this); + + g_dbus_method_invocation_return_value(invocation, g_variant_new("()")); + return true; +} + +static void +on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added); + PipeWireObjectSkeleton *skel; + + if (global->type == impl->core->type.client) { + PipeWireClient1 *iface; + struct pw_client *client = global->object; + struct pw_properties *props = client->properties; + char *path; + + asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_CLIENT, global->id); + skel = pipewire_object_skeleton_new(path); + free(path); + + iface = pipewire_client1_skeleton_new(); + pipewire_client1_set_properties(iface, + props ? pw_properties_to_variant(props) : NULL); + pipewire_object_skeleton_set_client1(skel, iface); + + object_new(sizeof(struct client), + impl, global, iface, skel, false, (pw_destroy_t) client_destroy); + + } else if (global->type == impl->core->type.node) { + PipeWireNode1 *iface; + struct pw_node *node = global->object; + struct pw_properties *props = node->properties; + char *path; + struct node *obj; + + asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_NODE, global->id); + skel = pipewire_object_skeleton_new(path); + free(path); + + iface = pipewire_node1_skeleton_new(); + g_signal_connect(iface, "handle-remove", (GCallback) handle_node_remove, node); + pipewire_node1_set_state(iface, node->state); + pipewire_node1_set_owner(iface, "/"); + pipewire_node1_set_name(iface, node->name); + pipewire_node1_set_properties(iface, + props ? pw_properties_to_variant(props) : NULL); + pipewire_object_skeleton_set_node1(skel, iface); + + obj = object_new(sizeof(struct node), impl, global, iface, skel, true, NULL); + pw_signal_add(&node->state_changed, &obj->state_changed, on_node_state_changed); + } else if (global->object == impl) { + struct impl *proto = global->object; + struct server *server; + PipeWireDaemon1 *iface; + char *path; + + iface = pipewire_daemon1_skeleton_new(); + g_signal_connect(iface, "handle-create-node", (GCallback) handle_create_node, + proto); + g_signal_connect(iface, "handle-create-client-node", + (GCallback) handle_create_client_node, proto); + + asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_SERVER, global->id); + skel = pipewire_object_skeleton_new(path); + free(path); + + pipewire_daemon1_set_user_name(iface, g_get_user_name()); + pipewire_daemon1_set_host_name(iface, g_get_host_name()); + pipewire_daemon1_set_version(iface, PACKAGE_VERSION); + pipewire_daemon1_set_name(iface, PACKAGE_NAME); + pipewire_daemon1_set_cookie(iface, g_random_int()); + pipewire_daemon1_set_properties(iface, proto->properties ? + pw_properties_to_variant(proto->properties) : NULL); + pipewire_object_skeleton_set_daemon1(skel, iface); + + server = object_new(sizeof(struct server), impl, global, iface, skel, true, NULL); + server->id = g_bus_own_name(G_BUS_TYPE_SESSION, + PIPEWIRE_DBUS_SERVICE, + G_BUS_NAME_OWNER_FLAGS_REPLACE, + bus_acquired_handler, + name_acquired_handler, name_lost_handler, proto, NULL); + } else if (global->type == impl->core->type.link) { + PipeWireLink1 *iface; + struct pw_link *link = global->object; + struct object *obj; + char *path; + + asprintf(&path, "%s_%u", PIPEWIRE_DBUS_OBJECT_LINK, global->id); + skel = pipewire_object_skeleton_new(path); + free(path); + + iface = pipewire_link1_skeleton_new(); + + obj = link->output ? find_object(impl, link->output->node) : NULL; + if (obj) { + pipewire_link1_set_output_node(iface, obj->object_path); + pipewire_link1_set_output_port(iface, link->output->port_id); + } else { + pipewire_link1_set_output_node(iface, "/"); + pipewire_link1_set_output_port(iface, SPA_ID_INVALID); + } + obj = link->input ? find_object(impl, link->input->node) : NULL; + if (obj) { + pipewire_link1_set_input_node(iface, obj->object_path); + pipewire_link1_set_input_port(iface, link->input->port_id); + } else { + pipewire_link1_set_output_node(iface, "/"); + pipewire_link1_set_output_port(iface, SPA_ID_INVALID); + } + pipewire_object_skeleton_set_link1(skel, iface); + + object_new(sizeof(struct object), impl, global, iface, skel, true, NULL); + } +} + +static void +on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed); + struct object *object; + + if ((object = find_object(impl, global->object))) + object_destroy(object); +} + +static struct impl *pw_protocol_dbus_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("protocol-dbus %p: new", impl); + + impl->core = core; + impl->properties = properties; + + spa_list_init(&impl->client_list); + spa_list_init(&impl->object_list); + + pw_signal_add(&core->global_added, &impl->global_added, on_global_added); + pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed); + + impl->server_manager = g_dbus_object_manager_server_new(PIPEWIRE_DBUS_OBJECT_PREFIX); + + return impl; +} + +#if 0 +static void pw_protocol_dbus_destroy(struct impl *impl) +{ + struct object *object, *tmp; + + pw_log_debug("protocol-dbus %p: destroy", impl); + + pw_global_destroy(impl->global); + + spa_list_for_each_safe(object, tmp, &impl->object_list, link) + object_destroy(object); + +#if 0 + if (impl->id != 0) + g_bus_unown_name(impl->id); +#endif + + pw_signal_remove(&impl->global_added); + pw_signal_remove(&impl->global_removed); + pw_signal_remove(&impl->node_state_changed); + + g_clear_object(&impl->server_manager); + + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + pw_protocol_dbus_new(module->core, NULL); + return TRUE; +} diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c new file mode 100644 index 00000000..6e9b5d20 --- /dev/null +++ b/src/modules/module-protocol-native.c @@ -0,0 +1,684 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/file.h> + +#include "config.h" + +#include "pipewire/pipewire.h" +#include "pipewire/log.h" +#include "pipewire/interfaces.h" +#include "pipewire/core.h" +#include "pipewire/node.h" +#include "pipewire/module.h" +#include "pipewire/client.h" +#include "pipewire/resource.h" +#include "pipewire/link.h" +#include "pipewire/node-factory.h" +#include "pipewire/data-loop.h" +#include "pipewire/main-loop.h" + +#include "modules/module-protocol-native/connection.h" + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +#define LOCK_SUFFIX ".lock" +#define LOCK_SUFFIXLEN 5 + +struct pw_protocol *pw_protocol_native_init(void); + +typedef bool(*demarshal_func_t) (void *object, void *data, size_t size); + +struct connection { + struct pw_protocol_connection this; + + int fd; + + struct spa_source *source; + struct pw_connection *connection; + + bool disconnecting; + struct pw_listener need_flush; + struct spa_source *flush_event; +}; + +struct listener { + struct pw_protocol_listener this; + + int fd; + int fd_lock; + struct sockaddr_un addr; + char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN]; + + struct pw_loop *loop; + struct spa_source *source; +}; + +struct impl { + struct pw_core *core; + struct spa_list link; + + struct pw_protocol *protocol; + struct pw_properties *properties; + + struct spa_list client_list; + + struct spa_loop_control_hooks hooks; +}; + +struct native_client { + struct impl *impl; + struct spa_list link; + struct pw_client *client; + int fd; + struct spa_source *source; + struct pw_connection *connection; + struct pw_listener busy_changed; +}; + +static void client_destroy(void *data) +{ + struct pw_client *client = data; + struct native_client *this = client->user_data; + + pw_loop_destroy_source(this->impl->core->main_loop, this->source); + spa_list_remove(&this->link); + + pw_connection_destroy(this->connection); + close(this->fd); +} + +static void +process_messages(struct native_client *client) +{ + struct pw_connection *conn = client->connection; + uint8_t opcode; + uint32_t id; + uint32_t size; + struct pw_client *c = client->client; + void *message; + + while (pw_connection_get_next(conn, &opcode, &id, &message, &size)) { + struct pw_resource *resource; + const demarshal_func_t *demarshal; + + pw_log_trace("protocol-native %p: got message %d from %u", client->impl, + opcode, id); + + resource = pw_map_lookup(&c->objects, id); + if (resource == NULL) { + pw_log_error("protocol-native %p: unknown resource %u", + client->impl, id); + continue; + } + if (opcode >= resource->iface->n_methods) { + pw_log_error("protocol-native %p: invalid method %u %u", client->impl, + id, opcode); + pw_client_destroy(c); + break; + } + demarshal = resource->iface->methods; + if (!demarshal[opcode] || !demarshal[opcode] (resource, message, size)) { + pw_log_error("protocol-native %p: invalid message received %u %u", + client->impl, id, opcode); + pw_client_destroy(c); + break; + } + if (c->busy) { + break; + } + } +} + +static void +on_busy_changed(struct pw_listener *listener, + struct pw_client *client) +{ + struct native_client *c = SPA_CONTAINER_OF(listener, struct native_client, busy_changed); + enum spa_io mask = SPA_IO_ERR | SPA_IO_HUP; + + if (!client->busy) + mask |= SPA_IO_IN; + + pw_loop_update_io(c->impl->core->main_loop, c->source, mask); + + if (!client->busy) + process_messages(c); + +} + +static void on_before_hook(const struct spa_loop_control_hooks *hooks) +{ + struct impl *this = SPA_CONTAINER_OF(hooks, struct impl, hooks); + struct native_client *client, *tmp; + + spa_list_for_each_safe(client, tmp, &this->client_list, link) + pw_connection_flush(client->connection); +} + +static void +connection_data(struct spa_loop_utils *utils, + struct spa_source *source, int fd, enum spa_io mask, void *data) +{ + struct native_client *client = data; + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_error("protocol-native %p: got connection error", client->impl); + pw_client_destroy(client->client); + return; + } + + if (mask & SPA_IO_IN) + process_messages(client); +} + +static struct native_client *client_new(struct impl *impl, int fd) +{ + struct native_client *this; + struct pw_client *client; + socklen_t len; + struct ucred ucred, *ucredp; + + len = sizeof(ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) { + pw_log_error("no peercred: %m"); + ucredp = NULL; + } else { + ucredp = &ucred; + } + + client = pw_client_new(impl->core, ucredp, NULL, sizeof(struct native_client)); + if (client == NULL) + goto no_client; + + client->destroy = client_destroy; + + this = client->user_data; + this->impl = impl; + this->fd = fd; + this->source = pw_loop_add_io(impl->core->main_loop, + this->fd, + SPA_IO_ERR | SPA_IO_HUP, false, connection_data, this); + if (this->source == NULL) + goto no_source; + + this->connection = pw_connection_new(fd); + if (this->connection == NULL) + goto no_connection; + + client->protocol = impl->protocol; + client->protocol_private = this->connection; + + this->client = client; + + spa_list_insert(impl->client_list.prev, &this->link); + + pw_signal_add(&client->busy_changed, &this->busy_changed, on_busy_changed); + + pw_global_bind(impl->core->global, client, 0, 0); + + return this; + + no_connection: + pw_loop_destroy_source(impl->core->main_loop, this->source); + no_source: + free(this); + no_client: + return NULL; +} + +static void destroy_listener(struct listener *l) +{ + if (l->source) + pw_loop_destroy_source(l->loop, l->source); + if (l->addr.sun_path[0]) + unlink(l->addr.sun_path); + if (l->fd >= 0) + close(l->fd); + if (l->lock_addr[0]) + unlink(l->lock_addr); + if (l->fd_lock >= 0) + close(l->fd_lock); + free(l); +} + +static bool init_socket_name(struct listener *l, const char *name) +{ + int name_size; + const char *runtime_dir; + + if ((runtime_dir = getenv("XDG_RUNTIME_DIR")) == NULL) { + pw_log_error("XDG_RUNTIME_DIR not set in the environment"); + return false; + } + + l->addr.sun_family = AF_LOCAL; + name_size = snprintf(l->addr.sun_path, sizeof(l->addr.sun_path), + "%s/%s", runtime_dir, name) + 1; + + if (name_size > (int) sizeof(l->addr.sun_path)) { + pw_log_error("socket path \"%s/%s\" plus null terminator exceeds 108 bytes", + runtime_dir, name); + *l->addr.sun_path = 0; + return false; + } + return true; +} + +static bool lock_socket(struct listener *l) +{ + struct stat socket_stat; + + snprintf(l->lock_addr, sizeof(l->lock_addr), "%s%s", l->addr.sun_path, LOCK_SUFFIX); + + l->fd_lock = open(l->lock_addr, O_CREAT | O_CLOEXEC, + (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)); + + if (l->fd_lock < 0) { + pw_log_error("unable to open lockfile %s check permissions", l->lock_addr); + goto err; + } + + if (flock(l->fd_lock, LOCK_EX | LOCK_NB) < 0) { + pw_log_error("unable to lock lockfile %s, maybe another daemon is running", + l->lock_addr); + goto err_fd; + } + + if (stat(l->addr.sun_path, &socket_stat) < 0) { + if (errno != ENOENT) { + pw_log_error("did not manage to stat file %s\n", l->addr.sun_path); + goto err_fd; + } + } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) { + unlink(l->addr.sun_path); + } + return true; + + err_fd: + close(l->fd_lock); + l->fd_lock = -1; + err: + *l->lock_addr = 0; + *l->addr.sun_path = 0; + return false; +} + +static void +socket_data(struct spa_loop_utils *utils, + struct spa_source *source, int fd, enum spa_io mask, void *data) +{ + struct impl *impl = data; + struct native_client *client; + struct sockaddr_un name; + socklen_t length; + int client_fd; + + length = sizeof(name); + client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC); + if (client_fd < 0) { + pw_log_error("failed to accept: %m"); + return; + } + + client = client_new(impl, client_fd); + if (client == NULL) { + pw_log_error("failed to create client"); + close(client_fd); + return; + } + + pw_loop_update_io(impl->core->main_loop, + client->source, SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP); +} + +static bool add_socket(struct impl *impl, struct listener *l) +{ + socklen_t size; + + if ((l->fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) + return false; + + size = offsetof(struct sockaddr_un, sun_path) + strlen(l->addr.sun_path); + if (bind(l->fd, (struct sockaddr *) &l->addr, size) < 0) { + pw_log_error("bind() failed with error: %m"); + return false; + } + + if (listen(l->fd, 128) < 0) { + pw_log_error("listen() failed with error: %m"); + return false; + } + + l->loop = impl->core->main_loop; + l->source = pw_loop_add_io(l->loop, l->fd, SPA_IO_IN, false, socket_data, impl); + if (l->source == NULL) + return false; + + return true; +} + +static const char * +get_name(struct pw_properties *properties) +{ + const char *name = NULL; + + if (properties) + name = pw_properties_get(properties, "pipewire.core.name"); + if (name == NULL) + name = getenv("PIPEWIRE_CORE"); + if (name == NULL) + name = "pipewire-0"; + + return name; +} + +static int impl_connect(struct pw_protocol_connection *conn) +{ + struct sockaddr_un addr; + socklen_t size; + const char *runtime_dir, *name = NULL; + int name_size, fd; + + if ((runtime_dir = getenv("XDG_RUNTIME_DIR")) == NULL) { + pw_log_error("connect failed: XDG_RUNTIME_DIR not set in the environment"); + return -1; + } + + if (name == NULL) + name = getenv("PIPEWIRE_CORE"); + if (name == NULL) + name = "pipewire-0"; + + if ((fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_LOCAL; + name_size = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir, name) + 1; + + if (name_size > (int) sizeof addr.sun_path) { + pw_log_error("socket path \"%s/%s\" plus null terminator exceeds 108 bytes", + runtime_dir, name); + goto error_close; + }; + + size = offsetof(struct sockaddr_un, sun_path) + name_size; + + if (connect(fd, (struct sockaddr *) &addr, size) < 0) { + goto error_close; + } + + return conn->connect_fd(conn, fd); + + error_close: + close(fd); + return -1; +} + + +static void +on_remote_data(struct spa_loop_utils *utils, + struct spa_source *source, int fd, enum spa_io mask, void *data) +{ + struct connection *impl = data; + struct pw_remote *this = impl->this.remote; + struct pw_connection *conn = impl->connection; + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + return; + } + + if (mask & SPA_IO_IN) { + uint8_t opcode; + uint32_t id; + uint32_t size; + void *message; + + while (!impl->disconnecting + && pw_connection_get_next(conn, &opcode, &id, &message, &size)) { + struct pw_proxy *proxy; + const demarshal_func_t *demarshal; + + pw_log_trace("protocol-native %p: got message %d from %u", this, opcode, id); + + proxy = pw_map_lookup(&this->objects, id); + if (proxy == NULL) { + pw_log_error("protocol-native %p: could not find proxy %u", this, id); + continue; + } + if (opcode >= proxy->iface->n_events) { + pw_log_error("protocol-native %p: invalid method %u for %u", this, opcode, + id); + continue; + } + + demarshal = proxy->iface->events; + if (demarshal[opcode]) { + if (!demarshal[opcode] (proxy, message, size)) + pw_log_error + ("protocol-native %p: invalid message received %u for %u", this, + opcode, id); + } else + pw_log_error("protocol-native %p: function %d not implemented on %u", this, + opcode, id); + + } + } +} + + +static void do_flush_event(struct spa_loop_utils *utils, struct spa_source *source, void *data) +{ + struct connection *impl = data; + if (impl->connection) + if (!pw_connection_flush(impl->connection)) + impl->this.disconnect(&impl->this); +} + +static void on_need_flush(struct pw_listener *listener, struct pw_connection *connection) +{ + struct connection *impl = SPA_CONTAINER_OF(listener, struct connection, need_flush); + struct pw_remote *remote = impl->this.remote; + pw_loop_signal_event(remote->core->main_loop, impl->flush_event); +} + +static int impl_connect_fd(struct pw_protocol_connection *conn, int fd) +{ + struct connection *impl = SPA_CONTAINER_OF(conn, struct connection, this); + struct pw_remote *remote = impl->this.remote; + + impl->connection = pw_connection_new(fd); + if (impl->connection == NULL) + goto error_close; + + conn->remote->protocol_private = impl->connection; + + pw_signal_add(&impl->connection->need_flush, &impl->need_flush, on_need_flush); + + impl->fd = fd; + impl->source = pw_loop_add_io(remote->core->main_loop, + fd, + SPA_IO_IN | SPA_IO_HUP | SPA_IO_ERR, + false, on_remote_data, impl); + + return 0; + + error_close: + close(fd); + return -1; +} + +static int impl_disconnect(struct pw_protocol_connection *conn) +{ + struct connection *impl = SPA_CONTAINER_OF(conn, struct connection, this); + struct pw_remote *remote = impl->this.remote; + + impl->disconnecting = true; + + if (impl->source) + pw_loop_destroy_source(remote->core->main_loop, impl->source); + impl->source = NULL; + + if (impl->connection) + pw_connection_destroy(impl->connection); + impl->connection = NULL; + + if (impl->fd != -1) + close(impl->fd); + impl->fd = -1; + + return 0; +} + +static int impl_destroy(struct pw_protocol_connection *conn) +{ + struct connection *impl = SPA_CONTAINER_OF(conn, struct connection, this); + struct pw_remote *remote = conn->remote; + + pw_loop_destroy_source(remote->core->main_loop, impl->flush_event); + + spa_list_remove(&conn->link); + free(impl); + + return 0; +} + +static struct pw_protocol_connection * +impl_new_connection(struct pw_protocol *protocol, + struct pw_remote *remote, + struct pw_properties *properties) +{ + struct impl *impl = protocol->protocol_private; + struct connection *c; + struct pw_protocol_connection *this; + + if ((c = calloc(1, sizeof(struct connection))) == NULL) + return NULL; + + this = &c->this; + this->remote = remote; + + this->connect = impl_connect; + this->connect_fd = impl_connect_fd; + this->disconnect = impl_disconnect; + this->destroy = impl_destroy; + + c->flush_event = pw_loop_add_event(remote->core->main_loop, do_flush_event, c); + + spa_list_insert(impl->protocol->connection_list.prev, &c->this.link); + + return this; +} + +static struct pw_protocol_listener * +impl_add_listener(struct pw_protocol *protocol, + struct pw_core *core, + struct pw_properties *properties) +{ + struct impl *impl = protocol->protocol_private; + struct listener *l; + const char *name; + + if ((l = calloc(1, sizeof(struct listener))) == NULL) + return NULL; + + l->fd = -1; + l->fd_lock = -1; + + name = get_name(properties); + + if (!init_socket_name(l, name)) + goto error; + + if (!lock_socket(l)) + goto error; + + if (!add_socket(impl, l)) + goto error; + + spa_list_insert(impl->protocol->listener_list.prev, &l->this.link); + + impl->hooks.before = on_before_hook; + pw_loop_add_hooks(impl->core->main_loop, &impl->hooks); + + pw_log_info("protocol-native %p: Added listener", protocol); + + return &l->this; + + error: + destroy_listener(l); + return NULL; +} + + +static struct impl *pw_protocol_native_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + + impl->core = core; + impl->properties = properties; + impl->protocol = pw_protocol_native_init(); + impl->protocol->new_connection = impl_new_connection; + impl->protocol->add_listener = impl_add_listener; + impl->protocol->protocol_private = impl; + pw_log_debug("protocol-native %p: new %p", impl, impl->protocol); + + spa_list_init(&impl->client_list); + + impl_add_listener(impl->protocol, core, properties); + + return impl; +} + +#if 0 +static void pw_protocol_native_destroy(struct impl *impl) +{ + struct impl *object, *tmp; + + pw_log_debug("protocol-native %p: destroy", impl); + + pw_signal_remove(&impl->before_iterate); + + pw_global_destroy(impl->global); + + spa_list_for_each_safe(object, tmp, &impl->object_list, link) + object_destroy(object); + + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + pw_protocol_native_new(module->core, NULL); + return true; +} diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c new file mode 100644 index 00000000..073cc836 --- /dev/null +++ b/src/modules/module-protocol-native/connection.c @@ -0,0 +1,529 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdint.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/socket.h> + +#include <spa/lib/debug.h> + +#include <pipewire/pipewire.h> + +#include "connection.h" + +/** \cond */ + +#define MAX_BUFFER_SIZE (1024 * 32) +#define MAX_FDS 28 + +static bool debug_messages = 0; + +struct buffer { + uint8_t *buffer_data; + size_t buffer_size; + size_t buffer_maxsize; + int fds[MAX_FDS]; + uint32_t n_fds; + + off_t offset; + void *data; + size_t size; + + bool update; +}; + +struct impl { + struct pw_connection this; + + struct buffer in, out; + + uint32_t dest_id; + uint8_t opcode; + struct spa_pod_builder builder; +}; + +/** \endcond */ + +/** Get an fd from a connection + * + * \param conn the connection + * \param index the index of the fd to get + * \return the fd at \a index or -1 when no such fd exists + * + * \memberof pw_connection + */ +int pw_connection_get_fd(struct pw_connection *conn, uint32_t index) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + + if (index < 0 || index >= impl->in.n_fds) + return -1; + + return impl->in.fds[index]; +} + +/** Add an fd to a connection + * + * \param conn the connection + * \param fd the fd to add + * \return the index of the fd or -1 when an error occured + * + * \memberof pw_connection + */ +uint32_t pw_connection_add_fd(struct pw_connection *conn, int fd) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + uint32_t index, i; + + for (i = 0; i < impl->out.n_fds; i++) { + if (impl->out.fds[i] == fd) + return i; + } + + index = impl->out.n_fds; + if (index >= MAX_FDS) { + pw_log_error("connection %p: too many fds", conn); + return -1; + } + + impl->out.fds[index] = fd; + impl->out.n_fds++; + + return index; +} + +static void *connection_ensure_size(struct pw_connection *conn, struct buffer *buf, size_t size) +{ + if (buf->buffer_size + size > buf->buffer_maxsize) { + buf->buffer_maxsize = SPA_ROUND_UP_N(buf->buffer_size + size, MAX_BUFFER_SIZE); + buf->buffer_data = realloc(buf->buffer_data, buf->buffer_maxsize); + + pw_log_warn("connection %p: resize buffer to %zd %zd %zd", + conn, buf->buffer_size, size, buf->buffer_maxsize); + } + return (uint8_t *) buf->buffer_data + buf->buffer_size; +} + +static bool refill_buffer(struct pw_connection *conn, struct buffer *buf) +{ + ssize_t len; + struct cmsghdr *cmsg; + struct msghdr msg = { 0 }; + struct iovec iov[1]; + char cmsgbuf[CMSG_SPACE(MAX_FDS * sizeof(int))]; + + iov[0].iov_base = buf->buffer_data + buf->buffer_size; + iov[0].iov_len = buf->buffer_maxsize - buf->buffer_size; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + msg.msg_flags = MSG_CMSG_CLOEXEC; + + while (true) { + len = recvmsg(conn->fd, &msg, msg.msg_flags); + if (len < 0) { + if (errno == EINTR) + continue; + else + goto recv_error; + } + break; + } + + buf->buffer_size += len; + + /* handle control messages */ + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) + continue; + + buf->n_fds = + (cmsg->cmsg_len - ((char *) CMSG_DATA(cmsg) - (char *) cmsg)) / sizeof(int); + memcpy(buf->fds, CMSG_DATA(cmsg), buf->n_fds * sizeof(int)); + } + pw_log_trace("connection %p: %d read %zd bytes and %d fds", conn, conn->fd, len, + buf->n_fds); + + return true; + + /* ERRORS */ + recv_error: + pw_log_error("could not recvmsg on fd %d: %s", conn->fd, strerror(errno)); + return false; +} + +static void clear_buffer(struct buffer *buf) +{ + buf->n_fds = 0; + buf->offset = 0; + buf->size = 0; + buf->buffer_size = 0; +} + +/** Make a new connection object for the given socket + * + * \param fd the socket + * \returns a newly allocated connection object + * + * \memberof pw_connection + */ +struct pw_connection *pw_connection_new(int fd) +{ + struct impl *impl; + struct pw_connection *this; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return NULL; + + debug_messages = pw_debug_is_category_enabled("connection"); + + this = &impl->this; + + pw_log_debug("connection %p: new", this); + + this->fd = fd; + pw_signal_init(&this->need_flush); + pw_signal_init(&this->destroy_signal); + + impl->out.buffer_data = malloc(MAX_BUFFER_SIZE); + impl->out.buffer_maxsize = MAX_BUFFER_SIZE; + impl->in.buffer_data = malloc(MAX_BUFFER_SIZE); + impl->in.buffer_maxsize = MAX_BUFFER_SIZE; + impl->in.update = true; + + if (impl->out.buffer_data == NULL || impl->in.buffer_data == NULL) + goto no_mem; + + return this; + + no_mem: + free(impl->out.buffer_data); + free(impl->in.buffer_data); + free(impl); + return NULL; +} + +/** Destroy a connection + * + * \param conn the connection to destroy + * + * \memberof pw_connection + */ +void pw_connection_destroy(struct pw_connection *conn) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + + pw_log_debug("connection %p: destroy", conn); + + pw_signal_emit(&conn->destroy_signal, conn); + + free(impl->out.buffer_data); + free(impl->in.buffer_data); + free(impl); +} + +/** Move to the next packet in the connection + * + * \param conn the connection + * \param opcode addres of result opcode + * \param dest_id addres of result destination id + * \param dt pointer to packet data + * \param sz size of packet data + * \return true on success + * + * Get the next packet in \a conn and store the opcode and destination + * id as well as the packet data and size. + * + * \memberof pw_connection + */ +bool +pw_connection_get_next(struct pw_connection *conn, + uint8_t *opcode, + uint32_t *dest_id, + void **dt, + uint32_t *sz) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + size_t len, size; + uint8_t *data; + struct buffer *buf; + uint32_t *p; + + buf = &impl->in; + + /* move to next packet */ + buf->offset += buf->size; + + again: + if (buf->update) { + if (!refill_buffer(conn, buf)) + return false; + buf->update = false; + } + + /* now read packet */ + data = buf->buffer_data; + size = buf->buffer_size; + + if (buf->offset >= size) { + clear_buffer(buf); + buf->update = true; + return false; + } + + data += buf->offset; + size -= buf->offset; + + if (size < 8) { + connection_ensure_size(conn, buf, 8); + buf->update = true; + goto again; + } + p = (uint32_t *) data; + data += 8; + size -= 8; + + *dest_id = p[0]; + *opcode = p[1] >> 24; + len = p[1] & 0xffffff; + + if (len > size) { + connection_ensure_size(conn, buf, len); + buf->update = true; + goto again; + } + buf->size = len; + buf->data = data; + buf->offset += 8; + + *dt = buf->data; + *sz = buf->size; + + + if (debug_messages) { + printf("<<<<<<<<< in:\n"); + spa_debug_pod((struct spa_pod *)data); + } + + return true; +} + +static inline void *begin_write(struct pw_connection *conn, uint32_t size) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + uint32_t *p; + struct buffer *buf = &impl->out; + /* 4 for dest_id, 1 for opcode, 3 for size and size for payload */ + p = connection_ensure_size(conn, buf, 8 + size); + return p + 2; +} + +static uint32_t write_pod(struct spa_pod_builder *b, uint32_t ref, const void *data, uint32_t size) +{ + struct impl *impl = SPA_CONTAINER_OF(b, struct impl, builder); + + if (ref == -1) + ref = b->offset; + + if (b->size <= b->offset) { + b->size = SPA_ROUND_UP_N(b->offset + size, 4096); + b->data = begin_write(&impl->this, b->size); + } + memcpy(b->data + ref, data, size); + + return ref; +} + +struct spa_pod_builder * +pw_connection_begin_write_resource(struct pw_connection *conn, + struct pw_resource *resource, + uint8_t opcode) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + uint32_t diff, base, i, b; + struct pw_client *client = resource->client; + struct pw_core *core = client->core; + const char **types; + + base = client->n_types; + diff = spa_type_map_get_size(core->type.map) - base; + if (diff > 0) { + types = alloca(diff * sizeof(char *)); + for (i = 0, b = base; i < diff; i++, b++) + types[i] = spa_type_map_get_type(core->type.map, b); + + client->n_types += diff; + pw_core_notify_update_types(client->core_resource, base, diff, types); + } + + impl->dest_id = resource->id; + impl->opcode = opcode; + impl->builder = (struct spa_pod_builder) { NULL, 0, 0, NULL, write_pod }; + + return &impl->builder; +} + +struct spa_pod_builder * +pw_connection_begin_write_proxy(struct pw_connection *conn, + struct pw_proxy *proxy, + uint8_t opcode) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + uint32_t diff, base, i, b; + const char **types; + struct pw_remote *remote = proxy->remote; + struct pw_core *core = remote->core; + + base = remote->n_types; + diff = spa_type_map_get_size(core->type.map) - base; + if (diff > 0) { + types = alloca(diff * sizeof(char *)); + for (i = 0, b = base; i < diff; i++, b++) + types[i] = spa_type_map_get_type(core->type.map, b); + + remote->n_types += diff; + pw_core_do_update_types(remote->core_proxy, base, diff, types); + } + + impl->dest_id = proxy->id; + impl->opcode = opcode; + impl->builder = (struct spa_pod_builder) { NULL, 0, 0, NULL, write_pod }; + + return &impl->builder; +} + +void +pw_connection_end_write(struct pw_connection *conn, + struct spa_pod_builder *builder) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + uint32_t *p, size = builder->offset; + struct buffer *buf = &impl->out; + + p = connection_ensure_size(conn, buf, 8 + size); + *p++ = impl->dest_id; + *p++ = (impl->opcode << 24) | (size & 0xffffff); + + buf->buffer_size += 8 + size; + + if (debug_messages) { + printf(">>>>>>>>> out:\n"); + spa_debug_pod((struct spa_pod *)p); + } + + pw_signal_emit(&conn->need_flush, conn); +} + +/** Flush the connection object + * + * \param conn the connection object + * \return true on success + * + * Write the queued messages on the connection to the socket + * + * \memberof pw_connection + */ +bool pw_connection_flush(struct pw_connection *conn) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + ssize_t len; + struct msghdr msg = { 0 }; + struct iovec iov[1]; + struct cmsghdr *cmsg; + char cmsgbuf[CMSG_SPACE(MAX_FDS * sizeof(int))]; + int *cm, i, fds_len; + struct buffer *buf; + + buf = &impl->out; + + if (buf->buffer_size == 0) + return true; + + fds_len = buf->n_fds * sizeof(int); + + iov[0].iov_base = buf->buffer_data; + iov[0].iov_len = buf->buffer_size; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + if (buf->n_fds > 0) { + msg.msg_control = cmsgbuf; + msg.msg_controllen = CMSG_SPACE(fds_len); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(fds_len); + cm = (int *) CMSG_DATA(cmsg); + for (i = 0; i < buf->n_fds; i++) + cm[i] = buf->fds[i] > 0 ? buf->fds[i] : -buf->fds[i]; + msg.msg_controllen = cmsg->cmsg_len; + } else { + msg.msg_control = NULL; + msg.msg_controllen = 0; + } + + while (true) { + len = sendmsg(conn->fd, &msg, MSG_NOSIGNAL); + if (len < 0) { + if (errno == EINTR) + continue; + else + goto send_error; + } + break; + } + pw_log_trace("connection %p: %d written %zd bytes and %u fds", conn, conn->fd, len, + buf->n_fds); + + buf->buffer_size -= len; + buf->n_fds = 0; + + return true; + + /* ERRORS */ + send_error: + pw_log_error("could not sendmsg: %s", strerror(errno)); + return false; +} + +/** Clear the connection object + * + * \param conn the connection object + * \return true on success + * + * Remove all queued messages from \a conn + * + * \memberof pw_connection + */ +bool pw_connection_clear(struct pw_connection *conn) +{ + struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this); + + clear_buffer(&impl->out); + clear_buffer(&impl->in); + impl->in.update = true; + + return true; +} diff --git a/src/modules/module-protocol-native/connection.h b/src/modules/module-protocol-native/connection.h new file mode 100644 index 00000000..7f213449 --- /dev/null +++ b/src/modules/module-protocol-native/connection.h @@ -0,0 +1,89 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_CONNECTION_H__ +#define __PIPEWIRE_CONNECTION_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/defs.h> +#include <pipewire/sig.h> + +/** \class pw_connection + * + * \brief Manages the connection between client and server + * + * The \ref pw_connection handles the connection between client + * and server on a given socket. + */ +struct pw_connection { + int fd; /**< the socket */ + + /** Emited when data has been written that needs to be flushed */ + PW_SIGNAL(need_flush, (struct pw_listener *listener, struct pw_connection *conn)); + /** Emited when the connection is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_connection *conn)); +}; + +struct pw_connection * +pw_connection_new(int fd); + +void +pw_connection_destroy(struct pw_connection *conn); + +uint32_t +pw_connection_add_fd(struct pw_connection *conn, int fd); + +int +pw_connection_get_fd(struct pw_connection *conn, uint32_t index); + +bool +pw_connection_get_next(struct pw_connection *conn, + uint8_t *opcode, + uint32_t *dest_id, + void **data, uint32_t *size); + +struct spa_pod_builder * +pw_connection_begin_write_resource(struct pw_connection *conn, + struct pw_resource *resource, + uint8_t opcode); + +struct spa_pod_builder * +pw_connection_begin_write_proxy(struct pw_connection *conn, + struct pw_proxy *proxy, + uint8_t opcode); + + +void +pw_connection_end_write(struct pw_connection *conn, + struct spa_pod_builder *builder); + +bool +pw_connection_flush(struct pw_connection *conn); + +bool +pw_connection_clear(struct pw_connection *conn); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_CONNECTION_H__ */ diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c new file mode 100644 index 00000000..225fcb4d --- /dev/null +++ b/src/modules/module-protocol-native/protocol-native.c @@ -0,0 +1,1077 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <errno.h> + +#include "spa/pod-iter.h" + +#include "pipewire/pipewire.h" +#include "pipewire/protocol.h" +#include "pipewire/interfaces.h" +#include "pipewire/resource.h" + +#include "connection.h" + +/** \cond */ + +typedef bool(*demarshal_func_t) (void *object, void *data, size_t size); + +/** \endcond */ + +static void core_marshal_client_update(void *object, const struct spa_dict *props) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + int i, n_items; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_CLIENT_UPDATE); + + n_items = props ? props->n_items : 0; + + spa_pod_builder_add(b, SPA_POD_TYPE_STRUCT, &f, SPA_POD_TYPE_INT, n_items, 0); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRING, props->items[i].key, + SPA_POD_TYPE_STRING, props->items[i].value, 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static void core_marshal_sync(void *object, uint32_t seq) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_SYNC); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, seq); + + pw_connection_end_write(connection, b); +} + +static void core_marshal_get_registry(void *object, uint32_t new_id) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_GET_REGISTRY); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, new_id); + + pw_connection_end_write(connection, b); +} + +static void +core_marshal_create_node(void *object, + const char *factory_name, + const char *name, const struct spa_dict *props, uint32_t new_id) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i, n_items; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_CREATE_NODE); + + n_items = props ? props->n_items : 0; + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_STRING, factory_name, + SPA_POD_TYPE_STRING, name, SPA_POD_TYPE_INT, n_items, 0); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRING, props->items[i].key, + SPA_POD_TYPE_STRING, props->items[i].value, 0); + } + spa_pod_builder_add(b, SPA_POD_TYPE_INT, new_id, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static void +core_marshal_create_link(void *object, + uint32_t output_node_id, + uint32_t output_port_id, + uint32_t input_node_id, + uint32_t input_port_id, + const struct spa_format *filter, + const struct spa_dict *props, + uint32_t new_id) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i, n_items; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_CREATE_LINK); + + n_items = props ? props->n_items : 0; + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, output_node_id, + SPA_POD_TYPE_INT, output_port_id, + SPA_POD_TYPE_INT, input_node_id, + SPA_POD_TYPE_INT, input_port_id, + SPA_POD_TYPE_POD, filter, + SPA_POD_TYPE_INT, n_items, 0); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRING, props->items[i].key, + SPA_POD_TYPE_STRING, props->items[i].value, 0); + } + spa_pod_builder_add(b, + SPA_POD_TYPE_INT, new_id, + -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static void +core_marshal_update_types_client(void *object, uint32_t first_id, uint32_t n_types, const char **types) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_CORE_METHOD_UPDATE_TYPES); + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, first_id, SPA_POD_TYPE_INT, n_types, 0); + + for (i = 0; i < n_types; i++) { + spa_pod_builder_add(b, SPA_POD_TYPE_STRING, types[i], 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static bool core_demarshal_info(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_dict props; + struct pw_core_info info; + struct spa_pod_iter it; + int i; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &info.id, + SPA_POD_TYPE_LONG, &info.change_mask, + SPA_POD_TYPE_STRING, &info.user_name, + SPA_POD_TYPE_STRING, &info.host_name, + SPA_POD_TYPE_STRING, &info.version, + SPA_POD_TYPE_STRING, &info.name, + SPA_POD_TYPE_INT, &info.cookie, SPA_POD_TYPE_INT, &props.n_items, 0)) + return false; + + info.props = &props; + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &props.items[i].key, + SPA_POD_TYPE_STRING, &props.items[i].value, 0)) + return false; + } + pw_proxy_notify(proxy, struct pw_core_events, info, &info); + return true; +} + +static bool core_demarshal_done(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t seq; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &seq, 0)) + return false; + + pw_proxy_notify(proxy, struct pw_core_events, done, seq); + return true; +} + +static bool core_demarshal_error(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t id, res; + const char *error; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &id, + SPA_POD_TYPE_INT, &res, SPA_POD_TYPE_STRING, &error, 0)) + return false; + + pw_proxy_notify(proxy, struct pw_core_events, error, id, res, error); + return true; +} + +static bool core_demarshal_remove_id(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t id; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &id, 0)) + return false; + + pw_proxy_notify(proxy, struct pw_core_events, remove_id, id); + return true; +} + +static bool core_demarshal_update_types_client(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t first_id, n_types; + const char **types; + int i; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &first_id, SPA_POD_TYPE_INT, &n_types, 0)) + return false; + + types = alloca(n_types * sizeof(char *)); + for (i = 0; i < n_types; i++) { + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_STRING, &types[i], 0)) + return false; + } + pw_proxy_notify(proxy, struct pw_core_events, update_types, first_id, n_types, types); + return true; +} + +static void core_marshal_info(void *object, struct pw_core_info *info) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i, n_items; + + b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_INFO); + + n_items = info->props ? info->props->n_items : 0; + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, info->id, + SPA_POD_TYPE_LONG, info->change_mask, + SPA_POD_TYPE_STRING, info->user_name, + SPA_POD_TYPE_STRING, info->host_name, + SPA_POD_TYPE_STRING, info->version, + SPA_POD_TYPE_STRING, info->name, + SPA_POD_TYPE_INT, info->cookie, SPA_POD_TYPE_INT, n_items, 0); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRING, info->props->items[i].key, + SPA_POD_TYPE_STRING, info->props->items[i].value, 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static void core_marshal_done(void *object, uint32_t seq) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_DONE); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, seq); + + pw_connection_end_write(connection, b); +} + +static void core_marshal_error(void *object, uint32_t id, int res, const char *error, ...) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + char buffer[128]; + struct spa_pod_builder *b; + struct spa_pod_frame f; + va_list ap; + + b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_ERROR); + + va_start(ap, error); + vsnprintf(buffer, sizeof(buffer), error, ap); + va_end(ap); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, id, + SPA_POD_TYPE_INT, res, SPA_POD_TYPE_STRING, buffer); + + pw_connection_end_write(connection, b); +} + +static void core_marshal_remove_id(void *object, uint32_t id) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_REMOVE_ID); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, id); + + pw_connection_end_write(connection, b); +} + +static void +core_marshal_update_types_server(void *object, uint32_t first_id, uint32_t n_types, const char **types) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i; + + b = pw_connection_begin_write_resource(connection, resource, PW_CORE_EVENT_UPDATE_TYPES); + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, first_id, SPA_POD_TYPE_INT, n_types, 0); + + for (i = 0; i < n_types; i++) { + spa_pod_builder_add(b, SPA_POD_TYPE_STRING, types[i], 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static bool core_demarshal_client_update(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_dict props; + struct spa_pod_iter it; + uint32_t i; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &props.n_items, 0)) + return false; + + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &props.items[i].key, + SPA_POD_TYPE_STRING, &props.items[i].value, 0)) + return false; + } + pw_resource_do(resource, struct pw_core_methods, client_update, &props); + return true; +} + +static bool core_demarshal_sync(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t seq; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &seq, 0)) + return false; + + pw_resource_do(resource, struct pw_core_methods, sync, seq); + return true; +} + +static bool core_demarshal_get_registry(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + int32_t new_id; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &new_id, 0)) + return false; + + pw_resource_do(resource, struct pw_core_methods, get_registry, new_id); + return true; +} + +static bool core_demarshal_create_node(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t new_id, i; + const char *factory_name, *name; + struct spa_dict props; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &factory_name, + SPA_POD_TYPE_STRING, &name, SPA_POD_TYPE_INT, &props.n_items, 0)) + return false; + + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &props.items[i].key, + SPA_POD_TYPE_STRING, &props.items[i].value, 0)) + return false; + } + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &new_id, 0)) + return false; + + pw_resource_do(resource, struct pw_core_methods, create_node, factory_name, + name, &props, new_id); + return true; +} + +static bool core_demarshal_create_link(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t new_id, i; + uint32_t output_node_id, output_port_id, input_node_id, input_port_id; + struct spa_format *filter = NULL; + struct spa_dict props; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &resource->client->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &output_node_id, + SPA_POD_TYPE_INT, &output_port_id, + SPA_POD_TYPE_INT, &input_node_id, + SPA_POD_TYPE_INT, &input_port_id, + -SPA_POD_TYPE_OBJECT, &filter, + SPA_POD_TYPE_INT, &props.n_items, 0)) + return false; + + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &props.items[i].key, + SPA_POD_TYPE_STRING, &props.items[i].value, 0)) + return false; + } + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &new_id, 0)) + return false; + + pw_resource_do(resource, struct pw_core_methods, create_link, output_node_id, + output_port_id, + input_node_id, + input_port_id, + filter, + &props, + new_id); + return true; +} + +static bool core_demarshal_update_types_server(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t first_id, n_types; + const char **types; + int i; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &first_id, SPA_POD_TYPE_INT, &n_types, 0)) + return false; + + types = alloca(n_types * sizeof(char *)); + for (i = 0; i < n_types; i++) { + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_STRING, &types[i], 0)) + return false; + } + pw_resource_do(resource, struct pw_core_methods, update_types, first_id, n_types, types); + return true; +} + +static void registry_marshal_global(void *object, uint32_t id, const char *type, uint32_t version) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_REGISTRY_EVENT_GLOBAL); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, id, + SPA_POD_TYPE_STRING, type, + SPA_POD_TYPE_INT, version); + + pw_connection_end_write(connection, b); +} + +static void registry_marshal_global_remove(void *object, uint32_t id) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_REGISTRY_EVENT_GLOBAL_REMOVE); + + spa_pod_builder_struct(b, &f, SPA_POD_TYPE_INT, id); + + pw_connection_end_write(connection, b); +} + +static bool registry_demarshal_bind(void *object, void *data, size_t size) +{ + struct pw_resource *resource = object; + struct spa_pod_iter it; + uint32_t id, version, new_id; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &id, + SPA_POD_TYPE_INT, &version, + SPA_POD_TYPE_INT, &new_id, 0)) + return false; + + pw_resource_do(resource, struct pw_registry_methods, bind, id, version, new_id); + return true; +} + +static void module_marshal_info(void *object, struct pw_module_info *info) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i, n_items; + + b = pw_connection_begin_write_resource(connection, resource, PW_MODULE_EVENT_INFO); + + n_items = info->props ? info->props->n_items : 0; + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, info->id, + SPA_POD_TYPE_LONG, info->change_mask, + SPA_POD_TYPE_STRING, info->name, + SPA_POD_TYPE_STRING, info->filename, + SPA_POD_TYPE_STRING, info->args, SPA_POD_TYPE_INT, n_items, 0); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRING, info->props->items[i].key, + SPA_POD_TYPE_STRING, info->props->items[i].value, 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static bool module_demarshal_info(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + struct spa_dict props; + struct pw_module_info info; + int i; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &info.id, + SPA_POD_TYPE_LONG, &info.change_mask, + SPA_POD_TYPE_STRING, &info.name, + SPA_POD_TYPE_STRING, &info.filename, + SPA_POD_TYPE_STRING, &info.args, SPA_POD_TYPE_INT, &props.n_items, 0)) + return false; + + info.props = &props; + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &props.items[i].key, + SPA_POD_TYPE_STRING, &props.items[i].value, 0)) + return false; + } + pw_proxy_notify(proxy, struct pw_module_events, info, &info); + return true; +} + +static void node_marshal_info(void *object, struct pw_node_info *info) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i, n_items; + + b = pw_connection_begin_write_resource(connection, resource, PW_NODE_EVENT_INFO); + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, info->id, + SPA_POD_TYPE_LONG, info->change_mask, + SPA_POD_TYPE_STRING, info->name, + SPA_POD_TYPE_INT, info->max_input_ports, + SPA_POD_TYPE_INT, info->n_input_ports, + SPA_POD_TYPE_INT, info->n_input_formats, 0); + + for (i = 0; i < info->n_input_formats; i++) + spa_pod_builder_add(b, SPA_POD_TYPE_POD, info->input_formats[i], 0); + + spa_pod_builder_add(b, + SPA_POD_TYPE_INT, info->max_output_ports, + SPA_POD_TYPE_INT, info->n_output_ports, + SPA_POD_TYPE_INT, info->n_output_formats, 0); + + for (i = 0; i < info->n_output_formats; i++) + spa_pod_builder_add(b, SPA_POD_TYPE_POD, info->output_formats[i], 0); + + n_items = info->props ? info->props->n_items : 0; + + spa_pod_builder_add(b, + SPA_POD_TYPE_INT, info->state, + SPA_POD_TYPE_STRING, info->error, SPA_POD_TYPE_INT, n_items, 0); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRING, info->props->items[i].key, + SPA_POD_TYPE_STRING, info->props->items[i].value, 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static bool node_demarshal_info(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + struct spa_dict props; + struct pw_node_info info; + int i; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &info.id, + SPA_POD_TYPE_LONG, &info.change_mask, + SPA_POD_TYPE_STRING, &info.name, + SPA_POD_TYPE_INT, &info.max_input_ports, + SPA_POD_TYPE_INT, &info.n_input_ports, + SPA_POD_TYPE_INT, &info.n_input_formats, 0)) + return false; + + info.input_formats = alloca(info.n_input_formats * sizeof(struct spa_format *)); + for (i = 0; i < info.n_input_formats; i++) + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &info.input_formats[i], 0)) + return false; + + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &info.max_output_ports, + SPA_POD_TYPE_INT, &info.n_output_ports, + SPA_POD_TYPE_INT, &info.n_output_formats, 0)) + return false; + + info.output_formats = alloca(info.n_output_formats * sizeof(struct spa_format *)); + for (i = 0; i < info.n_output_formats; i++) + if (!spa_pod_iter_get(&it, SPA_POD_TYPE_OBJECT, &info.output_formats[i], 0)) + return false; + + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &info.state, + SPA_POD_TYPE_STRING, &info.error, + SPA_POD_TYPE_INT, &props.n_items, 0)) + return false; + + info.props = &props; + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &props.items[i].key, + SPA_POD_TYPE_STRING, &props.items[i].value, 0)) + return false; + } + pw_proxy_notify(proxy, struct pw_node_events, info, &info); + return true; +} + +static void client_marshal_info(void *object, struct pw_client_info *info) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + uint32_t i, n_items; + + b = pw_connection_begin_write_resource(connection, resource, PW_CLIENT_EVENT_INFO); + + n_items = info->props ? info->props->n_items : 0; + + spa_pod_builder_add(b, + SPA_POD_TYPE_STRUCT, &f, + SPA_POD_TYPE_INT, info->id, + SPA_POD_TYPE_LONG, info->change_mask, SPA_POD_TYPE_INT, n_items, 0); + + for (i = 0; i < n_items; i++) { + spa_pod_builder_add(b, + SPA_POD_TYPE_STRING, info->props->items[i].key, + SPA_POD_TYPE_STRING, info->props->items[i].value, 0); + } + spa_pod_builder_add(b, -SPA_POD_TYPE_STRUCT, &f, 0); + + pw_connection_end_write(connection, b); +} + +static bool client_demarshal_info(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + struct spa_dict props; + struct pw_client_info info; + uint32_t i; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &info.id, + SPA_POD_TYPE_LONG, &info.change_mask, + SPA_POD_TYPE_INT, &props.n_items, 0)) + return false; + + info.props = &props; + props.items = alloca(props.n_items * sizeof(struct spa_dict_item)); + for (i = 0; i < props.n_items; i++) { + if (!spa_pod_iter_get(&it, + SPA_POD_TYPE_STRING, &props.items[i].key, + SPA_POD_TYPE_STRING, &props.items[i].value, 0)) + return false; + } + pw_proxy_notify(proxy, struct pw_client_events, info, &info); + return true; +} + +static void link_marshal_info(void *object, struct pw_link_info *info) +{ + struct pw_resource *resource = object; + struct pw_connection *connection = resource->client->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_resource(connection, resource, PW_LINK_EVENT_INFO); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, info->id, + SPA_POD_TYPE_LONG, info->change_mask, + SPA_POD_TYPE_INT, info->output_node_id, + SPA_POD_TYPE_INT, info->output_port_id, + SPA_POD_TYPE_INT, info->input_node_id, + SPA_POD_TYPE_INT, info->input_port_id, + SPA_POD_TYPE_POD, info->format); + + pw_connection_end_write(connection, b); +} + +static bool link_demarshal_info(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + struct pw_link_info info = { 0, }; + + if (!spa_pod_iter_struct(&it, data, size) || + !pw_pod_remap_data(SPA_POD_TYPE_STRUCT, data, size, &proxy->remote->types) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &info.id, + SPA_POD_TYPE_LONG, &info.change_mask, + SPA_POD_TYPE_INT, &info.output_node_id, + SPA_POD_TYPE_INT, &info.output_port_id, + SPA_POD_TYPE_INT, &info.input_node_id, + SPA_POD_TYPE_INT, &info.input_port_id, + -SPA_POD_TYPE_OBJECT, &info.format, 0)) + return false; + + pw_proxy_notify(proxy, struct pw_link_events, info, &info); + return true; +} + +static bool registry_demarshal_global(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t id, version; + const char *type; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, + SPA_POD_TYPE_INT, &id, + SPA_POD_TYPE_STRING, &type, + SPA_POD_TYPE_INT, &version, 0)) + return false; + + pw_proxy_notify(proxy, struct pw_registry_events, global, id, type, version); + return true; +} + +static bool registry_demarshal_global_remove(void *object, void *data, size_t size) +{ + struct pw_proxy *proxy = object; + struct spa_pod_iter it; + uint32_t id; + + if (!spa_pod_iter_struct(&it, data, size) || + !spa_pod_iter_get(&it, SPA_POD_TYPE_INT, &id, 0)) + return false; + + pw_proxy_notify(proxy, struct pw_registry_events, global_remove, id); + return true; +} + +static void registry_marshal_bind(void *object, uint32_t id, uint32_t version, uint32_t new_id) +{ + struct pw_proxy *proxy = object; + struct pw_connection *connection = proxy->remote->protocol_private; + struct spa_pod_builder *b; + struct spa_pod_frame f; + + b = pw_connection_begin_write_proxy(connection, proxy, PW_REGISTRY_METHOD_BIND); + + spa_pod_builder_struct(b, &f, + SPA_POD_TYPE_INT, id, + SPA_POD_TYPE_INT, version, + SPA_POD_TYPE_INT, new_id); + + pw_connection_end_write(connection, b); +} + +static const struct pw_core_methods pw_protocol_native_client_core_methods = { + &core_marshal_update_types_client, + &core_marshal_sync, + &core_marshal_get_registry, + &core_marshal_client_update, + &core_marshal_create_node, + &core_marshal_create_link +}; + +static const demarshal_func_t pw_protocol_native_client_core_demarshal[PW_CORE_EVENT_NUM] = { + &core_demarshal_update_types_client, + &core_demarshal_done, + &core_demarshal_error, + &core_demarshal_remove_id, + &core_demarshal_info +}; + +static const struct pw_interface pw_protocol_native_client_core_interface = { + PIPEWIRE_TYPE__Core, + PW_VERSION_CORE, + PW_CORE_METHOD_NUM, &pw_protocol_native_client_core_methods, + PW_CORE_EVENT_NUM, pw_protocol_native_client_core_demarshal +}; + +static const struct pw_registry_methods pw_protocol_native_client_registry_methods = { + ®istry_marshal_bind +}; + +static const demarshal_func_t pw_protocol_native_client_registry_demarshal[] = { + ®istry_demarshal_global, + ®istry_demarshal_global_remove, +}; + +static const struct pw_interface pw_protocol_native_client_registry_interface = { + PIPEWIRE_TYPE__Registry, + PW_VERSION_REGISTRY, + PW_REGISTRY_METHOD_NUM, &pw_protocol_native_client_registry_methods, + PW_REGISTRY_EVENT_NUM, pw_protocol_native_client_registry_demarshal, +}; + +static const demarshal_func_t pw_protocol_native_client_module_demarshal[] = { + &module_demarshal_info, +}; + +static const struct pw_interface pw_protocol_native_client_module_interface = { + PIPEWIRE_TYPE__Module, + PW_VERSION_MODULE, + 0, NULL, + PW_MODULE_EVENT_NUM, pw_protocol_native_client_module_demarshal, +}; + +static const demarshal_func_t pw_protocol_native_client_node_demarshal[] = { + &node_demarshal_info, +}; + +static const struct pw_interface pw_protocol_native_client_node_interface = { + PIPEWIRE_TYPE__Node, + PW_VERSION_NODE, + 0, NULL, + PW_NODE_EVENT_NUM, pw_protocol_native_client_node_demarshal, +}; + +static const demarshal_func_t pw_protocol_native_client_client_demarshal[] = { + &client_demarshal_info, +}; + +static const struct pw_interface pw_protocol_native_client_client_interface = { + PIPEWIRE_TYPE__Client, + PW_VERSION_CLIENT, + 0, NULL, + PW_CLIENT_EVENT_NUM, pw_protocol_native_client_client_demarshal, +}; + +static const demarshal_func_t pw_protocol_native_client_link_demarshal[] = { + &link_demarshal_info, +}; + +static const struct pw_interface pw_protocol_native_client_link_interface = { + PIPEWIRE_TYPE__Link, + PW_VERSION_LINK, + 0, NULL, + PW_LINK_EVENT_NUM, pw_protocol_native_client_link_demarshal, +}; + +static const demarshal_func_t pw_protocol_native_server_core_demarshal[PW_CORE_METHOD_NUM] = { + &core_demarshal_update_types_server, + &core_demarshal_sync, + &core_demarshal_get_registry, + &core_demarshal_client_update, + &core_demarshal_create_node, + &core_demarshal_create_link +}; + +static const struct pw_core_events pw_protocol_native_server_core_events = { + &core_marshal_update_types_server, + &core_marshal_done, + &core_marshal_error, + &core_marshal_remove_id, + &core_marshal_info +}; + +const struct pw_interface pw_protocol_native_server_core_interface = { + PIPEWIRE_TYPE__Core, + PW_VERSION_CORE, + PW_CORE_METHOD_NUM, pw_protocol_native_server_core_demarshal, + PW_CORE_EVENT_NUM, &pw_protocol_native_server_core_events, +}; + +static const demarshal_func_t pw_protocol_native_server_registry_demarshal[] = { + ®istry_demarshal_bind, +}; + +static const struct pw_registry_events pw_protocol_native_server_registry_events = { + ®istry_marshal_global, + ®istry_marshal_global_remove, +}; + +const struct pw_interface pw_protocol_native_server_registry_interface = { + PIPEWIRE_TYPE__Registry, + PW_VERSION_REGISTRY, + PW_REGISTRY_METHOD_NUM, pw_protocol_native_server_registry_demarshal, + PW_REGISTRY_EVENT_NUM, &pw_protocol_native_server_registry_events, +}; + +static const struct pw_module_events pw_protocol_native_server_module_events = { + &module_marshal_info, +}; + +const struct pw_interface pw_protocol_native_server_module_interface = { + PIPEWIRE_TYPE__Module, + PW_VERSION_MODULE, + 0, NULL, + PW_MODULE_EVENT_NUM, &pw_protocol_native_server_module_events, +}; + +static const struct pw_node_events pw_protocol_native_server_node_events = { + &node_marshal_info, +}; + +const struct pw_interface pw_protocol_native_server_node_interface = { + PIPEWIRE_TYPE__Node, + PW_VERSION_NODE, + 0, NULL, + PW_NODE_EVENT_NUM, &pw_protocol_native_server_node_events, +}; + +static const struct pw_client_events pw_protocol_native_server_client_events = { + &client_marshal_info, +}; + +const struct pw_interface pw_protocol_native_server_client_interface = { + PIPEWIRE_TYPE__Client, + PW_VERSION_CLIENT, + 0, NULL, + PW_CLIENT_EVENT_NUM, &pw_protocol_native_server_client_events, +}; + +static const struct pw_link_events pw_protocol_native_server_link_events = { + &link_marshal_info, +}; + +const struct pw_interface pw_protocol_native_server_link_interface = { + PIPEWIRE_TYPE__Link, + PW_VERSION_LINK, + 0, NULL, + PW_LINK_EVENT_NUM, &pw_protocol_native_server_link_events, +}; + +struct pw_protocol *pw_protocol_native_init(void) +{ + static bool init = false; + struct pw_protocol *protocol; + + protocol = pw_protocol_get(PW_TYPE_PROTOCOL__Native); + + if (init) + return protocol; + + pw_protocol_add_interfaces(protocol, + &pw_protocol_native_client_core_interface, + &pw_protocol_native_server_core_interface); + pw_protocol_add_interfaces(protocol, + &pw_protocol_native_client_registry_interface, + &pw_protocol_native_server_registry_interface); + pw_protocol_add_interfaces(protocol, + &pw_protocol_native_client_module_interface, + &pw_protocol_native_server_module_interface); + pw_protocol_add_interfaces(protocol, + &pw_protocol_native_client_node_interface, + &pw_protocol_native_server_node_interface); + pw_protocol_add_interfaces(protocol, + &pw_protocol_native_client_client_interface, + &pw_protocol_native_server_client_interface); + pw_protocol_add_interfaces(protocol, + &pw_protocol_native_client_link_interface, + &pw_protocol_native_server_link_interface); + + init = true; + + return protocol; +} diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c new file mode 100644 index 00000000..208a3b5e --- /dev/null +++ b/src/modules/module-suspend-on-idle.c @@ -0,0 +1,195 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include "config.h" + +#include "pipewire/core.h" +#include "pipewire/module.h" + +struct impl { + struct pw_core *core; + struct pw_properties *properties; + + struct pw_listener global_added; + struct pw_listener global_removed; + + struct spa_list node_list; +}; + +struct node_info { + struct impl *impl; + struct pw_node *node; + struct spa_list link; + struct pw_listener node_state_request; + struct pw_listener node_state_changed; + struct spa_source *idle_timeout; +}; + +static struct node_info *find_node_info(struct impl *impl, struct pw_node *node) +{ + struct node_info *info; + + spa_list_for_each(info, &impl->node_list, link) { + if (info->node == node) + return info; + } + return NULL; +} + +static void remove_idle_timeout(struct node_info *info) +{ + if (info->idle_timeout) { + pw_loop_destroy_source(info->impl->core->main_loop, info->idle_timeout); + info->idle_timeout = NULL; + } +} + +static void node_info_free(struct node_info *info) +{ + spa_list_remove(&info->link); + remove_idle_timeout(info); + pw_signal_remove(&info->node_state_request); + pw_signal_remove(&info->node_state_changed); + free(info); +} + +static void idle_timeout(struct spa_loop_utils *utils, struct spa_source *source, void *data) +{ + struct node_info *info = data; + + pw_log_debug("module %p: node %p idle timeout", info->impl, info->node); + remove_idle_timeout(info); + pw_node_set_state(info->node, PW_NODE_STATE_SUSPENDED); +} + +static void +on_node_state_request(struct pw_listener *listener, struct pw_node *node, enum pw_node_state state) +{ + struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, node_state_request); + remove_idle_timeout(info); +} + +static void +on_node_state_changed(struct pw_listener *listener, + struct pw_node *node, enum pw_node_state old, enum pw_node_state state) +{ + struct node_info *info = SPA_CONTAINER_OF(listener, struct node_info, node_state_changed); + struct impl *impl = info->impl; + + if (state != PW_NODE_STATE_IDLE) { + remove_idle_timeout(info); + } else { + struct timespec value; + + pw_log_debug("module %p: node %p became idle", impl, node); + info->idle_timeout = pw_loop_add_timer(impl->core->main_loop, + idle_timeout, info); + value.tv_sec = 3; + value.tv_nsec = 0; + pw_loop_update_timer(impl->core->main_loop, + info->idle_timeout, &value, NULL, false); + } +} + +static void +on_global_added(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_added); + + if (global->type == impl->core->type.node) { + struct pw_node *node = global->object; + struct node_info *info; + + info = calloc(1, sizeof(struct node_info)); + info->impl = impl; + info->node = node; + spa_list_insert(impl->node_list.prev, &info->link); + pw_signal_add(&node->state_request, &info->node_state_request, + on_node_state_request); + pw_signal_add(&node->state_changed, &info->node_state_changed, + on_node_state_changed); + + pw_log_debug("module %p: node %p added", impl, node); + } +} + +static void +on_global_removed(struct pw_listener *listener, struct pw_core *core, struct pw_global *global) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, global_removed); + + if (global->type == impl->core->type.node) { + struct pw_node *node = global->object; + struct node_info *info; + + if ((info = find_node_info(impl, node))) + node_info_free(info); + + pw_log_debug("module %p: node %p removed", impl, node); + } +} + + +/** + * module_new: + * @core: #struct pw_core + * @properties: #struct pw_properties + * + * Make a new #struct impl object with given @properties + * + * Returns: a new #struct impl + */ +static struct impl *module_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("module %p: new", impl); + + impl->core = core; + impl->properties = properties; + + spa_list_init(&impl->node_list); + + pw_signal_add(&core->global_added, &impl->global_added, on_global_added); + pw_signal_add(&core->global_removed, &impl->global_removed, on_global_removed); + + return impl; +} + +#if 0 +static void module_destroy(struct impl *impl) +{ + pw_log_debug("module %p: destroy", impl); + + pw_global_destroy(impl->global); + + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + module_new(module->core, NULL); + return true; +} diff --git a/src/modules/spa/meson.build b/src/modules/spa/meson.build new file mode 100644 index 00000000..af49555f --- /dev/null +++ b/src/modules/spa/meson.build @@ -0,0 +1,34 @@ +pipewire_module_spa_c_args = [ + '-DHAVE_CONFIG_H', + '-D_GNU_SOURCE', +] + +pipewire_module_spa_monitor = shared_library('pipewire-module-spa-monitor', + [ 'module-monitor.c', 'spa-monitor.c', 'spa-node.c' ], + c_args : pipewire_module_spa_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : '@0@/pipewire-0.1'.format(get_option('libdir')), + dependencies : [mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_spa_node = shared_library('pipewire-module-spa-node', + [ 'module-node.c', 'spa-node.c' ], + c_args : pipewire_module_spa_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : '@0@/pipewire-0.1'.format(get_option('libdir')), + dependencies : [mathlib, dl_lib, pipewire_dep], +) + +pipewire_module_spa_node_factory = shared_library('pipewire-module-spa-node-factory', + [ 'module-node-factory.c', 'spa-node.c' ], + c_args : pipewire_module_spa_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + install_dir : '@0@/pipewire-0.1'.format(get_option('libdir')), + dependencies : [mathlib, dl_lib, pipewire_dep], +) diff --git a/src/modules/spa/module-monitor.c b/src/modules/spa/module-monitor.c new file mode 100644 index 00000000..f3f2b248 --- /dev/null +++ b/src/modules/spa/module-monitor.c @@ -0,0 +1,63 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <getopt.h> +#include <limits.h> + +#include <spa/lib/props.h> + +#include <pipewire/utils.h> +#include <pipewire/core.h> +#include <pipewire/module.h> + +#include "spa-monitor.h" + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + const char *dir; + char **argv; + int n_tokens; + + if (args == NULL) + goto wrong_arguments; + + argv = pw_split_strv(args, " \t", INT_MAX, &n_tokens); + if (n_tokens < 3) + goto not_enough_arguments; + + if ((dir = getenv("SPA_PLUGIN_DIR")) == NULL) + dir = PLUGINDIR; + + pw_spa_monitor_load(module->core, dir, argv[0], argv[1], argv[2]); + + pw_free_strv(argv); + + return true; + + not_enough_arguments: + pw_free_strv(argv); + wrong_arguments: + pw_log_error("usage: module-spa-monitor <plugin> <factory> <name>"); + return false; +} diff --git a/src/modules/spa/module-node-factory.c b/src/modules/spa/module-node-factory.c new file mode 100644 index 00000000..29a50f2b --- /dev/null +++ b/src/modules/spa/module-node-factory.c @@ -0,0 +1,118 @@ +/* PipeWire + * Copyright (C) 2017 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <dlfcn.h> + +#include "config.h" + +#include "pipewire/interfaces.h" +#include "pipewire/core.h" +#include "pipewire/module.h" + +#include "spa-node.h" + +struct impl { + struct pw_node_factory this; + struct pw_properties *properties; +}; + +static struct pw_node *create_node(struct pw_node_factory *factory, + struct pw_resource *resource, + const char *name, + struct pw_properties *properties) +{ + struct pw_node *node; + const char *lib, *factory_name; + + if (properties == NULL) + goto no_properties; + + lib = pw_properties_get(properties, "spa.library.name"); + factory_name = pw_properties_get(properties, "spa.factory.name"); + + if(lib == NULL || factory_name == NULL) + goto no_properties; + + node = pw_spa_node_load(factory->core, + NULL, + lib, + factory_name, + name, + properties); + if (node == NULL) + goto no_mem; + + return node; + + no_properties: + pw_log_error("missing properties"); + if (resource) { + pw_core_notify_error(resource->client->core_resource, + resource->client->core_resource->id, + SPA_RESULT_INVALID_ARGUMENTS, "missing properties"); + } + return NULL; + no_mem: + pw_log_error("can't create node"); + if (resource) { + pw_core_notify_error(resource->client->core_resource, + resource->client->core_resource->id, + SPA_RESULT_NO_MEMORY, "no memory"); + } + return NULL; +} + +static struct impl *module_new(struct pw_core *core, struct pw_properties *properties) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("module %p: new", impl); + + impl->properties = properties; + + impl->this.core = core; + impl->this.name = "spa-node-factory"; + pw_signal_init(&impl->this.destroy_signal); + impl->this.create_node = create_node; + + spa_list_insert(core->node_factory_list.prev, &impl->this.link); + + pw_core_add_global(core, NULL, core->type.node_factory, 0, impl, NULL, &impl->this.global); + + return impl; +} + +#if 0 +static void module_destroy(struct impl *impl) +{ + pw_log_debug("module %p: destroy", impl); + + free(impl); +} +#endif + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + module_new(module->core, NULL); + return true; +} diff --git a/src/modules/spa/module-node.c b/src/modules/spa/module-node.c new file mode 100644 index 00000000..d44fd1ba --- /dev/null +++ b/src/modules/spa/module-node.c @@ -0,0 +1,74 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <getopt.h> +#include <limits.h> + +#include <spa/lib/props.h> + +#include <pipewire/utils.h> +#include <pipewire/core.h> +#include <pipewire/module.h> + +#include "spa-monitor.h" +#include "spa-node.h" + +bool pipewire__module_init(struct pw_module *module, const char *args) +{ + struct pw_properties *props = NULL; + char **argv; + int i, n_tokens; + + if (args == NULL) + goto wrong_arguments; + + argv = pw_split_strv(args, " \t", INT_MAX, &n_tokens); + if (n_tokens < 3) + goto not_enough_arguments; + + props = pw_properties_new(NULL, NULL); + + for (i = 3; i < n_tokens; i++) { + char **prop; + int n_props; + + prop = pw_split_strv(argv[i], "=", INT_MAX, &n_props); + if (n_props >= 2) + pw_properties_set(props, prop[0], prop[1]); + + pw_free_strv(prop); + } + + pw_spa_node_load(module->core, NULL, argv[0], argv[1], argv[2], props); + + pw_free_strv(argv); + + return true; + + not_enough_arguments: + pw_free_strv(argv); + wrong_arguments: + pw_log_error("usage: module-spa-node <plugin> <factory> <name> [key=value ...]"); + return false; +} diff --git a/src/modules/spa/spa-monitor.c b/src/modules/spa/spa-monitor.c new file mode 100644 index 00000000..e8850a6c --- /dev/null +++ b/src/modules/spa/spa-monitor.c @@ -0,0 +1,315 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <dlfcn.h> +#include <errno.h> +#include <poll.h> +#include <string.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/mman.h> + +#include <spa/node.h> +#include <spa/monitor.h> +#include <spa/pod-iter.h> + +#include <pipewire/log.h> +#include <pipewire/node.h> + +#include "spa-monitor.h" +#include "spa-node.h" + +struct monitor_item { + char *id; + struct spa_list link; + struct pw_node *node; +}; + +struct impl { + struct pw_spa_monitor this; + + struct pw_core *core; + + void *hnd; + + struct spa_list item_list; +}; + +static void add_item(struct pw_spa_monitor *this, struct spa_monitor_item *item) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res; + struct spa_handle *handle; + struct monitor_item *mitem; + void *node_iface; + void *clock_iface; + struct pw_properties *props = NULL; + const char *name, *id, *klass; + struct spa_handle_factory *factory; + struct spa_pod *info = NULL; + + spa_pod_object_query(&item->object, + impl->core->type.monitor.name, SPA_POD_TYPE_STRING, &name, + impl->core->type.monitor.id, SPA_POD_TYPE_STRING, &id, + impl->core->type.monitor.klass, SPA_POD_TYPE_STRING, &klass, + impl->core->type.monitor.factory, SPA_POD_TYPE_POINTER, &factory, + impl->core->type.monitor.info, SPA_POD_TYPE_STRUCT, &info, 0); + + pw_log_debug("monitor %p: add: \"%s\" (%s)", this, name, id); + + props = pw_properties_new(NULL, NULL); + + if (info) { + struct spa_pod_iter it; + + spa_pod_iter_pod(&it, info); + while (true) { + const char *key, *val; + if (!spa_pod_iter_get + (&it, SPA_POD_TYPE_STRING, &key, SPA_POD_TYPE_STRING, &val, 0)) + break; + pw_properties_set(props, key, val); + } + } + pw_properties_set(props, "media.class", klass); + + handle = calloc(1, factory->size); + if ((res = spa_handle_factory_init(factory, + handle, + &props->dict, + impl->core->support, impl->core->n_support)) < 0) { + pw_log_error("can't make factory instance: %d", res); + return; + } + if ((res = spa_handle_get_interface(handle, impl->core->type.spa_node, &node_iface)) < 0) { + pw_log_error("can't get NODE interface: %d", res); + return; + } + if ((res = spa_handle_get_interface(handle, impl->core->type.spa_clock, &clock_iface)) < 0) { + pw_log_info("no CLOCK interface: %d", res); + } + + + mitem = calloc(1, sizeof(struct monitor_item)); + mitem->id = strdup(id); + mitem->node = pw_spa_node_new(impl->core, NULL, name, false, node_iface, clock_iface, props); + + spa_list_insert(impl->item_list.prev, &mitem->link); +} + +static struct monitor_item *find_item(struct pw_spa_monitor *this, const char *id) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct monitor_item *mitem; + + spa_list_for_each(mitem, &impl->item_list, link) { + if (strcmp(mitem->id, id) == 0) { + return mitem; + } + } + return NULL; +} + +void destroy_item(struct monitor_item *mitem) +{ + pw_node_destroy(mitem->node); + spa_list_remove(&mitem->link); + free(mitem->id); + free(mitem); +} + +static void remove_item(struct pw_spa_monitor *this, struct spa_monitor_item *item) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct monitor_item *mitem; + const char *name, *id; + + spa_pod_object_query(&item->object, + impl->core->type.monitor.name, SPA_POD_TYPE_STRING, &name, + impl->core->type.monitor.id, SPA_POD_TYPE_STRING, &id, 0); + + pw_log_debug("monitor %p: remove: \"%s\" (%s)", this, name, id); + mitem = find_item(this, id); + if (mitem) + destroy_item(mitem); +} + +static void on_monitor_event(struct spa_monitor *monitor, struct spa_event *event, void *user_data) +{ + struct impl *impl = user_data; + struct pw_spa_monitor *this = &impl->this; + + if (SPA_EVENT_TYPE(event) == impl->core->type.monitor.Added) { + struct spa_monitor_item *item = SPA_POD_CONTENTS(struct spa_event, event); + add_item(this, item); + } else if (SPA_EVENT_TYPE(event) == impl->core->type.monitor.Removed) { + struct spa_monitor_item *item = SPA_POD_CONTENTS(struct spa_event, event); + remove_item(this, item); + } else if (SPA_EVENT_TYPE(event) == impl->core->type.monitor.Changed) { + struct spa_monitor_item *item = SPA_POD_CONTENTS(struct spa_event, event); + const char *name; + + spa_pod_object_query(&item->object, + impl->core->type.monitor.name, SPA_POD_TYPE_STRING, &name, 0); + + pw_log_debug("monitor %p: changed: \"%s\"", this, name); + } +} + +static void update_monitor(struct pw_core *core, const char *name) +{ + const char *monitors; + struct spa_dict_item item; + struct spa_dict dict = SPA_DICT_INIT(1, &item); + + if (core->properties) + monitors = pw_properties_get(core->properties, "monitors"); + else + monitors = NULL; + + item.key = "monitors"; + if (monitors == NULL) + item.value = name; + else + asprintf((char **) &item.value, "%s,%s", monitors, name); + + pw_core_update_properties(core, &dict); + + if (monitors != NULL) + free((void *) item.value); +} + +static const struct spa_monitor_callbacks callbacks = { + SPA_VERSION_MONITOR_CALLBACKS, + on_monitor_event, +}; + +struct pw_spa_monitor *pw_spa_monitor_load(struct pw_core *core, + const char *dir, + const char *lib, + const char *factory_name, const char *system_name) +{ + struct impl *impl; + struct pw_spa_monitor *this; + struct spa_handle *handle; + int res; + void *iface; + void *hnd; + uint32_t index; + spa_handle_factory_enum_func_t enum_func; + const struct spa_handle_factory *factory; + char *filename; + + asprintf(&filename, "%s/%s.so", dir, lib); + + if ((hnd = dlopen(filename, RTLD_NOW)) == NULL) { + pw_log_error("can't load %s: %s", filename, dlerror()); + goto open_failed; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + pw_log_error("can't find enum function"); + goto no_symbol; + } + + for (index = 0;; index++) { + if ((res = enum_func(&factory, index)) < 0) { + if (res != SPA_RESULT_ENUM_END) + pw_log_error("can't enumerate factories: %d", res); + goto enum_failed; + } + if (strcmp(factory->name, factory_name) == 0) + break; + } + handle = calloc(1, factory->size); + if ((res = spa_handle_factory_init(factory, + handle, NULL, core->support, core->n_support)) < 0) { + pw_log_error("can't make factory instance: %d", res); + goto init_failed; + } + if ((res = spa_handle_get_interface(handle, core->type.spa_monitor, &iface)) < 0) { + free(handle); + pw_log_error("can't get MONITOR interface: %d", res); + goto interface_failed; + } + + impl = calloc(1, sizeof(struct impl)); + impl->core = core; + impl->hnd = hnd; + + this = &impl->this; + pw_signal_init(&this->destroy_signal); + this->monitor = iface; + this->lib = filename; + this->factory_name = strdup(factory_name); + this->system_name = strdup(system_name); + this->handle = handle; + + update_monitor(core, this->system_name); + + spa_list_init(&impl->item_list); + + for (index = 0;; index++) { + struct spa_monitor_item *item; + int res; + + if ((res = spa_monitor_enum_items(this->monitor, &item, index)) < 0) { + if (res != SPA_RESULT_ENUM_END) + pw_log_debug("spa_monitor_enum_items: got error %d\n", res); + break; + } + add_item(this, item); + } + spa_monitor_set_callbacks(this->monitor, &callbacks, impl); + + return this; + + interface_failed: + spa_handle_clear(handle); + init_failed: + free(handle); + enum_failed: + no_symbol: + dlclose(hnd); + open_failed: + free(filename); + return NULL; + +} + +void pw_spa_monitor_destroy(struct pw_spa_monitor *monitor) +{ + struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, this); + struct monitor_item *mitem, *tmp; + + pw_log_debug("spa-monitor %p: dispose", impl); + pw_signal_emit(&monitor->destroy_signal, monitor); + + spa_list_for_each_safe(mitem, tmp, &impl->item_list, link) + destroy_item(mitem); + + spa_handle_clear(monitor->handle); + free(monitor->handle); + free(monitor->lib); + free(monitor->factory_name); + free(monitor->system_name); + + dlclose(impl->hnd); + free(impl); +} diff --git a/src/modules/spa/spa-monitor.h b/src/modules/spa/spa-monitor.h new file mode 100644 index 00000000..846d16fc --- /dev/null +++ b/src/modules/spa/spa-monitor.h @@ -0,0 +1,54 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_SPA_MONITOR_H__ +#define __PIPEWIRE_SPA_MONITOR_H__ + +#include <spa/monitor.h> + +#include <pipewire/core.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct pw_spa_monitor { + struct spa_monitor *monitor; + + char *lib; + char *factory_name; + char *system_name; + struct spa_handle *handle; + + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_spa_monitor *monitor)); +}; + +struct pw_spa_monitor * +pw_spa_monitor_load(struct pw_core *core, + const char *dir, + const char *lib, + const char *factory_name, const char *system_name); +void +pw_spa_monitor_destroy(struct pw_spa_monitor *monitor); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_SPA_MONITOR_H__ */ diff --git a/src/modules/spa/spa-node.c b/src/modules/spa/spa-node.c new file mode 100644 index 00000000..a3aaf5c1 --- /dev/null +++ b/src/modules/spa/spa-node.c @@ -0,0 +1,588 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <spa/graph-scheduler3.h> + +#include <string.h> +#include <stdio.h> +#include <dlfcn.h> + +#include <spa/node.h> + +#include "spa-node.h" + +struct impl { + struct pw_node *this; + + bool async_init; + + void *hnd; + struct spa_handle *handle; + struct spa_node *node; /**< handle to SPA node */ + char *lib; + char *factory_name; +}; + +struct port { + struct pw_port *port; + + struct spa_node *node; +}; + + +static int port_impl_enum_formats(struct pw_port *port, + struct spa_format **format, + const struct spa_format *filter, + int32_t index) +{ + struct port *p = port->user_data; + return spa_node_port_enum_formats(p->node, port->direction, port->port_id, format, filter, index); +} + +static int port_impl_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format) +{ + struct port *p = port->user_data; + return spa_node_port_set_format(p->node, port->direction, port->port_id, flags, format); +} + +static int port_impl_get_format(struct pw_port *port, const struct spa_format **format) +{ + struct port *p = port->user_data; + return spa_node_port_get_format(p->node, port->direction, port->port_id, format); +} + +static int port_impl_get_info(struct pw_port *port, const struct spa_port_info **info) +{ + struct port *p = port->user_data; + return spa_node_port_get_info(p->node, port->direction, port->port_id, info); +} + +static int port_impl_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param) +{ + struct port *p = port->user_data; + return spa_node_port_enum_params(p->node, port->direction, port->port_id, index, param); +} + +static int port_impl_set_param(struct pw_port *port, struct spa_param *param) +{ + struct port *p = port->user_data; + return spa_node_port_set_param(p->node, port->direction, port->port_id, param); +} + +static int port_impl_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers) +{ + struct port *p = port->user_data; + return spa_node_port_use_buffers(p->node, port->direction, port->port_id, buffers, n_buffers); +} + +static int port_impl_alloc_buffers(struct pw_port *port, + struct spa_param **params, uint32_t n_params, + struct spa_buffer **buffers, uint32_t *n_buffers) +{ + struct port *p = port->user_data; + return spa_node_port_alloc_buffers(p->node, port->direction, port->port_id, + params, n_params, buffers, n_buffers); +} + +static int port_impl_reuse_buffer(struct pw_port *port, uint32_t buffer_id) +{ + struct port *p = port->user_data; + return spa_node_port_reuse_buffer(p->node, port->port_id, buffer_id); +} + +static int port_impl_send_command(struct pw_port *port, struct spa_command *command) +{ + struct port *p = port->user_data; + return spa_node_port_send_command(p->node, + port->direction, + port->port_id, + command); +} + +const struct pw_port_implementation port_impl = { + PW_VERSION_PORT_IMPLEMENTATION, + port_impl_enum_formats, + port_impl_set_format, + port_impl_get_format, + port_impl_get_info, + port_impl_enum_params, + port_impl_set_param, + port_impl_use_buffers, + port_impl_alloc_buffers, + port_impl_reuse_buffer, + port_impl_send_command, +}; + +static struct pw_port * +make_port(struct pw_node *node, enum pw_direction direction, uint32_t port_id) +{ + struct impl *impl = node->user_data; + struct pw_port *port; + struct port *p; + + port = pw_port_new(direction, port_id, sizeof(struct port)); + if (port == NULL) + return NULL; + + p = port->user_data; + p->node = impl->node; + + port->implementation = &port_impl; + + spa_node_port_set_io(impl->node, direction, port_id, &port->io); + + pw_port_add(port, node); + + return port; +} + +static void update_port_ids(struct impl *impl) +{ + struct pw_node *this = impl->this; + uint32_t *input_port_ids, *output_port_ids; + uint32_t n_input_ports, n_output_ports, max_input_ports, max_output_ports; + uint32_t i; + struct spa_list *ports; + + spa_node_get_n_ports(impl->node, + &n_input_ports, &max_input_ports, &n_output_ports, &max_output_ports); + + this->info.max_input_ports = max_input_ports; + this->info.max_output_ports = max_output_ports; + + input_port_ids = alloca(sizeof(uint32_t) * n_input_ports); + output_port_ids = alloca(sizeof(uint32_t) * n_output_ports); + + spa_node_get_port_ids(impl->node, + max_input_ports, input_port_ids, max_output_ports, output_port_ids); + + pw_log_debug("node %p: update_port ids %u/%u, %u/%u", this, + n_input_ports, max_input_ports, n_output_ports, max_output_ports); + + i = 0; + ports = &this->input_ports; + while (true) { + struct pw_port *p = (ports == &this->input_ports) ? NULL : + SPA_CONTAINER_OF(ports, struct pw_port, link); + + if (p && i < n_input_ports && p->port_id == input_port_ids[i]) { + pw_log_debug("node %p: exiting input port %d", this, input_port_ids[i]); + i++; + ports = ports->next; + } else if ((p && i < n_input_ports && input_port_ids[i] < p->port_id) + || i < n_input_ports) { + struct pw_port *np; + pw_log_debug("node %p: input port added %d", this, input_port_ids[i]); + np = make_port(this, PW_DIRECTION_INPUT, input_port_ids[i]); + + ports = np->link.next; + i++; + } else if (p) { + ports = ports->next; + pw_log_debug("node %p: input port removed %d", this, p->port_id); + pw_port_destroy(p); + } else { + pw_log_debug("node %p: no more input ports", this); + break; + } + } + + i = 0; + ports = &this->output_ports; + while (true) { + struct pw_port *p = (ports == &this->output_ports) ? NULL : + SPA_CONTAINER_OF(ports, struct pw_port, link); + + if (p && i < n_output_ports && p->port_id == output_port_ids[i]) { + pw_log_debug("node %p: exiting output port %d", this, output_port_ids[i]); + i++; + ports = ports->next; + } else if ((p && i < n_output_ports && output_port_ids[i] < p->port_id) + || i < n_output_ports) { + struct pw_port *np; + pw_log_debug("node %p: output port added %d", this, output_port_ids[i]); + np = make_port(this, PW_DIRECTION_OUTPUT, output_port_ids[i]); + ports = np->link.next; + i++; + } else if (p) { + ports = ports->next; + pw_log_debug("node %p: output port removed %d", this, p->port_id); + pw_port_destroy(p); + } else { + pw_log_debug("node %p: no more output ports", this); + break; + } + } +} + + +static int node_impl_get_props(struct pw_node *node, struct spa_props **props) +{ + struct impl *impl = node->user_data; + return spa_node_get_props(impl->node, props); +} + +static int node_impl_set_props(struct pw_node *node, const struct spa_props *props) +{ + struct impl *impl = node->user_data; + return spa_node_set_props(impl->node, props); +} + +static int node_impl_send_command(struct pw_node *node, struct spa_command *command) +{ + struct impl *impl = node->user_data; + return spa_node_send_command(impl->node, command); +} + +static struct pw_port* +node_impl_add_port(struct pw_node *node, + enum pw_direction direction, + uint32_t port_id) +{ + struct impl *impl = node->user_data; + int res; + + if ((res = spa_node_add_port(impl->node, direction, port_id)) < 0) { + pw_log_error("node %p: could not add port %d %d", node, port_id, res); + return NULL; + } + + return make_port(node, direction, port_id); +} + +static int node_impl_schedule_input(struct pw_node *node) +{ + struct impl *impl = node->user_data; + return spa_node_process_input(impl->node); +} + +static int node_impl_schedule_output(struct pw_node *node) +{ + struct impl *impl = node->user_data; + return spa_node_process_output(impl->node); +} + +static const struct pw_node_implementation node_impl = { + PW_VERSION_NODE_IMPLEMENTATION, + node_impl_get_props, + node_impl_set_props, + node_impl_send_command, + node_impl_add_port, + node_impl_schedule_input, + node_impl_schedule_output, +}; + +static void pw_spa_node_destroy(void *object) +{ + struct pw_node *node = object; + struct impl *impl = node->user_data; + + pw_log_debug("spa-node %p: destroy", node); + + if (impl->handle) { + spa_handle_clear(impl->handle); + free(impl->handle); + } + free(impl->lib); + free(impl->factory_name); + if (impl->hnd) + dlclose(impl->hnd); +} + +static void complete_init(struct impl *impl) +{ + struct pw_node *this = impl->this; + update_port_ids(impl); + pw_node_export(this); +} + +static void on_node_done(struct spa_node *node, int seq, int res, void *user_data) +{ + struct impl *impl = user_data; + struct pw_node *this = impl->this; + + if (impl->async_init) { + complete_init(impl); + impl->async_init = false; + } + + pw_log_debug("spa-node %p: async complete event %d %d", this, seq, res); + pw_signal_emit(&this->async_complete, this, seq, res); +} + +static void on_node_event(struct spa_node *node, struct spa_event *event, void *user_data) +{ + struct impl *impl = user_data; + struct pw_node *this = impl->this; + + pw_signal_emit(&this->event, this, event); +} + +static void on_node_need_input(struct spa_node *node, void *user_data) +{ + struct impl *impl = user_data; + struct pw_node *this = impl->this; + + spa_graph_scheduler_pull(this->rt.sched, &this->rt.node); + while (spa_graph_scheduler_iterate(this->rt.sched)); +} + +static void on_node_have_output(struct spa_node *node, void *user_data) +{ + struct impl *impl = user_data; + struct pw_node *this = impl->this; + + spa_graph_scheduler_push(this->rt.sched, &this->rt.node); + while (spa_graph_scheduler_iterate(this->rt.sched)); +} + +static void +on_node_reuse_buffer(struct spa_node *node, uint32_t port_id, uint32_t buffer_id, void *user_data) +{ + struct impl *impl = user_data; + struct pw_node *this = impl->this; + struct spa_graph_port *p, *pp; + + spa_list_for_each(p, &this->rt.node.ports[SPA_DIRECTION_INPUT], link) { + if (p->port_id != port_id) + continue; + + pp = p->peer; + if (pp && pp->methods->reuse_buffer) + pp->methods->reuse_buffer(pp, buffer_id, pp->user_data); + + break; + } +} + +static const struct spa_node_callbacks node_callbacks = { + SPA_VERSION_NODE_CALLBACKS, + &on_node_done, + &on_node_event, + &on_node_need_input, + &on_node_have_output, + &on_node_reuse_buffer, +}; + +struct pw_node * +pw_spa_node_new(struct pw_core *core, + struct pw_resource *owner, + const char *name, + bool async, + struct spa_node *node, + struct spa_clock *clock, + struct pw_properties *properties) +{ + struct pw_node *this; + struct impl *impl; + + if (node->info) { + uint32_t i; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + + if (properties) + return NULL; + + for (i = 0; i < node->info->n_items; i++) + pw_properties_set(properties, + node->info->items[i].key, + node->info->items[i].value); + } + + this = pw_node_new(core, owner, name, properties, sizeof(struct impl)); + if (this == NULL) + return NULL; + + this->destroy = pw_spa_node_destroy; + this->implementation = &node_impl; + this->clock = clock; + + impl = this->user_data; + impl->this = this; + impl->node = node; + impl->async_init = async; + + if (spa_node_set_callbacks(impl->node, &node_callbacks, impl) < 0) + pw_log_warn("spa-node %p: error setting callback", this); + + if (!async) { + complete_init(impl); + } + + return this; +} + +static int +setup_props(struct pw_core *core, struct spa_node *spa_node, struct pw_properties *pw_props) +{ + int res; + struct spa_props *props; + void *state = NULL; + const char *key; + + if ((res = spa_node_get_props(spa_node, &props)) != SPA_RESULT_OK) { + pw_log_debug("spa_node_get_props failed: %d", res); + return SPA_RESULT_ERROR; + } + + while ((key = pw_properties_iterate(pw_props, &state))) { + struct spa_pod_prop *prop; + uint32_t id; + + if (!spa_type_is_a(key, SPA_TYPE_PROPS_BASE)) + continue; + + id = spa_type_map_get_id(core->type.map, key); + if (id == SPA_ID_INVALID) + continue; + + if ((prop = spa_pod_object_find_prop(&props->object, id))) { + const char *value = pw_properties_get(pw_props, key); + + pw_log_info("configure prop %s", key); + + switch(prop->body.value.type) { + case SPA_POD_TYPE_ID: + SPA_POD_VALUE(struct spa_pod_id, &prop->body.value) = + spa_type_map_get_id(core->type.map, value); + break; + case SPA_POD_TYPE_INT: + SPA_POD_VALUE(struct spa_pod_int, &prop->body.value) = atoi(value); + break; + case SPA_POD_TYPE_LONG: + SPA_POD_VALUE(struct spa_pod_long, &prop->body.value) = atoi(value); + break; + case SPA_POD_TYPE_FLOAT: + SPA_POD_VALUE(struct spa_pod_float, &prop->body.value) = atof(value); + break; + case SPA_POD_TYPE_DOUBLE: + SPA_POD_VALUE(struct spa_pod_double, &prop->body.value) = atof(value); + break; + case SPA_POD_TYPE_STRING: + break; + default: + break; + } + } + } + + if ((res = spa_node_set_props(spa_node, props)) != SPA_RESULT_OK) { + pw_log_debug("spa_node_set_props failed: %d", res); + return SPA_RESULT_ERROR; + } + return SPA_RESULT_OK; +} + + +struct pw_node *pw_spa_node_load(struct pw_core *core, + struct pw_resource *owner, + const char *lib, + const char *factory_name, + const char *name, + struct pw_properties *properties) +{ + struct pw_node *this; + struct impl *impl; + struct spa_node *spa_node; + struct spa_clock *spa_clock; + int res; + struct spa_handle *handle; + void *hnd; + uint32_t index; + spa_handle_factory_enum_func_t enum_func; + const struct spa_handle_factory *factory; + void *iface; + char *filename; + const char *dir; + bool async; + + if ((dir = getenv("SPA_PLUGIN_DIR")) == NULL) + dir = PLUGINDIR; + + asprintf(&filename, "%s/%s.so", dir, lib); + + if ((hnd = dlopen(filename, RTLD_NOW)) == NULL) { + pw_log_error("can't load %s: %s", filename, dlerror()); + goto open_failed; + } + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + pw_log_error("can't find enum function"); + goto no_symbol; + } + + for (index = 0;; index++) { + if ((res = enum_func(&factory, index)) < 0) { + if (res != SPA_RESULT_ENUM_END) + pw_log_error("can't enumerate factories: %d", res); + goto enum_failed; + } + if (strcmp(factory->name, factory_name) == 0) + break; + } + + handle = calloc(1, factory->size); + if ((res = spa_handle_factory_init(factory, + handle, NULL, core->support, core->n_support)) < 0) { + pw_log_error("can't make factory instance: %d", res); + goto init_failed; + } + async = SPA_RESULT_IS_ASYNC(res); + + if ((res = spa_handle_get_interface(handle, core->type.spa_node, &iface)) < 0) { + pw_log_error("can't get node interface %d", res); + goto interface_failed; + } + spa_node = iface; + + if ((res = spa_handle_get_interface(handle, core->type.spa_clock, &iface)) < 0) { + iface = NULL; + } + spa_clock = iface; + + if (properties != NULL) { + if (setup_props(core, spa_node, properties) != SPA_RESULT_OK) { + pw_log_debug("Unrecognized properties"); + } + } + + this = pw_spa_node_new(core, owner, name, async, spa_node, spa_clock, properties); + impl->hnd = hnd; + impl->handle = handle; + impl->lib = filename; + impl->factory_name = strdup(factory_name); + + return this; + + interface_failed: + spa_handle_clear(handle); + init_failed: + free(handle); + enum_failed: + no_symbol: + dlclose(hnd); + open_failed: + free(filename); + return NULL; +} diff --git a/src/modules/spa/spa-node.h b/src/modules/spa/spa-node.h new file mode 100644 index 00000000..35eba6a2 --- /dev/null +++ b/src/modules/spa/spa-node.h @@ -0,0 +1,51 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_SPA_NODE_H__ +#define __PIPEWIRE_SPA_NODE_H__ + +#include <pipewire/core.h> +#include <pipewire/node.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct pw_node * +pw_spa_node_new(struct pw_core *core, + struct pw_resource *owner, /**< optional owner */ + const char *name, + bool async, + struct spa_node *node, + struct spa_clock *clock, + struct pw_properties *properties); + +struct pw_node * +pw_spa_node_load(struct pw_core *core, + struct pw_resource *owner, /**< optional owner */ + const char *lib, + const char *factory_name, + const char *name, + struct pw_properties *properties); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_SPA_NODE_H__ */ diff --git a/src/pipewire/.gitignore b/src/pipewire/.gitignore new file mode 100644 index 00000000..7ed96236 --- /dev/null +++ b/src/pipewire/.gitignore @@ -0,0 +1,2 @@ +enumtypes.c +enumtypes.h diff --git a/src/pipewire/array.h b/src/pipewire/array.h new file mode 100644 index 00000000..1f761161 --- /dev/null +++ b/src/pipewire/array.h @@ -0,0 +1,134 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_ARRAY_H__ +#define __PIPEWIRE_ARRAY_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/defs.h> + +/** \class pw_array + * + * \brief An array object + * + * The array is a dynamically resizable data structure that can + * hold items of the same size. + */ +struct pw_array { + void *data; /**< pointer to array data */ + size_t size; /**< length of array in bytes */ + size_t alloc; /**< number of allocated memory in \a data */ + size_t extend; /**< number of bytes to extend with */ +}; + +#define PW_ARRAY_INIT(extend) (struct pw_array) { NULL, 0, 0, extend } + +#define pw_array_get_len_s(a,s) ((a)->size / (s)) +#define pw_array_get_unchecked_s(a,idx,s,t) SPA_MEMBER((a)->data,(idx)*(s),t) +#define pw_array_check_index_s(a,idx,s) ((idx) < pw_array_get_len_s(a,s)) + +/** Get the number of items of type \a t in array \memberof pw_array */ +#define pw_array_get_len(a,t) pw_array_get_len_s(a,sizeof(t)) +/** Get the item with index \a idx and type \a t from array \memberof pw_array */ +#define pw_array_get_unchecked(a,idx,t) pw_array_get_unchecked_s(a,idx,sizeof(t),t) +/** Check if an item with index \a idx and type \a t exist in array \memberof pw_array */ +#define pw_array_check_index(a,idx,t) pw_array_check_index_s(a,idx,sizeof(t)) + +#define pw_array_for_each(pos, array) \ + for (pos = (array)->data; \ + (const uint8_t *) pos < ((const uint8_t *) (array)->data + (array)->size); \ + (pos)++) + +/** Initialize the array with given extend \memberof pw_array */ +static inline void pw_array_init(struct pw_array *arr, size_t extend) +{ + arr->data = NULL; + arr->size = arr->alloc = 0; + arr->extend = extend; +} + +/** Clear the array */ +static inline void pw_array_clear(struct pw_array *arr) +{ + free(arr->data); +} + +/** Make sure \a size bytes can be added to the array \memberof pw_array */ +static inline bool pw_array_ensure_size(struct pw_array *arr, size_t size) +{ + size_t alloc, need; + + alloc = arr->alloc; + need = arr->size + size; + + if (SPA_UNLIKELY(alloc < need)) { + void *data; + alloc = SPA_MAX(alloc, arr->extend); + while (alloc < need) + alloc *= 2; + if (SPA_UNLIKELY((data = realloc(arr->data, alloc)) == NULL)) + return false; + arr->data = data; + arr->alloc = alloc; + } + return true; +} + +/** Add \a ref size bytes to \a arr. A pointer to memory that can + * hold at least \a size bytes is returned \memberof pw_array */ +static inline void *pw_array_add(struct pw_array *arr, size_t size) +{ + void *p; + + if (!pw_array_ensure_size(arr, size)) + return NULL; + + p = arr->data + arr->size; + arr->size += size; + + return p; +} + +/** Add \a ref size bytes to \a arr. When there is not enough memory to + * hold \a size bytes, NULL is returned \memberof pw_array */ +static inline void *pw_array_add_fixed(struct pw_array *arr, size_t size) +{ + void *p; + + if (SPA_UNLIKELY(arr->alloc < arr->size + size)) + return NULL; + + p = arr->data + arr->size; + arr->size += size; + + return p; +} + +/** Add a pointer to array \memberof pw_array */ +#define pw_array_add_ptr(a,p) \ + *((void**) pw_array_add(a, sizeof(void*))) = (p) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_ARRAY_H__ */ diff --git a/src/pipewire/client.c b/src/pipewire/client.c new file mode 100644 index 00000000..3a509d3b --- /dev/null +++ b/src/pipewire/client.c @@ -0,0 +1,204 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> + +#include "pipewire/pipewire.h" +#include "pipewire/interfaces.h" + +#include "pipewire/client.h" +#include "pipewire/resource.h" + +/** \cond */ +struct impl { + struct pw_client this; +}; +/** \endcond */ + +static void client_unbind_func(void *data) +{ + struct pw_resource *resource = data; + spa_list_remove(&resource->link); +} + +static int +client_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id) +{ + struct pw_client *this = global->object; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, global->type, 0); + if (resource == NULL) + goto no_mem; + + pw_resource_set_implementation(resource, global->object, PW_VERSION_CLIENT, NULL, client_unbind_func); + + pw_log_debug("client %p: bound to %d", global->object, resource->id); + + spa_list_insert(this->resource_list.prev, &resource->link); + + this->info.change_mask = ~0; + pw_client_notify_info(resource, &this->info); + + return SPA_RESULT_OK; + + no_mem: + pw_log_error("can't create client resource"); + pw_core_notify_error(client->core_resource, + client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory"); + return SPA_RESULT_NO_MEMORY; +} + +/** Make a new client object + * + * \param core a \ref pw_core object to register the client with + * \param ucred a ucred structure or NULL when unknown + * \param properties optional client properties, ownership is taken + * \return a newly allocated client object + * + * \memberof pw_client + */ +struct pw_client *pw_client_new(struct pw_core *core, + struct ucred *ucred, + struct pw_properties *properties, + size_t user_data_size) +{ + struct pw_client *this; + struct impl *impl; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + return NULL; + + pw_log_debug("client %p: new", impl); + + this = &impl->this; + this->core = core; + if ((this->ucred_valid = (ucred != NULL))) + this->ucred = *ucred; + this->properties = properties; + + if (user_data_size > 0) + this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void); + + spa_list_init(&this->resource_list); + pw_signal_init(&this->properties_changed); + pw_signal_init(&this->resource_added); + pw_signal_init(&this->resource_impl); + pw_signal_init(&this->resource_removed); + pw_signal_init(&this->busy_changed); + + pw_map_init(&this->objects, 0, 32); + pw_map_init(&this->types, 0, 32); + pw_signal_init(&this->destroy_signal); + + spa_list_insert(core->client_list.prev, &this->link); + + pw_core_add_global(core, NULL, core->type.client, 0, this, client_bind_func, &this->global); + + this->info.id = this->global->id; + this->info.props = this->properties ? &this->properties->dict : NULL; + + return this; +} + +static void destroy_resource(void *object, void *data) +{ + pw_resource_destroy(object); +} + +/** Destroy a client object + * + * \param client the client to destroy + * + * \memberof pw_client + */ +void pw_client_destroy(struct pw_client *client) +{ + struct pw_resource *resource, *tmp; + struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this); + + pw_log_debug("client %p: destroy", client); + pw_signal_emit(&client->destroy_signal, client); + + spa_list_remove(&client->link); + pw_global_destroy(client->global); + + spa_list_for_each_safe(resource, tmp, &client->resource_list, link) + pw_resource_destroy(resource); + + pw_map_for_each(&client->objects, destroy_resource, client); + + pw_log_debug("client %p: free", impl); + + if (client->destroy) + client->destroy(client); + + pw_map_clear(&client->objects); + pw_map_clear(&client->types); + + if (client->properties) + pw_properties_free(client->properties); + + free(impl); +} + +/** Update client properties + * + * \param client the client + * \param dict a \ref spa_dict with properties + * + * Add all properties in \a dict to the client properties. Existing + * properties are overwritten. Items can be removed by setting the value + * to NULL. + * + * \memberof pw_client + */ +void pw_client_update_properties(struct pw_client *client, const struct spa_dict *dict) +{ + struct pw_resource *resource; + + if (client->properties == NULL) { + if (dict) + client->properties = pw_properties_new_dict(dict); + } else { + uint32_t i; + + for (i = 0; i < dict->n_items; i++) + pw_properties_set(client->properties, + dict->items[i].key, dict->items[i].value); + } + + client->info.change_mask = 1 << 0; + client->info.props = client->properties ? &client->properties->dict : NULL; + + pw_signal_emit(&client->properties_changed, client); + + spa_list_for_each(resource, &client->resource_list, link) { + pw_client_notify_info(resource, &client->info); + } +} + +void pw_client_set_busy(struct pw_client *client, bool busy) +{ + if (client->busy != busy) { + client->busy = busy; + pw_signal_emit(&client->busy_changed, client); + } +} diff --git a/src/pipewire/client.h b/src/pipewire/client.h new file mode 100644 index 00000000..9de1388d --- /dev/null +++ b/src/pipewire/client.h @@ -0,0 +1,142 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_CLIENT_H__ +#define __PIPEWIRE_CLIENT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __USE_GNU +#define __USE_GNU +#endif + +#include <sys/socket.h> + +#include <pipewire/core.h> +#include <pipewire/introspect.h> +#include <pipewire/properties.h> +#include <pipewire/sig.h> +#include <pipewire/resource.h> + +/** \page page_client Client + * + * \section sec_page_client_overview Overview + * + * The \ref pw_client object is created by a protocol implementation when + * a new client connects. + * + * The client is used to keep track of all resources belonging to one + * connection with the PipeWire server. + * + * \section sec_page_client_credentials Credentials + * + * The client object will have its credentials filled in by the protocol. + * This information is used to check if a resource or action is available + * for this client. See also \ref page_access + * + * \section sec_page_client_types Types + * + * The client and server maintain a mapping between the client and server + * types. All type ids that are in messages exchanged between the client + * and server will automatically be remapped. See also \ref page_types. + * + * \section sec_page_client_resources Resources + * + * When a client binds to core global object, a resource is made for this + * binding and a unique id is assigned to the resources. The client and + * server will use this id as the destination when exchanging messages. + * See also \ref page_resource + */ + +/** \class pw_client + * + * \brief PipeWire client object class. + * + * The client object represents a client connection with the PipeWire + * server. + * + * Each client has its own list of resources it is bound to along with + * a mapping between the client types and server types. + */ +struct pw_client { + struct pw_core *core; /**< core object */ + struct spa_list link; /**< link in core object client list */ + struct pw_global *global; /**< global object created for this client */ + + struct pw_properties *properties; /**< Client properties */ + /** Emited when the properties changed */ + PW_SIGNAL(properties_changed, (struct pw_listener *listener, struct pw_client *client)); + + struct pw_client_info info; /**< client info */ + bool ucred_valid; /**< if the ucred member is valid */ + struct ucred ucred; /**< ucred information */ + + struct pw_resource *core_resource; /**< core resource object */ + + struct pw_map objects; /**< list of resource objects */ + uint32_t n_types; /**< number of client types */ + struct pw_map types; /**< map of client types */ + + struct spa_list resource_list; /**< The list of resources of this client */ + /** Emited when a resource is added */ + PW_SIGNAL(resource_added, (struct pw_listener *listener, + struct pw_client *client, struct pw_resource *resource)); + /** Emited when a resource implementation is set */ + PW_SIGNAL(resource_impl, (struct pw_listener *listener, + struct pw_client *client, struct pw_resource *resource)); + /** Emited when a resource is removed */ + PW_SIGNAL(resource_removed, (struct pw_listener *listener, + struct pw_client *client, struct pw_resource *resource)); + + bool busy; + /** Emited when the client starts/stops an async operation that should + * block/resume all methods for this client */ + PW_SIGNAL(busy_changed, (struct pw_listener *listener, struct pw_client *client)); + + /** Emited when the client is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_client *client)); + + struct pw_protocol *protocol; /**< protocol in use */ + void *protocol_private; /**< private data for the protocol */ + + void *user_data; /**< extra user data */ + pw_destroy_t destroy; /**< function to clean up the object */ +}; + +struct pw_client * +pw_client_new(struct pw_core *core, + struct ucred *ucred, + struct pw_properties *properties, + size_t user_data_size); + +void +pw_client_destroy(struct pw_client *client); + +void +pw_client_update_properties(struct pw_client *client, const struct spa_dict *dict); + +void pw_client_set_busy(struct pw_client *client, bool busy); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_CLIENT_H__ */ diff --git a/src/pipewire/command.c b/src/pipewire/command.c new file mode 100644 index 00000000..a3b6d835 --- /dev/null +++ b/src/pipewire/command.c @@ -0,0 +1,160 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> + +#include <pipewire/pipewire.h> +#include <pipewire/utils.h> +#include <pipewire/module.h> + +#include "command.h" + +/** \cond */ +typedef bool(*pw_command_func_t) (struct pw_command *command, struct pw_core *core, char **err); + +static bool execute_command_module_load(struct pw_command *command, + struct pw_core *core, char **err); + +typedef struct pw_command *(*pw_command_parse_func_t) (const char *line, char **err); + +static struct pw_command *parse_command_module_load(const char *line, char **err); + +struct impl { + struct pw_command this; + + pw_command_func_t func; + char **args; + int n_args; +}; + +struct command_parse { + const char *name; + pw_command_parse_func_t func; +}; + +static const struct command_parse parsers[] = { + {"load-module", parse_command_module_load}, + {NULL, NULL} +}; + +static const char whitespace[] = " \t"; +/** \endcond */ + +static struct pw_command *parse_command_module_load(const char *line, char **err) +{ + struct impl *impl; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + goto no_mem; + + impl->func = execute_command_module_load; + impl->args = pw_split_strv(line, whitespace, 3, &impl->n_args); + + if (impl->args[1] == NULL) + goto no_module; + + impl->this.name = impl->args[0]; + + return &impl->this; + + no_module: + asprintf(err, "%s requires a module name", impl->args[0]); + pw_free_strv(impl->args); + return NULL; + no_mem: + asprintf(err, "no memory"); + return NULL; +} + +static bool +execute_command_module_load(struct pw_command *command, struct pw_core *core, char **err) +{ + struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this); + + return pw_module_load(core, impl->args[1], impl->args[2], err) != NULL; +} + +/** Free command + * + * \param command a command to free + * + * Free all resources assicated with \a command. + * + * \memberof pw_command + */ +void pw_command_free(struct pw_command *command) +{ + struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this); + + spa_list_remove(&command->link); + pw_free_strv(impl->args); + free(impl); +} + +/** Parses a command line + * \param line command line to parse + * \param[out] err Return location for an error + * \return The command or NULL when \a err is set. + * + * Parses a command line, \a line, and return the parsed command. + * A command can later be executed with \ref pw_command_run() + * + * \memberof pw_command + */ +struct pw_command *pw_command_parse(const char *line, char **err) +{ + struct pw_command *command = NULL; + const struct command_parse *parse; + char *name; + size_t len; + + len = strcspn(line, whitespace); + + name = strndup(line, len); + + for (parse = parsers; parse->name != NULL; parse++) { + if (strcmp(name, parse->name) == 0) { + command = parse->func(line, err); + goto out; + } + } + + asprintf(err, "Command \"%s\" does not exist", name); + out: + free(name); + return command; +} + +/** Run a command + * + * \param command: A \ref pw_command + * \param core: A \ref pw_core + * \param err: Return location for an error string, or NULL + * \return true if \a command was executed successfully, false otherwise. + * + * \memberof pw_command + */ +bool pw_command_run(struct pw_command *command, struct pw_core *core, char **err) +{ + struct impl *impl = SPA_CONTAINER_OF(command, struct impl, this); + + return impl->func(command, core, err); +} diff --git a/src/pipewire/command.h b/src/pipewire/command.h new file mode 100644 index 00000000..d64f3c69 --- /dev/null +++ b/src/pipewire/command.h @@ -0,0 +1,54 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_COMMAND_H__ +#define __PIPEWIRE_COMMAND_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/core.h> + +/** \class pw_command + * + * A configuration command + */ +struct pw_command { + struct spa_list link; /**< link in list of commands */ + + const char *name; /**< command name */ +}; + + +struct pw_command * +pw_command_parse(const char *line, char **err); + +void +pw_command_free(struct pw_command *command); + +bool +pw_command_run(struct pw_command *command, struct pw_core *core, char **err); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_COMMAND_H__ */ diff --git a/src/pipewire/core.c b/src/pipewire/core.c new file mode 100644 index 00000000..c654fa6f --- /dev/null +++ b/src/pipewire/core.c @@ -0,0 +1,716 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include <time.h> + +#include <spa/lib/debug.h> +#include <spa/format-utils.h> + +#include <pipewire/pipewire.h> +#include <pipewire/interfaces.h> +#include <pipewire/core.h> +#include <pipewire/data-loop.h> + +/** \cond */ +struct global_impl { + struct pw_global this; + pw_bind_func_t bind; +}; + +struct impl { + struct pw_core this; + + struct spa_support support[4]; + struct pw_data_loop *data_loop; +}; + +/** \endcond */ + +static bool pw_global_is_visible(struct pw_global *global, + struct pw_client *client) +{ + struct pw_core *core = client->core; + + return (core->global_filter == NULL || + core->global_filter(global, client, core->global_filter_data)); +} + +static void registry_bind(void *object, uint32_t id, uint32_t version, uint32_t new_id) +{ + struct pw_resource *resource = object; + struct pw_client *client = resource->client; + struct pw_core *core = resource->core; + struct pw_global *global; + + spa_list_for_each(global, &core->global_list, link) { + if (global->id == id) + break; + } + if (&global->link == &core->global_list) + goto no_id; + + if (!pw_global_is_visible(global, client)) + goto no_id; + + pw_log_debug("global %p: bind object id %d to %d", global, id, new_id); + pw_global_bind(global, client, version, new_id); + + return; + + no_id: + pw_log_debug("registry %p: no global with id %u to bind to %u", resource, id, new_id); + /* unmark the new_id the map, the client does not yet know about the failed + * bind and will choose the next id, which we would refuse when we don't mark + * new_id as 'used and freed' */ + pw_map_insert_at(&client->objects, new_id, NULL); + pw_core_notify_remove_id(client->core_resource, new_id); + return; +} + +static struct pw_registry_methods registry_methods = { + ®istry_bind +}; + +static void destroy_registry_resource(void *object) +{ + struct pw_resource *resource = object; + spa_list_remove(&resource->link); +} + +static void core_client_update(void *object, const struct spa_dict *props) +{ + struct pw_resource *resource = object; + + pw_client_update_properties(resource->client, props); +} + +static void core_sync(void *object, uint32_t seq) +{ + struct pw_resource *resource = object; + + pw_core_notify_done(resource, seq); +} + +static void core_get_registry(void *object, uint32_t new_id) +{ + struct pw_resource *resource = object; + struct pw_client *client = resource->client; + struct pw_core *this = resource->core; + struct pw_global *global; + struct pw_resource *registry_resource; + + registry_resource = pw_resource_new(client, + new_id, + this->type.registry, + 0); + if (registry_resource == NULL) + goto no_mem; + + pw_resource_set_implementation(registry_resource, + this, + PW_VERSION_REGISTRY, + ®istry_methods, + destroy_registry_resource); + + spa_list_insert(this->registry_resource_list.prev, ®istry_resource->link); + + spa_list_for_each(global, &this->global_list, link) { + if (pw_global_is_visible(global, client)) + pw_registry_notify_global(registry_resource, + global->id, + spa_type_map_get_type(this->type.map, + global->type), + global->version); + } + + return; + + no_mem: + pw_log_error("can't create registry resource"); + pw_core_notify_error(client->core_resource, + resource->id, SPA_RESULT_NO_MEMORY, "no memory"); +} + +static void +core_create_node(void *object, + const char *factory_name, + const char *name, + const struct spa_dict *props, + uint32_t new_id) +{ + struct pw_resource *resource = object; + struct pw_resource *node_resource; + struct pw_client *client = resource->client; + struct pw_node_factory *factory; + struct pw_properties *properties; + + factory = pw_core_find_node_factory(client->core, factory_name); + if (factory == NULL) + goto no_factory; + + node_resource = pw_resource_new(client, + new_id, + factory->type, + 0); + if (node_resource == NULL) + goto no_resource; + + if (props) { + properties = pw_properties_new_dict(props); + if (properties == NULL) + goto no_properties; + } else + properties = NULL; + + /* error will be posted */ + pw_node_factory_create_node(factory, node_resource, name, properties); + properties = NULL; + + done: + return; + + no_factory: + pw_log_error("can't find node factory"); + pw_core_notify_error(client->core_resource, + resource->id, SPA_RESULT_INVALID_ARGUMENTS, "unknown factory name"); + goto done; + + no_resource: + pw_log_error("can't create resource"); + goto no_mem; + no_properties: + pw_log_error("can't create properties"); + pw_resource_destroy(node_resource); + goto no_mem; + no_mem: + pw_core_notify_error(client->core_resource, + resource->id, SPA_RESULT_NO_MEMORY, "no memory"); + goto done; +} + +static void +core_create_link(void *object, + uint32_t output_node_id, + uint32_t output_port_id, + uint32_t input_node_id, + uint32_t input_port_id, + const struct spa_format *filter, + const struct spa_dict *props, + uint32_t new_id) +{ + struct pw_resource *resource = object; + struct pw_client *client = resource->client; + + pw_log_error("can't create link"); + pw_core_notify_error(client->core_resource, + resource->id, SPA_RESULT_NOT_IMPLEMENTED, "not implemented"); +} + +static void core_update_types(void *object, uint32_t first_id, uint32_t n_types, const char **types) +{ + struct pw_resource *resource = object; + struct pw_core *this = resource->core; + struct pw_client *client = resource->client; + int i; + + for (i = 0; i < n_types; i++, first_id++) { + uint32_t this_id = spa_type_map_get_id(this->type.map, types[i]); + if (!pw_map_insert_at(&client->types, first_id, PW_MAP_ID_TO_PTR(this_id))) + pw_log_error("can't add type for client"); + } +} + +static const struct pw_core_methods core_methods = { + &core_update_types, + &core_sync, + &core_get_registry, + &core_client_update, + &core_create_node, + &core_create_link +}; + +static void core_unbind_func(void *data) +{ + struct pw_resource *resource = data; + resource->client->core_resource = NULL; + spa_list_remove(&resource->link); +} + +static int +core_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id) +{ + struct pw_core *this = global->object; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, global->type, 0); + if (resource == NULL) + goto no_mem; + + pw_resource_set_implementation(resource, global->object, + PW_VERSION_CORE, &core_methods, core_unbind_func); + + spa_list_insert(this->resource_list.prev, &resource->link); + client->core_resource = resource; + + pw_log_debug("core %p: bound to %d", global->object, resource->id); + + this->info.change_mask = PW_CORE_CHANGE_MASK_ALL; + pw_core_notify_info(resource, &this->info); + + return SPA_RESULT_OK; + + no_mem: + pw_log_error("can't create core resource"); + return SPA_RESULT_NO_MEMORY; +} + +/** Create a new core object + * + * \param main_loop the main loop to use + * \param properties extra properties for the core, ownership it taken + * \return a newly allocated core object + * + * \memberof pw_core + */ +struct pw_core *pw_core_new(struct pw_loop *main_loop, struct pw_properties *properties) +{ + struct impl *impl; + struct pw_core *this; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return NULL; + + this = &impl->this; + impl->data_loop = pw_data_loop_new(); + if (impl->data_loop == NULL) + goto no_data_loop; + + this->data_loop = impl->data_loop->loop; + this->main_loop = main_loop; + this->properties = properties; + + pw_type_init(&this->type); + pw_map_init(&this->objects, 128, 32); + + spa_graph_init(&this->rt.graph); + spa_graph_scheduler_init(&this->rt.sched, &this->rt.graph); + + spa_debug_set_type_map(this->type.map); + + impl->support[0] = SPA_SUPPORT_INIT(SPA_TYPE__TypeMap, this->type.map); + impl->support[1] = SPA_SUPPORT_INIT(SPA_TYPE_LOOP__DataLoop, this->data_loop->loop); + impl->support[2] = SPA_SUPPORT_INIT(SPA_TYPE_LOOP__MainLoop, this->main_loop->loop); + impl->support[3] = SPA_SUPPORT_INIT(SPA_TYPE__Log, pw_log_get()); + this->support = impl->support; + this->n_support = 4; + + pw_data_loop_start(impl->data_loop); + + spa_list_init(&this->remote_list); + spa_list_init(&this->resource_list); + spa_list_init(&this->registry_resource_list); + spa_list_init(&this->global_list); + spa_list_init(&this->client_list); + spa_list_init(&this->node_list); + spa_list_init(&this->node_factory_list); + spa_list_init(&this->link_list); + pw_signal_init(&this->info_changed); + pw_signal_init(&this->destroy_signal); + pw_signal_init(&this->global_added); + pw_signal_init(&this->global_removed); + + pw_core_add_global(this, NULL, this->type.core, 0, this, core_bind_func, &this->global); + + this->info.id = this->global->id; + this->info.change_mask = 0; + this->info.user_name = pw_get_user_name(); + this->info.host_name = pw_get_host_name(); + this->info.version = "0"; + this->info.name = "pipewire-0"; + srandom(time(NULL)); + this->info.cookie = random(); + this->info.props = this->properties ? &this->properties->dict : NULL; + + return this; + + no_data_loop: + free(impl); + return NULL; +} + +/** Destroy a core object + * + * \param core a core to destroy + * + * \memberof pw_core + */ +void pw_core_destroy(struct pw_core *core) +{ + struct impl *impl = SPA_CONTAINER_OF(core, struct impl, this); + + pw_log_debug("core %p: destroy", core); + pw_signal_emit(&core->destroy_signal, core); + + pw_data_loop_destroy(impl->data_loop); + + pw_map_clear(&core->objects); + + pw_log_debug("core %p: free", core); + free(impl); +} + +/** Create and add a new global to the core + * + * \param core a core + * \param owner an optional owner of the global + * \param type the type of the global + * \param version the version + * \param object the associated object + * \param bind a function to bind to this global + * \param[out] global a result global + * \return true on success + * + * \memberof pw_core + */ +bool +pw_core_add_global(struct pw_core *core, + struct pw_resource *owner, + uint32_t type, + uint32_t version, + void *object, + pw_bind_func_t bind, + struct pw_global **global) +{ + struct global_impl *impl; + struct pw_global *this; + struct pw_resource *registry; + const char *type_name; + + impl = calloc(1, sizeof(struct global_impl)); + if (impl == NULL) + return false; + + this = &impl->this; + impl->bind = bind; + + this->core = core; + this->owner = owner; + this->type = type; + this->version = version; + this->object = object; + *global = this; + + pw_signal_init(&this->destroy_signal); + + this->id = pw_map_insert_new(&core->objects, this); + + spa_list_insert(core->global_list.prev, &this->link); + pw_signal_emit(&core->global_added, core, this); + + type_name = spa_type_map_get_type(core->type.map, this->type); + pw_log_debug("global %p: new %u %s, owner %p", this, this->id, type_name, owner); + + spa_list_for_each(registry, &core->registry_resource_list, link) + if (pw_global_is_visible(this, registry->client)) + pw_registry_notify_global(registry, this->id, type_name, this->version); + + return true; +} + +/** Bind to a global + * + * \param global the global to bind to + * \param client the client that binds + * \param version the version + * \param id the id + * + * Let \a client bind to \a global with the given version and id. + * After binding, the client and the global object will be able to + * exchange messages. + * + * \memberof pw_global + */ +int +pw_global_bind(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id) +{ + int res; + struct global_impl *impl = SPA_CONTAINER_OF(global, struct global_impl, this); + + if (impl->bind) { + res = impl->bind(global, client, version, id); + } else { + res = SPA_RESULT_NOT_IMPLEMENTED; + pw_core_notify_error(client->core_resource, + client->core_resource->id, res, "can't bind object id %d", id); + } + return res; +} + +/** Destroy a global + * + * \param global a global to destroy + * + * \memberof pw_global + */ +void pw_global_destroy(struct pw_global *global) +{ + struct pw_core *core = global->core; + struct pw_resource *registry; + + pw_log_debug("global %p: destroy %u", global, global->id); + pw_signal_emit(&global->destroy_signal, global); + + spa_list_for_each(registry, &core->registry_resource_list, link) + if (pw_global_is_visible(global, registry->client)) + pw_registry_notify_global_remove(registry, global->id); + + pw_map_remove(&core->objects, global->id); + + spa_list_remove(&global->link); + pw_signal_emit(&core->global_removed, core, global); + + pw_log_debug("global %p: free", global); + free(global); +} + +/** Update core properties + * + * \param core a core + * \param dict properties to update + * + * Update the core object with the given properties + * + * \memberof pw_core + */ +void pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict) +{ + struct pw_resource *resource; + + if (core->properties == NULL) { + if (dict) + core->properties = pw_properties_new_dict(dict); + } else if (dict != &core->properties->dict) { + uint32_t i; + + for (i = 0; i < dict->n_items; i++) + pw_properties_set(core->properties, + dict->items[i].key, dict->items[i].value); + } + + core->info.change_mask = PW_CORE_CHANGE_MASK_PROPS; + core->info.props = core->properties ? &core->properties->dict : NULL; + + pw_signal_emit(&core->info_changed, core); + + spa_list_for_each(resource, &core->resource_list, link) { + pw_core_notify_info(resource, &core->info); + } +} + +/** Find a port to link with + * + * \param core a core + * \param other_port a port to find a link with + * \param id the id of a port or SPA_ID_INVALID + * \param props extra properties + * \param n_format_filters number of filters + * \param format_filters array of format filters + * \param[out] error an error when something is wrong + * \return a port that can be used to link to \a otherport or NULL on error + * + * \memberof pw_core + */ +struct pw_port *pw_core_find_port(struct pw_core *core, + struct pw_port *other_port, + uint32_t id, + struct pw_properties *props, + uint32_t n_format_filters, + struct spa_format **format_filters, + char **error) +{ + struct pw_port *best = NULL; + bool have_id; + struct pw_node *n; + + have_id = id != SPA_ID_INVALID; + + pw_log_debug("id \"%u\", %d", id, have_id); + + spa_list_for_each(n, &core->node_list, link) { + if (n->global == NULL) + continue; + + pw_log_debug("node id \"%d\"", n->global->id); + + if (have_id) { + if (n->global->id == id) { + pw_log_debug("id \"%u\" matches node %p", id, n); + + best = + pw_node_get_free_port(n, + pw_direction_reverse(other_port-> + direction)); + if (best) + break; + } + } else { + struct pw_port *p, *pin, *pout; + + p = pw_node_get_free_port(n, pw_direction_reverse(other_port->direction)); + if (p == NULL) + continue; + + if (p->direction == PW_DIRECTION_OUTPUT) { + pin = other_port; + pout = p; + } else { + pin = p; + pout = other_port; + } + + if (pw_core_find_format(core, + pout, + pin, + props, + n_format_filters, format_filters, error) == NULL) + continue; + + best = p; + } + } + if (best == NULL) { + asprintf(error, "No matching Node found"); + } + return best; +} + +/** Find a common format between two ports + * + * \param core a core object + * \param output an output port + * \param input an input port + * \param props extra properties + * \param n_format_filters number of format filters + * \param format_filters array of format filters + * \param[out] error an error when something is wrong + * \return a common format of NULL on error + * + * Find a common format between the given ports. The format will + * be restricted to a subset given with the format filters. + * + * \memberof pw_core + */ +struct spa_format *pw_core_find_format(struct pw_core *core, + struct pw_port *output, + struct pw_port *input, + struct pw_properties *props, + uint32_t n_format_filters, + struct spa_format **format_filters, + char **error) +{ + uint32_t out_state, in_state; + int res; + struct spa_format *filter = NULL, *format; + uint32_t iidx = 0, oidx = 0; + + out_state = output->state; + in_state = input->state; + + pw_log_debug("core %p: finding best format %d %d", core, out_state, in_state); + + /* when a port is configured but the node is idle, we can reconfigure with a different format */ + if (out_state > PW_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE) + out_state = PW_PORT_STATE_CONFIGURE; + if (in_state > PW_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE) + in_state = PW_PORT_STATE_CONFIGURE; + + if (in_state == PW_PORT_STATE_CONFIGURE && out_state > PW_PORT_STATE_CONFIGURE) { + /* only input needs format */ + if ((res = pw_port_get_format(output, (const struct spa_format **) &format)) < 0) { + asprintf(error, "error get output format: %d", res); + goto error; + } + } else if (out_state == PW_PORT_STATE_CONFIGURE && in_state > PW_PORT_STATE_CONFIGURE) { + /* only output needs format */ + if ((res = pw_port_get_format(input, (const struct spa_format **) &format)) < 0) { + asprintf(error, "error get input format: %d", res); + goto error; + } + } else if (in_state == PW_PORT_STATE_CONFIGURE && out_state == PW_PORT_STATE_CONFIGURE) { + again: + /* both ports need a format */ + pw_log_debug("core %p: finding best format", core); + if ((res = pw_port_enum_formats(input, &filter, NULL, iidx)) < 0) { + if (res == SPA_RESULT_ENUM_END && iidx != 0) { + asprintf(error, "error input enum formats: %d", res); + goto error; + } + } + pw_log_debug("Try filter: %p", filter); + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_format(filter); + + if ((res = pw_port_enum_formats(output, &format, filter, oidx)) < 0) { + if (res == SPA_RESULT_ENUM_END) { + oidx = 0; + iidx++; + goto again; + } + asprintf(error, "error output enum formats: %d", res); + goto error; + } + pw_log_debug("Got filtered:"); + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_format(format); + + spa_format_fixate(format); + } else { + asprintf(error, "error node state"); + goto error; + } + if (format == NULL) { + asprintf(error, "error get format"); + goto error; + } + return format; + + error: + return NULL; +} + +/** Find a node by name + * + * \param core the core object + * \param name the name of the node to find + * + * Find in the list of nodes registered in \a core for one with + * the given \a name. + * + * \memberof pw_core + */ +struct pw_node_factory *pw_core_find_node_factory(struct pw_core *core, const char *name) +{ + struct pw_node_factory *factory; + + spa_list_for_each(factory, &core->node_factory_list, link) { + if (strcmp(factory->name, name) == 0) + return factory; + } + return NULL; +} diff --git a/src/pipewire/core.h b/src/pipewire/core.h new file mode 100644 index 00000000..0cccc764 --- /dev/null +++ b/src/pipewire/core.h @@ -0,0 +1,243 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_CORE_H__ +#define __PIPEWIRE_CORE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/log.h> +#include <spa/graph-scheduler3.h> + +struct pw_global; + +#include <pipewire/type.h> +#include <pipewire/interfaces.h> + +#include <pipewire/main-loop.h> +#include <pipewire/data-loop.h> +#include <pipewire/resource.h> +#include <pipewire/node.h> +#include <pipewire/link.h> +#include <pipewire/node-factory.h> + +/** \page page_server_api Server API + * + * \section page_server_overview Overview + * + * \subpage page_core + * + * \subpage page_registry + * + * \subpage page_global + * + * \subpage page_client + * + * \subpage page_resource + * + * \subpage page_node + * + * \subpage page_port + * + * \subpage page_link + */ + +/** \page page_core Core + * + * \section page_core_overview Overview + * + * The core object is a singleton object that manages the state and + * resources of the PipeWire server. + */ +/** \page page_registry Registry + * + * \section page_registry_overview Overview + * + * The registry object is a singleton object that keeps track of + * global objects on the PipeWire server. See also \ref page_global. + * + * Global objects typically represent an actual object in the + * server (for example, a module or node) or they are singleton + * objects such as the core. + * + * When a client creates a registry object, the registry object + * will emit a global event for each global currently in the + * registry. Globals come and go as a result of device hotplugs or + * reconfiguration or other events, and the registry will send out + * global and global_remove events to keep the client up to date + * with the changes. To mark the end of the initial burst of + * events, the client can use the pw_core.sync methosd immediately + * after calling pw_core.get_registry. + * + * A client can bind to a global object by using the bind + * request. This creates a client-side proxy that lets the object + * emit events to the client and lets the client invoke methods on + * the object. + */ +typedef int (*pw_bind_func_t) (struct pw_global *global, + struct pw_client *client, uint32_t version, uint32_t id); + +typedef bool (*pw_global_filter_func_t) (struct pw_global *global, + struct pw_client *client, void *data); + +/** \page page_global Global + * + * Global objects represent resources that are available on the server and + * accessible to clients. + * Globals come and go when devices or other resources become available for + * clients. + * + * The client receives a list of globals when it binds to the registry + * object. See \ref page_registry. + * + * A client can bind to a global to send methods or receive events from + * the global. + */ +/** \class pw_global + * + * \brief A global object visible to all clients + * + * A global object is visible to all clients and represents a resource + * that can be used or inspected. + * + * See \ref page_server_api + */ +struct pw_global { + struct pw_core *core; /**< the core */ + struct pw_resource *owner; /**< the owner of this object, NULL when the + * PipeWire server is the owner */ + + struct spa_list link; /**< link in core list of globals */ + uint32_t id; /**< server id of the object */ + uint32_t type; /**< type of the object */ + uint32_t version; /**< version of the object */ + void *object; /**< object associated with the global */ + + /** Emited when the global is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_global *global)); +}; + +/** \class pw_core + * + * \brief the core PipeWire object + * + * The server core object manages all resources available on the + * server. + * + * See \ref page_server_api + */ +struct pw_core { + struct pw_global *global; /**< the global of the core */ + + struct pw_core_info info; /**< info about the core */ + /** Emited when the core info is updated */ + PW_SIGNAL(info_changed, (struct pw_listener *listener, struct pw_core *core)); + + struct pw_properties *properties; /**< properties of the core */ + + struct pw_type type; /**< type map and common types */ + + pw_global_filter_func_t global_filter; + void *global_filter_data; + + struct pw_map objects; /**< map of known objects */ + + struct spa_list remote_list; /**< list of remote connections */ + struct spa_list resource_list; /**< list of core resources */ + struct spa_list registry_resource_list; /**< list of registry resources */ + struct spa_list global_list; /**< list of globals */ + struct spa_list client_list; /**< list of clients */ + struct spa_list node_list; /**< list of nodes */ + struct spa_list node_factory_list; /**< list of node factories */ + struct spa_list link_list; /**< list of links */ + + struct pw_loop *main_loop; /**< main loop for control */ + struct pw_loop *data_loop; /**< data loop for data passing */ + + struct spa_support *support; /**< support for spa plugins */ + uint32_t n_support; /**< number of support items */ + + /** Emited when the core is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_core *core)); + + /** Emited when a global is added */ + PW_SIGNAL(global_added, (struct pw_listener *listener, + struct pw_core *core, struct pw_global *global)); + /** Emited when a global is removed */ + PW_SIGNAL(global_removed, (struct pw_listener *listener, + struct pw_core *core, struct pw_global *global)); + + struct { + struct spa_graph_scheduler sched; + struct spa_graph graph; + } rt; +}; + +struct pw_core * +pw_core_new(struct pw_loop *main_loop, struct pw_properties *props); + +void +pw_core_destroy(struct pw_core *core); + +void +pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict); + +bool +pw_core_add_global(struct pw_core *core, + struct pw_resource *owner, + uint32_t type, + uint32_t version, + void *object, pw_bind_func_t bind, + struct pw_global **global); + +int +pw_global_bind(struct pw_global *global, + struct pw_client *client, uint32_t version, uint32_t id); + +void +pw_global_destroy(struct pw_global *global); + +struct spa_format * +pw_core_find_format(struct pw_core *core, + struct pw_port *output, + struct pw_port *input, + struct pw_properties *props, + uint32_t n_format_filters, + struct spa_format **format_filters, + char **error); + +struct pw_port * +pw_core_find_port(struct pw_core *core, + struct pw_port *other_port, + uint32_t id, + struct pw_properties *props, + uint32_t n_format_filters, + struct spa_format **format_filters, + char **error); + +struct pw_node_factory * +pw_core_find_node_factory(struct pw_core *core, const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_CORE_H__ */ diff --git a/src/pipewire/data-loop.c b/src/pipewire/data-loop.c new file mode 100644 index 00000000..83da38a6 --- /dev/null +++ b/src/pipewire/data-loop.c @@ -0,0 +1,214 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <pthread.h> +#include <errno.h> +#include <sys/resource.h> + +#include "pipewire/log.h" +#include "pipewire/rtkit.h" +#include "pipewire/data-loop.h" + +/** \cond */ +struct impl { + struct pw_data_loop this; + + struct spa_source *event; + + bool running; + pthread_t thread; +}; +/** \endcond */ + +static void make_realtime(struct pw_data_loop *this) +{ + struct sched_param sp; + struct pw_rtkit_bus *system_bus; + struct rlimit rl; + int r, rtprio; + long long rttime; + + rtprio = 20; + rttime = 20000; + + spa_zero(sp); + sp.sched_priority = rtprio; + + if (pthread_setschedparam(pthread_self(), SCHED_OTHER | SCHED_RESET_ON_FORK, &sp) == 0) { + pw_log_debug("SCHED_OTHER|SCHED_RESET_ON_FORK worked."); + return; + } + system_bus = pw_rtkit_bus_get_system(); + + rl.rlim_cur = rl.rlim_max = rttime; + if ((r = setrlimit(RLIMIT_RTTIME, &rl)) < 0) + pw_log_debug("setrlimit() failed: %s", strerror(errno)); + + if (rttime >= 0) { + r = getrlimit(RLIMIT_RTTIME, &rl); + if (r >= 0 && (long long) rl.rlim_max > rttime) { + pw_log_debug("Clamping rlimit-rttime to %lld for RealtimeKit", rttime); + rl.rlim_cur = rl.rlim_max = rttime; + + if ((r = setrlimit(RLIMIT_RTTIME, &rl)) < 0) + pw_log_debug("setrlimit() failed: %s", strerror(errno)); + } + } + + if ((r = pw_rtkit_make_realtime(system_bus, 0, rtprio)) < 0) { + pw_log_debug("could not make thread realtime: %s", strerror(r)); + } else { + pw_log_debug("thread made realtime"); + } + pw_rtkit_bus_free(system_bus); +} + +static void *do_loop(void *user_data) +{ + struct impl *impl = user_data; + struct pw_data_loop *this = &impl->this; + int res; + + make_realtime(this); + + pw_log_debug("data-loop %p: enter thread", this); + pw_loop_enter(impl->this.loop); + + while (impl->running) { + if ((res = pw_loop_iterate(this->loop, -1)) < 0) + pw_log_warn("data-loop %p: iterate error %d", this, res); + } + pw_log_debug("data-loop %p: leave thread", this); + pw_loop_leave(impl->this.loop); + + return NULL; +} + + +static void do_stop(struct spa_loop_utils *utils, struct spa_source *source, void *data) +{ + struct impl *impl = data; + impl->running = false; +} + +/** Create a new \ref pw_data_loop. + * \return a newly allocated data loop + * + * \memberof pw_data_loop + */ +struct pw_data_loop *pw_data_loop_new(void) +{ + struct impl *impl; + struct pw_data_loop *this; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return NULL; + + pw_log_debug("data-loop %p: new", impl); + + this = &impl->this; + this->loop = pw_loop_new(); + if (this->loop == NULL) + goto no_loop; + + pw_signal_init(&this->destroy_signal); + + impl->event = pw_loop_add_event(this->loop, do_stop, impl); + return this; + + no_loop: + free(impl); + return NULL; +} + +/** Destroy a data loop + * \param loop the data loop to destroy + * \memberof pw_data_loop + */ +void pw_data_loop_destroy(struct pw_data_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + pw_log_debug("data-loop %p: destroy", impl); + pw_signal_emit(&loop->destroy_signal, loop); + + pw_data_loop_stop(loop); + + pw_loop_destroy_source(loop->loop, impl->event); + pw_loop_destroy(loop->loop); + free(impl); +} + +/** Start a data loop + * \param loop the data loop to start + * \return 0 if ok, -1 on error + * + * This will start the realtime thread that manages the loop. + * + * \memberof pw_data_loop + */ +int pw_data_loop_start(struct pw_data_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + if (!impl->running) { + int err; + + impl->running = true; + if ((err = pthread_create(&impl->thread, NULL, do_loop, impl)) != 0) { + pw_log_warn("data-loop %p: can't create thread: %s", impl, strerror(err)); + impl->running = false; + return SPA_RESULT_ERROR; + } + } + return SPA_RESULT_OK; +} + +/** Stop a data loop + * \param loop the data loop to Stop + * \return \ref SPA_RESULT_OK + * + * This will stop and join the realtime thread that manages the loop. + * + * \memberof pw_data_loop + */ +int pw_data_loop_stop(struct pw_data_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + if (impl->running) { + pw_loop_signal_event(impl->this.loop, impl->event); + + pthread_join(impl->thread, NULL); + } + return SPA_RESULT_OK; +} + +/** Check if we are inside the data loop + * \param loop the data loop to check + * \return true is the current thread is the data loop thread + * + * \memberof pw_data_loop + */ +bool pw_data_loop_in_thread(struct pw_data_loop * loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + return pthread_equal(impl->thread, pthread_self()); +} diff --git a/src/pipewire/data-loop.h b/src/pipewire/data-loop.h new file mode 100644 index 00000000..64a4c241 --- /dev/null +++ b/src/pipewire/data-loop.h @@ -0,0 +1,59 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_DATA_LOOP_H__ +#define __PIPEWIRE_DATA_LOOP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/loop.h> + +/** \class pw_data_loop + * + * PipeWire rt-loop object. + */ +struct pw_data_loop { + struct pw_loop *loop; /**< wrapped loop object */ + + /** Emited when the data loop is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_data_loop *loop)); +}; + +struct pw_data_loop * +pw_data_loop_new(void); + +void +pw_data_loop_destroy(struct pw_data_loop *loop); + +int +pw_data_loop_start(struct pw_data_loop *loop); + +int +pw_data_loop_stop(struct pw_data_loop *loop); + +bool +pw_data_loop_in_thread(struct pw_data_loop *loop); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_DATA_LOOP_H__ */ diff --git a/src/pipewire/interfaces.h b/src/pipewire/interfaces.h new file mode 100644 index 00000000..76aa767b --- /dev/null +++ b/src/pipewire/interfaces.h @@ -0,0 +1,347 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_INTERFACES_H__ +#define __PIPEWIRE_INTERFACES_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/defs.h> +#include <spa/props.h> +#include <spa/format.h> +#include <spa/param-alloc.h> +#include <spa/node.h> + +#include <pipewire/introspect.h> + +/** + * \page page_pipewire_protocol The PipeWire protocol + * \section page_ifaces_pipewire Interfaces + * - \subpage page_iface_pw_core - core global object + * - \subpage page_iface_pw_registry - global registry object + */ + +/** + * \page page_iface_pw_core pw_core + * \section page_iface_pw_core_desc Description + * + * The core global object. This is a special singleton object. It + * is used for internal Wayland protocol features. + * \section page_iface_pw_core API + */ + +#define PW_VERSION_CORE 0 + +#define PW_CORE_METHOD_UPDATE_TYPES 0 +#define PW_CORE_METHOD_SYNC 1 +#define PW_CORE_METHOD_GET_REGISTRY 2 +#define PW_CORE_METHOD_CLIENT_UPDATE 3 +#define PW_CORE_METHOD_CREATE_NODE 4 +#define PW_CORE_METHOD_CREATE_LINK 5 +#define PW_CORE_METHOD_NUM 6 + +/** + * \struct pw_core_methods + * \brief Core methods + * + * The core global object. This is a singleton object used for + * creating new objects in the PipeWire server. It is also used + * for internal features. + */ +struct pw_core_methods { + /** + * Update the type map + * + * Send a type map update to the PipeWire server. The server uses this + * information to keep a mapping between client types and the server types. + * \param first_id the id of the first type + * \param n_types the number of types + * \param types the types as a string + */ + void (*update_types) (void *object, + uint32_t first_id, + uint32_t n_types, + const char **types); + /** + * Do server roundtrip + * + * Ask the server to emit the 'done' event with \a id. + * Since methods are handled in-order and events are delivered + * in-order, this can be used as a barrier to ensure all previous + * methods and the resulting events have been handled. + * \param seq the sequence number passed to the done event + */ + void (*sync) (void *object, uint32_t seq); + /** + * Get the registry object + * + * Create a registry object that allows the client to list and bind + * the global objects available from the PipeWire server + * \param id the client proxy id + */ + void (*get_registry) (void *object, uint32_t new_id); + /** + * Update the client properties + * \param props the new client properties + */ + void (*client_update) (void *object, const struct spa_dict *props); + /** + * Create a new node on the PipeWire server from a factory. + * Use a \a fectory_name of "client-node" to create a + * \ref pw_client_node. + * + * \param factory_name the factory name to use + * \param name the node name + * \param props extra properties + * \param new_id the client proxy id + */ + void (*create_node) (void *object, + const char *factory_name, + const char *name, + const struct spa_dict *props, + uint32_t new_id); + /** + * Create a new link between two node ports + * + * \param output_node_id the global id of the output node + * \param output_port_id the id of the output port + * \param input_node_id the global id of the input node + * \param input_port_id the id of the input port + * \param filter an optional format filter + * \param props optional properties + * \param new_id the client proxy id + */ + void (*create_link) (void *object, + uint32_t output_node_id, + uint32_t output_port_id, + uint32_t input_node_id, + uint32_t input_port_id, + const struct spa_format *filter, + const struct spa_dict *props, + uint32_t new_id); +}; + +#define pw_core_do_update_types(p,...) pw_proxy_do(p,struct pw_core_methods,update_types,__VA_ARGS__) +#define pw_core_do_sync(p,...) pw_proxy_do(p,struct pw_core_methods,sync,__VA_ARGS__) +#define pw_core_do_get_registry(p,...) pw_proxy_do(p,struct pw_core_methods,get_registry,__VA_ARGS__) +#define pw_core_do_client_update(p,...) pw_proxy_do(p,struct pw_core_methods,client_update,__VA_ARGS__) +#define pw_core_do_create_node(p,...) pw_proxy_do(p,struct pw_core_methods,create_node,__VA_ARGS__) +#define pw_core_do_create_link(p,...) pw_proxy_do(p,struct pw_core_methods,create_link,__VA_ARGS__) + +#define PW_CORE_EVENT_UPDATE_TYPES 0 +#define PW_CORE_EVENT_DONE 1 +#define PW_CORE_EVENT_ERROR 2 +#define PW_CORE_EVENT_REMOVE_ID 3 +#define PW_CORE_EVENT_INFO 4 +#define PW_CORE_EVENT_NUM 5 + +/** \struct pw_core_events + * \brief Core events + * \ingroup pw_core_interface The pw_core interface + */ +struct pw_core_events { + /** + * Update the type map + * + * Send a type map update to the client. The client uses this + * information to keep a mapping between server types and the client types. + * \param first_id the id of the first type + * \param n_types the number of types + * \param types the types as a string + */ + void (*update_types) (void *object, + uint32_t first_id, + uint32_t n_types, + const char **types); + /** + * Emit a done event + * + * The done event is emited as a result of a sync method with the + * same sequence number. + * \param seq the sequence number passed to the sync method call + */ + void (*done) (void *object, uint32_t seq); + /** + * Fatal error event + * + * The error event is sent out when a fatal (non-recoverable) + * error has occurred. The id argument is the object where + * the error occurred, most often in response to a request to that + * object. The message is a brief description of the error, + * for (debugging) convenience. + * \param id object where the error occurred + * \param res error code + * \param error error description + */ + void (*error) (void *object, uint32_t id, int res, const char *error, ...); + /** + * Remove an object ID + * + * This event is used internally by the object ID management + * logic. When a client deletes an object, the server will send + * this event to acknowledge that it has seen the delete request. + * When the client receives this event, it will know that it can + * safely reuse the object ID. + * \param id deleted object ID + */ + void (*remove_id) (void *object, uint32_t id); + /** + * Notify new core info + * + * \param info new core info + */ + void (*info) (void *object, struct pw_core_info *info); +}; + +#define pw_core_notify_update_types(r,...) pw_resource_notify(r,struct pw_core_events,update_types,__VA_ARGS__) +#define pw_core_notify_done(r,...) pw_resource_notify(r,struct pw_core_events,done,__VA_ARGS__) +#define pw_core_notify_error(r,...) pw_resource_notify(r,struct pw_core_events,error,__VA_ARGS__) +#define pw_core_notify_remove_id(r,...) pw_resource_notify(r,struct pw_core_events,remove_id,__VA_ARGS__) +#define pw_core_notify_info(r,...) pw_resource_notify(r,struct pw_core_events,info,__VA_ARGS__) + +#define PW_VERSION_REGISTRY 0 + +#define PW_REGISTRY_METHOD_BIND 0 +#define PW_REGISTRY_METHOD_NUM 1 + +/** Registry methods */ +struct pw_registry_methods { + /** + * Bind to a global object + * + * Bind to the global object with \a id and use the client proxy + * with new_id as the proxy. After this call, methods can be + * send to the remote global object and events can be received + * + * \param id the global id to bind to + * \param version the version to use + * \param new_id the client proxy to use + */ + void (*bind) (void *object, uint32_t id, uint32_t version, uint32_t new_id); +}; + +#define pw_registry_do_bind(p,...) pw_proxy_do(p,struct pw_registry_methods,bind,__VA_ARGS__) + +#define PW_REGISTRY_EVENT_GLOBAL 0 +#define PW_REGISTRY_EVENT_GLOBAL_REMOVE 1 +#define PW_REGISTRY_EVENT_NUM 2 + +/** Registry events */ +struct pw_registry_events { + /** + * Notify of a new global object + * + * The registry emits this event when a new global object is + * available. + * + * \param id the global object id + * \param type the type of the object + * \param version the version of the object + */ + void (*global) (void *object, uint32_t id, const char *type, uint32_t version); + /** + * Notify of a global object removal + * + * Emited when a global object was removed from the registry. + * If the client has any bindings to the global, it should destroy + * those. + * + * \param id the id of the global that was removed + */ + void (*global_remove) (void *object, uint32_t id); +}; + +#define pw_registry_notify_global(r,...) pw_resource_notify(r,struct pw_registry_events,global,__VA_ARGS__) +#define pw_registry_notify_global_remove(r,...) pw_resource_notify(r,struct pw_registry_events,global_remove,__VA_ARGS__) + +#define PW_VERSION_MODULE 0 + +#define PW_MODULE_EVENT_INFO 0 +#define PW_MODULE_EVENT_NUM 1 + +/** Module events */ +struct pw_module_events { + /** + * Notify module info + * + * \param info info about the module + */ + void (*info) (void *object, struct pw_module_info *info); +}; + +#define pw_module_notify_info(r,...) pw_resource_notify(r,struct pw_module_events,info,__VA_ARGS__) + +#define PW_VERSION_NODE 0 + +#define PW_NODE_EVENT_INFO 0 +#define PW_NODE_EVENT_NUM 1 + +/** Node events */ +struct pw_node_events { + /** + * Notify node info + * + * \param info info about the node + */ + void (*info) (void *object, struct pw_node_info *info); +}; + +#define pw_node_notify_info(r,...) pw_resource_notify(r,struct pw_node_events,info,__VA_ARGS__) + +#define PW_VERSION_CLIENT 0 + +#define PW_CLIENT_EVENT_INFO 0 +#define PW_CLIENT_EVENT_NUM 1 + +/** Client events */ +struct pw_client_events { + /** + * Notify client info + * + * \param info info about the client + */ + void (*info) (void *object, struct pw_client_info *info); +}; + +#define pw_client_notify_info(r,...) pw_resource_notify(r,struct pw_client_events,info,__VA_ARGS__) + +#define PW_VERSION_LINK 0 + +#define PW_LINK_EVENT_INFO 0 +#define PW_LINK_EVENT_NUM 1 + +/** Link events */ +struct pw_link_events { + /** + * Notify link info + * + * \param info info about the link + */ + void (*info) (void *object, struct pw_link_info *info); +}; + +#define pw_link_notify_info(r,...) pw_resource_notify(r,struct pw_link_events,info,__VA_ARGS__) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_INTERFACES_H__ */ diff --git a/src/pipewire/introspect.c b/src/pipewire/introspect.c new file mode 100644 index 00000000..02e4b811 --- /dev/null +++ b/src/pipewire/introspect.c @@ -0,0 +1,390 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> + +#include "pipewire/pipewire.h" + +#include "pipewire/remote.h" + +const char *pw_node_state_as_string(enum pw_node_state state) +{ + switch (state) { + case PW_NODE_STATE_ERROR: + return "error"; + case PW_NODE_STATE_CREATING: + return "creating"; + case PW_NODE_STATE_SUSPENDED: + return "suspended"; + case PW_NODE_STATE_IDLE: + return "idle"; + case PW_NODE_STATE_RUNNING: + return "running"; + } + return "invalid-state"; +} + +const char *pw_direction_as_string(enum pw_direction direction) +{ + switch (direction) { + case PW_DIRECTION_INPUT: + return "input"; + case PW_DIRECTION_OUTPUT: + return "output"; + default: + return "invalid"; + } + return "invalid-direction"; +} + +const char *pw_link_state_as_string(enum pw_link_state state) +{ + switch (state) { + case PW_LINK_STATE_ERROR: + return "error"; + case PW_LINK_STATE_UNLINKED: + return "unlinked"; + case PW_LINK_STATE_INIT: + return "init"; + case PW_LINK_STATE_NEGOTIATING: + return "negotiating"; + case PW_LINK_STATE_ALLOCATING: + return "allocating"; + case PW_LINK_STATE_PAUSED: + return "paused"; + case PW_LINK_STATE_RUNNING: + return "running"; + } + return "invalid-state"; +} + +static void pw_spa_dict_destroy(struct spa_dict *dict) +{ + struct spa_dict_item *item; + + spa_dict_for_each(item, dict) { + free((void *) item->key); + free((void *) item->value); + } + free(dict->items); + free(dict); +} + +static struct spa_dict *pw_spa_dict_copy(struct spa_dict *dict) +{ + struct spa_dict *copy; + uint32_t i; + + if (dict == NULL) + return NULL; + + copy = calloc(1, sizeof(struct spa_dict)); + if (copy == NULL) + goto no_mem; + copy->items = calloc(dict->n_items, sizeof(struct spa_dict_item)); + if (copy->items == NULL) + goto no_items; + copy->n_items = dict->n_items; + + for (i = 0; i < dict->n_items; i++) { + copy->items[i].key = strdup(dict->items[i].key); + copy->items[i].value = strdup(dict->items[i].value); + } + return copy; + + no_items: + free(copy); + no_mem: + return NULL; +} + +struct pw_core_info *pw_core_info_update(struct pw_core_info *info, + const struct pw_core_info *update) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_core_info)); + if (info == NULL) + return NULL; + } + info->id = update->id; + info->change_mask = update->change_mask; + + if (update->change_mask & (1 << 0)) { + if (info->user_name) + free((void *) info->user_name); + info->user_name = update->user_name ? strdup(update->user_name) : NULL; + } + if (update->change_mask & (1 << 1)) { + if (info->host_name) + free((void *) info->host_name); + info->host_name = update->host_name ? strdup(update->host_name) : NULL; + } + if (update->change_mask & (1 << 2)) { + if (info->version) + free((void *) info->version); + info->version = update->version ? strdup(update->version) : NULL; + } + if (update->change_mask & (1 << 3)) { + if (info->name) + free((void *) info->name); + info->name = update->name ? strdup(update->name) : NULL; + } + if (update->change_mask & (1 << 4)) + info->cookie = update->cookie; + if (update->change_mask & (1 << 5)) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +void pw_core_info_free(struct pw_core_info *info) +{ + if (info->user_name) + free((void *) info->user_name); + if (info->host_name) + free((void *) info->host_name); + if (info->version) + free((void *) info->version); + if (info->name) + free((void *) info->name); + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + +struct pw_node_info *pw_node_info_update(struct pw_node_info *info, + const struct pw_node_info *update) +{ + int i; + + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_node_info)); + if (info == NULL) + return NULL; + } + info->id = update->id; + info->change_mask = update->change_mask; + + if (update->change_mask & (1 << 0)) { + if (info->name) + free((void *) info->name); + info->name = update->name ? strdup(update->name) : NULL; + } + if (update->change_mask & (1 << 1)) { + info->max_input_ports = update->max_input_ports; + info->n_input_ports = update->n_input_ports; + } + if (update->change_mask & (1 << 2)) { + for (i = 0; i < info->n_input_formats; i++) + free(info->input_formats[i]); + info->n_input_formats = update->n_input_formats; + if (info->n_input_formats) + info->input_formats = + realloc(info->input_formats, + info->n_input_formats * sizeof(struct spa_format *)); + else { + free(info->input_formats); + info->input_formats = NULL; + } + for (i = 0; i < info->n_input_formats; i++) { + info->input_formats[i] = spa_format_copy(update->input_formats[i]); + } + } + if (update->change_mask & (1 << 3)) { + info->max_output_ports = update->max_output_ports; + info->n_output_ports = update->n_output_ports; + } + if (update->change_mask & (1 << 4)) { + for (i = 0; i < info->n_output_formats; i++) + free(info->output_formats[i]); + info->n_output_formats = update->n_output_formats; + if (info->n_output_formats) + info->output_formats = + realloc(info->output_formats, + info->n_output_formats * sizeof(struct spa_format *)); + else { + free(info->output_formats); + info->output_formats = NULL; + } + for (i = 0; i < info->n_output_formats; i++) { + info->output_formats[i] = spa_format_copy(update->output_formats[i]); + } + } + + if (update->change_mask & (1 << 5)) { + info->state = update->state; + if (info->error) + free((void *) info->error); + info->error = update->error ? strdup(update->error) : NULL; + } + if (update->change_mask & (1 << 6)) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +void pw_node_info_free(struct pw_node_info *info) +{ + int i; + + if (info->name) + free((void *) info->name); + if (info->input_formats) { + for (i = 0; i < info->n_input_formats; i++) + free(info->input_formats[i]); + free(info->input_formats); + } + if (info->output_formats) { + for (i = 0; i < info->n_output_formats; i++) + free(info->output_formats[i]); + free(info->output_formats); + } + if (info->error) + free((void *) info->error); + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + +struct pw_module_info *pw_module_info_update(struct pw_module_info *info, + const struct pw_module_info *update) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_module_info)); + if (info == NULL) + return NULL; + } + info->id = update->id; + info->change_mask = update->change_mask; + + if (update->change_mask & (1 << 0)) { + if (info->name) + free((void *) info->name); + info->name = update->name ? strdup(update->name) : NULL; + } + if (update->change_mask & (1 << 1)) { + if (info->filename) + free((void *) info->filename); + info->filename = update->filename ? strdup(update->filename) : NULL; + } + if (update->change_mask & (1 << 2)) { + if (info->args) + free((void *) info->args); + info->args = update->args ? strdup(update->args) : NULL; + } + if (update->change_mask & (1 << 3)) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +void pw_module_info_free(struct pw_module_info *info) +{ + if (info->name) + free((void *) info->name); + if (info->filename) + free((void *) info->filename); + if (info->args) + free((void *) info->args); + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + + +struct pw_client_info *pw_client_info_update(struct pw_client_info *info, + const struct pw_client_info *update) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_client_info)); + if (info == NULL) + return NULL; + } + info->id = update->id; + info->change_mask = update->change_mask; + + if (update->change_mask & (1 << 0)) { + if (info->props) + pw_spa_dict_destroy(info->props); + info->props = pw_spa_dict_copy(update->props); + } + return info; +} + +void pw_client_info_free(struct pw_client_info *info) +{ + if (info->props) + pw_spa_dict_destroy(info->props); + free(info); +} + +struct pw_link_info *pw_link_info_update(struct pw_link_info *info, + const struct pw_link_info *update) +{ + if (update == NULL) + return info; + + if (info == NULL) { + info = calloc(1, sizeof(struct pw_link_info)); + if (info == NULL) + return NULL; + } + info->id = update->id; + info->change_mask = update->change_mask; + + if (update->change_mask & (1 << 0)) + info->output_node_id = update->output_node_id; + if (update->change_mask & (1 << 1)) + info->output_port_id = update->output_port_id; + if (update->change_mask & (1 << 2)) + info->input_node_id = update->input_node_id; + if (update->change_mask & (1 << 3)) + info->input_port_id = update->input_port_id; + if (update->change_mask & (1 << 4)) { + if (info->format) + free(info->format); + info->format = spa_format_copy(update->format); + } + return info; +} + +void pw_link_info_free(struct pw_link_info *info) +{ + if (info->format) + free(info->format); + free(info); +} diff --git a/src/pipewire/introspect.h b/src/pipewire/introspect.h new file mode 100644 index 00000000..dd0d772e --- /dev/null +++ b/src/pipewire/introspect.h @@ -0,0 +1,184 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_INTROSPECT_H__ +#define __PIPEWIRE_INTROSPECT_H__ + +#include <spa/defs.h> +#include <spa/format.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/properties.h> + +/** \enum pw_node_state The different node states \memberof pw_node */ +enum pw_node_state { + PW_NODE_STATE_ERROR = -1, /**< error state */ + PW_NODE_STATE_CREATING = 0, /**< the node is being created */ + PW_NODE_STATE_SUSPENDED = 1, /**< the node is suspended, the device might + * be closed */ + PW_NODE_STATE_IDLE = 2, /**< the node is running but there is no active + * port */ + PW_NODE_STATE_RUNNING = 3, /**< the node is running */ +}; + +/** Convert a \ref pw_node_state to a readable string \memberof pw_node */ +const char * pw_node_state_as_string(enum pw_node_state state); + +/** \enum pw_direction The direction of a port \memberof pw_introspect */ +enum pw_direction { + PW_DIRECTION_INPUT = SPA_DIRECTION_INPUT, /**< an input port direction */ + PW_DIRECTION_OUTPUT = SPA_DIRECTION_OUTPUT /**< an output port direction */ +}; + +/** Convert a \ref pw_direction to a readable string \memberof pw_introspect */ +const char * pw_direction_as_string(enum pw_direction direction); + +/** \enum pw_link_state The different link states \memberof pw_link */ +enum pw_link_state { + PW_LINK_STATE_ERROR = -2, /**< the link is in error */ + PW_LINK_STATE_UNLINKED = -1, /**< the link is unlinked */ + PW_LINK_STATE_INIT = 0, /**< the link is initialized */ + PW_LINK_STATE_NEGOTIATING = 1, /**< the link is negotiating formats */ + PW_LINK_STATE_ALLOCATING = 2, /**< the link is allocating buffers */ + PW_LINK_STATE_PAUSED = 3, /**< the link is paused */ + PW_LINK_STATE_RUNNING = 4, /**< the link is running */ +}; + +/** Convert a \ref pw_link_state to a readable string \memberof pw_link */ +const char * pw_link_state_as_string(enum pw_link_state state); + +/** \class pw_introspect + * + * The introspection methods and structures are used to get information + * about the object in the PipeWire server + */ + +/** The core information. Extra information can be added in later versions \memberof pw_introspect */ +struct pw_core_info { + uint32_t id; /**< server side id of the core */ +#define PW_CORE_CHANGE_MASK_USER_NAME (1 << 0) +#define PW_CORE_CHANGE_MASK_HOST_NAME (1 << 1) +#define PW_CORE_CHANGE_MASK_VERSION (1 << 2) +#define PW_CORE_CHANGE_MASK_NAME (1 << 3) +#define PW_CORE_CHANGE_MASK_COOKIE (1 << 4) +#define PW_CORE_CHANGE_MASK_PROPS (1 << 5) +#define PW_CORE_CHANGE_MASK_ALL (~0) + uint64_t change_mask; /**< bitfield of changed fields since last call */ + const char *user_name; /**< name of the user that started the core */ + const char *host_name; /**< name of the machine the core is running on */ + const char *version; /**< version of the core */ + const char *name; /**< name of the core */ + uint32_t cookie; /**< a random cookie for identifying this instance of PipeWire */ + struct spa_dict *props; /**< extra properties */ +}; + +/** Update and existing \ref pw_core_info with \a update \memberof pw_introspect */ +struct pw_core_info * +pw_core_info_update(struct pw_core_info *info, + const struct pw_core_info *update); + +/** Free a \ref pw_core_info \memberof pw_introspect */ +void pw_core_info_free(struct pw_core_info *info); + +/** The module information. Extra information can be added in later versions \memberof pw_introspect */ +struct pw_module_info { + uint32_t id; /**< server side id of the module */ + uint64_t change_mask; /**< bitfield of changed fields since last call */ + const char *name; /**< name of the module */ + const char *filename; /**< filename of the module */ + const char *args; /**< arguments passed to the module */ + struct spa_dict *props; /**< extra properties */ +}; + +/** Update and existing \ref pw_module_info with \a update \memberof pw_introspect */ +struct pw_module_info * +pw_module_info_update(struct pw_module_info *info, + const struct pw_module_info *update); + +/** Free a \ref pw_module_info \memberof pw_introspect */ +void pw_module_info_free(struct pw_module_info *info); + +/** The client information. Extra information can be added in later versions \memberof pw_introspect */ +struct pw_client_info { + uint32_t id; /**< server side id of the client */ + uint64_t change_mask; /**< bitfield of changed fields since last call */ + struct spa_dict *props; /**< extra properties */ +}; + +/** Update and existing \ref pw_client_info with \a update \memberof pw_introspect */ +struct pw_client_info * +pw_client_info_update(struct pw_client_info *info, + const struct pw_client_info *update); + +/** Free a \ref pw_client_info \memberof pw_introspect */ +void pw_client_info_free(struct pw_client_info *info); + + +/** The node information. Extra information can be added in later versions \memberof pw_introspect */ +struct pw_node_info { + uint32_t id; /**< server side id of the node */ + uint64_t change_mask; /**< bitfield of changed fields since last call */ + const char *name; /**< name the node, suitable for display */ + uint32_t max_input_ports; /**< maximum number of inputs */ + uint32_t n_input_ports; /**< number of inputs */ + uint32_t n_input_formats; /**< number of input formats */ + struct spa_format **input_formats; /**< array of input formats */ + uint32_t max_output_ports; /**< maximum number of outputs */ + uint32_t n_output_ports; /**< number of outputs */ + uint32_t n_output_formats; /**< number of output formats */ + struct spa_format **output_formats; /**< array of output formats */ + enum pw_node_state state; /**< the current state of the node */ + const char *error; /**< an error reason if \a state is error */ + struct spa_dict *props; /**< the properties of the node */ +}; + +struct pw_node_info * +pw_node_info_update(struct pw_node_info *info, + const struct pw_node_info *update); + +void +pw_node_info_free(struct pw_node_info *info); + + +/** The link information. Extra information can be added in later versions \memberof pw_introspect */ +struct pw_link_info { + uint32_t id; /**< server side id of the link */ + uint64_t change_mask; /**< bitfield of changed fields since last call */ + uint32_t output_node_id; /**< server side output node id */ + uint32_t output_port_id; /**< output port id */ + uint32_t input_node_id; /**< server side input node id */ + uint32_t input_port_id; /**< input port id */ + struct spa_format *format; /**< format over link */ +}; + +struct pw_link_info * +pw_link_info_update(struct pw_link_info *info, + const struct pw_link_info *update); + +void +pw_link_info_free(struct pw_link_info *info); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_INTROSPECT_H__ */ diff --git a/src/pipewire/link.c b/src/pipewire/link.c new file mode 100644 index 00000000..8ac6ab2a --- /dev/null +++ b/src/pipewire/link.c @@ -0,0 +1,1139 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> + +#include <spa/lib/debug.h> +#include <spa/video/format.h> +#include <spa/pod-utils.h> + +#include <spa/lib/format.h> +#include <spa/lib/props.h> + +#include "pipewire.h" +#include "interfaces.h" +#include "link.h" +#include "work-queue.h" + +#define MAX_BUFFERS 16 + +/** \cond */ +struct impl { + struct pw_link this; + + bool active; + + struct pw_work_queue *work; + + struct spa_format *format_filter; + struct pw_properties *properties; + + struct pw_listener input_port_destroy; + struct pw_listener input_async_complete; + struct pw_listener output_port_destroy; + struct pw_listener output_async_complete; + + void *buffer_owner; + struct pw_memblock buffer_mem; + struct spa_buffer **buffers; + uint32_t n_buffers; +}; + +/** \endcond */ + +static void pw_link_update_state(struct pw_link *link, enum pw_link_state state, char *error) +{ + enum pw_link_state old = link->state; + + if (state != old) { + pw_log_debug("link %p: update state %s -> %s (%s)", link, + pw_link_state_as_string(old), pw_link_state_as_string(state), error); + + link->state = state; + if (link->error) + free(link->error); + link->error = error; + + pw_signal_emit(&link->state_changed, link, old, state); + } +} + +static void complete_ready(void *obj, void *data, int res, uint32_t id) +{ + struct pw_port *port = data; + if (SPA_RESULT_IS_OK(res)) { + port->state = PW_PORT_STATE_READY; + pw_log_debug("port %p: state READY", port); + } else + pw_log_warn("port %p: failed to go to READY", port); +} + +static void complete_paused(void *obj, void *data, int res, uint32_t id) +{ + struct pw_port *port = data; + if (SPA_RESULT_IS_OK(res)) { + port->state = PW_PORT_STATE_PAUSED; + pw_log_debug("port %p: state PAUSED", port); + } else + pw_log_warn("port %p: failed to go to PAUSED", port); +} + +static void complete_streaming(void *obj, void *data, int res, uint32_t id) +{ + struct pw_port *port = data; + if (SPA_RESULT_IS_OK(res)) { + port->state = PW_PORT_STATE_STREAMING; + pw_log_debug("port %p: state STREAMING", port); + } else + pw_log_warn("port %p: failed to go to STREAMING", port); +} + +static int do_negotiate(struct pw_link *this, uint32_t in_state, uint32_t out_state) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res = SPA_RESULT_ERROR, res2; + struct spa_format *format, *current; + char *error = NULL; + + if (in_state != PW_PORT_STATE_CONFIGURE && out_state != PW_PORT_STATE_CONFIGURE) + return SPA_RESULT_OK; + + pw_link_update_state(this, PW_LINK_STATE_NEGOTIATING, NULL); + + format = pw_core_find_format(this->core, this->output, this->input, NULL, 0, NULL, &error); + if (format == NULL) + goto error; + + format = spa_format_copy(format); + + if (out_state > PW_PORT_STATE_CONFIGURE && this->output->node->info.state == PW_NODE_STATE_IDLE) { + if ((res = pw_port_get_format(this->output, + (const struct spa_format **) ¤t)) < 0) { + asprintf(&error, "error get output format: %d", res); + goto error; + } + if (spa_format_compare(current, format) < 0) { + pw_log_debug("link %p: output format change, renegotiate", this); + pw_node_set_state(this->output->node, PW_NODE_STATE_SUSPENDED); + out_state = PW_PORT_STATE_CONFIGURE; + } + else + pw_node_update_state(this->output->node, PW_NODE_STATE_RUNNING, NULL); + } + if (in_state > PW_PORT_STATE_CONFIGURE && this->input->node->info.state == PW_NODE_STATE_IDLE) { + if ((res = pw_port_get_format(this->input, + (const struct spa_format **) ¤t)) < 0) { + asprintf(&error, "error get input format: %d", res); + goto error; + } + if (spa_format_compare(current, format) < 0) { + pw_log_debug("link %p: input format change, renegotiate", this); + pw_node_set_state(this->input->node, PW_NODE_STATE_SUSPENDED); + in_state = PW_PORT_STATE_CONFIGURE; + } + else + pw_node_update_state(this->input->node, PW_NODE_STATE_RUNNING, NULL); + } + + pw_log_debug("link %p: doing set format", this); + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_format(format); + + if (out_state == PW_PORT_STATE_CONFIGURE) { + pw_log_debug("link %p: doing set format on output", this); + if ((res = pw_port_set_format(this->output, SPA_PORT_FORMAT_FLAG_NEAREST, format)) < 0) { + asprintf(&error, "error set output format: %d", res); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res)) + pw_work_queue_add(impl->work, this->output->node, res, complete_ready, + this->output); + } + if (in_state == PW_PORT_STATE_CONFIGURE) { + pw_log_debug("link %p: doing set format on input", this); + if ((res2 = pw_port_set_format(this->input, SPA_PORT_FORMAT_FLAG_NEAREST, format)) < 0) { + asprintf(&error, "error set input format: %d", res2); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res2)) + pw_work_queue_add(impl->work, this->input->node, res2, complete_ready, this->input); + } + + if (this->info.format) + free(this->info.format); + this->info.format = format; + + return SPA_RESULT_OK; + + error: + pw_link_update_state(this, PW_LINK_STATE_ERROR, error); + if (format) + free(format); + return res; +} + +static struct spa_param *find_param(struct spa_param **params, int n_params, uint32_t type) +{ + uint32_t i; + + for (i = 0; i < n_params; i++) { + if (spa_pod_is_object_type(¶ms[i]->object.pod, type)) + return params[i]; + } + return NULL; +} + +static struct spa_param *find_meta_enable(struct pw_core *core, struct spa_param **params, + int n_params, uint32_t type) +{ + uint32_t i; + + for (i = 0; i < n_params; i++) { + if (spa_pod_is_object_type + (¶ms[i]->object.pod, core->type.param_alloc_meta_enable.MetaEnable)) { + uint32_t qtype; + + if (spa_param_query(params[i], + core->type.param_alloc_meta_enable.type, + SPA_POD_TYPE_ID, &qtype, 0) != 1) + continue; + + if (qtype == type) + return params[i]; + } + } + return NULL; +} + +static struct spa_buffer **alloc_buffers(struct pw_link *this, + uint32_t n_buffers, + uint32_t n_params, + struct spa_param **params, + uint32_t n_datas, + size_t *data_sizes, + ssize_t *data_strides, + struct pw_memblock *mem) +{ + struct spa_buffer **buffers, *bp; + uint32_t i; + size_t skel_size, data_size, meta_size; + struct spa_chunk *cdp; + void *ddp; + uint32_t n_metas; + struct spa_meta *metas; + + n_metas = data_size = meta_size = 0; + + /* each buffer */ + skel_size = sizeof(struct spa_buffer); + + metas = alloca(sizeof(struct spa_meta) * n_params + 1); + + /* add shared metadata */ + metas[n_metas].type = this->core->type.meta.Shared; + metas[n_metas].size = sizeof(struct spa_meta_shared); + meta_size += metas[n_metas].size; + n_metas++; + skel_size += sizeof(struct spa_meta); + + /* collect metadata */ + for (i = 0; i < n_params; i++) { + if (spa_pod_is_object_type + (¶ms[i]->object.pod, this->core->type.param_alloc_meta_enable.MetaEnable)) { + uint32_t type, size; + + if (spa_param_query(params[i], + this->core->type.param_alloc_meta_enable.type, + SPA_POD_TYPE_ID, &type, + this->core->type.param_alloc_meta_enable.size, + SPA_POD_TYPE_INT, &size, 0) != 2) + continue; + + pw_log_debug("link %p: enable meta %d %d", this, type, size); + + metas[n_metas].type = type; + metas[n_metas].size = size; + meta_size += metas[n_metas].size; + n_metas++; + skel_size += sizeof(struct spa_meta); + } + } + data_size += meta_size; + + /* data */ + for (i = 0; i < n_datas; i++) { + data_size += sizeof(struct spa_chunk); + data_size += data_sizes[i]; + skel_size += sizeof(struct spa_data); + } + + buffers = calloc(n_buffers, skel_size + sizeof(struct spa_buffer *)); + /* pointer to buffer structures */ + bp = SPA_MEMBER(buffers, n_buffers * sizeof(struct spa_buffer *), struct spa_buffer); + + pw_memblock_alloc(PW_MEMBLOCK_FLAG_WITH_FD | + PW_MEMBLOCK_FLAG_MAP_READWRITE | + PW_MEMBLOCK_FLAG_SEAL, n_buffers * data_size, mem); + + for (i = 0; i < n_buffers; i++) { + int j; + struct spa_buffer *b; + void *p; + + buffers[i] = b = SPA_MEMBER(bp, skel_size * i, struct spa_buffer); + + p = SPA_MEMBER(mem->ptr, data_size * i, void); + + b->id = i; + b->n_metas = n_metas; + b->metas = SPA_MEMBER(b, sizeof(struct spa_buffer), struct spa_meta); + for (j = 0; j < n_metas; j++) { + struct spa_meta *m = &b->metas[j]; + + m->type = metas[j].type; + m->data = p; + m->size = metas[j].size; + + if (m->type == this->core->type.meta.Shared) { + struct spa_meta_shared *msh = p; + + msh->flags = 0; + msh->fd = mem->fd; + msh->offset = data_size * i; + msh->size = data_size; + } else if (m->type == this->core->type.meta.Ringbuffer) { + struct spa_meta_ringbuffer *rb = p; + spa_ringbuffer_init(&rb->ringbuffer, data_sizes[0]); + } + p += m->size; + } + /* pointer to data structure */ + b->n_datas = n_datas; + b->datas = SPA_MEMBER(b->metas, n_metas * sizeof(struct spa_meta), struct spa_data); + + cdp = p; + ddp = SPA_MEMBER(cdp, sizeof(struct spa_chunk) * n_datas, void); + + for (j = 0; j < n_datas; j++) { + struct spa_data *d = &b->datas[j]; + + d->chunk = &cdp[j]; + if (data_sizes[j] > 0) { + d->type = this->core->type.data.MemFd; + d->flags = 0; + d->fd = mem->fd; + d->mapoffset = SPA_PTRDIFF(ddp, mem->ptr); + d->maxsize = data_sizes[j]; + d->data = SPA_MEMBER(mem->ptr, d->mapoffset, void); + d->chunk->offset = 0; + d->chunk->size = data_sizes[j]; + d->chunk->stride = data_strides[j]; + ddp += data_sizes[j]; + } else { + d->type = SPA_ID_INVALID; + d->data = NULL; + } + } + } + return buffers; +} + +static int +param_filter(struct pw_link *this, + struct pw_port *in_port, + struct pw_port *out_port, + struct spa_pod_builder *result) +{ + int res; + struct spa_param *oparam, *iparam; + int iidx, oidx, num = 0; + + for (iidx = 0;; iidx++) { + if (pw_port_enum_params(in_port, iidx, &iparam) + < 0) + break; + + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_param(iparam); + + for (oidx = 0;; oidx++) { + struct spa_pod_frame f; + uint32_t offset; + + if (pw_port_enum_params(out_port, oidx, &oparam) < 0) + break; + + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_param(oparam); + + if (iparam->object.body.type != oparam->object.body.type) + continue; + + offset = result->offset; + spa_pod_builder_push_object(result, &f, 0, iparam->object.body.type); + if ((res = spa_props_filter(result, + SPA_POD_CONTENTS(struct spa_param, iparam), + SPA_POD_CONTENTS_SIZE(struct spa_param, iparam), + SPA_POD_CONTENTS(struct spa_param, oparam), + SPA_POD_CONTENTS_SIZE(struct spa_param, oparam))) < 0) { + result->offset = offset; + result->stack = NULL; + continue; + } + spa_pod_builder_pop(result, &f); + num++; + } + } + return num; +} + +static int do_allocation(struct pw_link *this, uint32_t in_state, uint32_t out_state) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + int res; + const struct spa_port_info *iinfo, *oinfo; + uint32_t in_flags, out_flags; + char *error = NULL; + + if (in_state != PW_PORT_STATE_READY && out_state != PW_PORT_STATE_READY) + return SPA_RESULT_OK; + + pw_link_update_state(this, PW_LINK_STATE_ALLOCATING, NULL); + + pw_log_debug("link %p: doing alloc buffers %p %p", this, this->output->node, + this->input->node); + /* find out what's possible */ + if ((res = pw_port_get_info(this->output, &oinfo)) < 0) { + asprintf(&error, "error get output port info: %d", res); + goto error; + } + if ((res = pw_port_get_info(this->input, &iinfo)) < 0) { + asprintf(&error, "error get input port info: %d", res); + goto error; + } + + in_flags = iinfo->flags; + out_flags = oinfo->flags; + + if (out_flags & SPA_PORT_INFO_FLAG_LIVE) { + pw_log_debug("setting link as live"); + this->output->node->live = true; + this->input->node->live = true; + } + + if (in_state == PW_PORT_STATE_READY && out_state == PW_PORT_STATE_READY) { + if ((out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) && + (in_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS)) { + out_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS; + in_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + } else if ((out_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) && + (in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS)) { + out_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + in_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS; + } else if ((out_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) && + (in_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS)) { + out_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + in_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + } else if ((out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) && + (in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS)) { + out_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS; + in_flags = SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS; + } else { + asprintf(&error, "no common buffer alloc found"); + res = SPA_RESULT_ERROR; + goto error; + } + } else if (in_state == PW_PORT_STATE_READY && out_state > PW_PORT_STATE_READY) { + out_flags &= ~SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + in_flags &= ~SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS; + } else if (out_state == PW_PORT_STATE_READY && in_state > PW_PORT_STATE_READY) { + in_flags &= ~SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + out_flags &= ~SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS; + } else { + pw_log_debug("link %p: delay allocation, state %d %d", this, in_state, out_state); + return SPA_RESULT_OK; + } + + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) { + spa_debug_port_info(oinfo); + spa_debug_port_info(iinfo); + } + + if (impl->buffers == NULL) { + struct spa_param **params, *param; + uint8_t buffer[4096]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + int i, offset, n_params; + uint32_t max_buffers; + size_t minsize = 1024, stride = 0; + + n_params = param_filter(this, this->input, this->output, &b); + + params = alloca(n_params * sizeof(struct spa_param *)); + for (i = 0, offset = 0; i < n_params; i++) { + params[i] = SPA_MEMBER(buffer, offset, struct spa_param); + spa_param_fixate(params[i]); + if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG)) + spa_debug_param(params[i]); + offset += SPA_ROUND_UP_N(SPA_POD_SIZE(params[i]), 8); + } + + param = find_meta_enable(this->core, params, n_params, + this->core->type.meta.Ringbuffer); + if (param) { + uint32_t ms, s; + max_buffers = 1; + + if (spa_param_query(param, + this->core->type.param_alloc_meta_enable.ringbufferSize, + SPA_POD_TYPE_INT, &ms, + this->core->type.param_alloc_meta_enable. + ringbufferStride, SPA_POD_TYPE_INT, &s, 0) == 2) { + minsize = ms; + stride = s; + } + } else { + max_buffers = MAX_BUFFERS; + minsize = stride = 0; + param = find_param(params, n_params, + this->core->type.param_alloc_buffers.Buffers); + if (param) { + uint32_t qmax_buffers = max_buffers, + qminsize = minsize, qstride = stride; + + spa_param_query(param, + this->core->type.param_alloc_buffers.size, + SPA_POD_TYPE_INT, &qminsize, + this->core->type.param_alloc_buffers.stride, + SPA_POD_TYPE_INT, &qstride, + this->core->type.param_alloc_buffers.buffers, + SPA_POD_TYPE_INT, &qmax_buffers, 0); + + max_buffers = + qmax_buffers == 0 ? max_buffers : SPA_MIN(qmax_buffers, + max_buffers); + minsize = SPA_MAX(minsize, qminsize); + stride = SPA_MAX(stride, qstride); + } else { + minsize = 4096; + } + } + + if ((in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) || + (out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS)) + minsize = 0; + + if (this->output->n_buffers) { + out_flags = 0; + in_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + impl->n_buffers = this->output->n_buffers; + impl->buffers = this->output->buffers; + impl->buffer_owner = this->output; + pw_log_debug("reusing %d output buffers %p", impl->n_buffers, + impl->buffers); + } else if (this->input->n_buffers) { + out_flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + in_flags = 0; + impl->n_buffers = this->input->n_buffers; + impl->buffers = this->input->buffers; + impl->buffer_owner = this->input; + pw_log_debug("reusing %d input buffers %p", impl->n_buffers, impl->buffers); + } else { + size_t data_sizes[1]; + ssize_t data_strides[1]; + + data_sizes[0] = minsize; + data_strides[0] = stride; + + impl->buffer_owner = this; + impl->n_buffers = max_buffers; + impl->buffers = alloc_buffers(this, + impl->n_buffers, + n_params, + params, + 1, + data_sizes, data_strides, &impl->buffer_mem); + + pw_log_debug("allocating %d input buffers %p %zd %zd", impl->n_buffers, + impl->buffers, minsize, stride); + } + + if (out_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) { + if ((res = pw_port_alloc_buffers(this->output, + params, n_params, + impl->buffers, &impl->n_buffers)) < 0) { + asprintf(&error, "error alloc output buffers: %d", res); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res)) + pw_work_queue_add(impl->work, this->output->node, res, complete_paused, + this->output); + this->output->buffer_mem = impl->buffer_mem; + impl->buffer_owner = this->output; + pw_log_debug("allocated %d buffers %p from output port", impl->n_buffers, + impl->buffers); + } else if (in_flags & SPA_PORT_INFO_FLAG_CAN_ALLOC_BUFFERS) { + if ((res = pw_port_alloc_buffers(this->input, + params, n_params, + impl->buffers, &impl->n_buffers)) < 0) { + asprintf(&error, "error alloc input buffers: %d", res); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res)) + pw_work_queue_add(impl->work, this->input->node, res, complete_paused, + this->input); + this->input->buffer_mem = impl->buffer_mem; + impl->buffer_owner = this->input; + pw_log_debug("allocated %d buffers %p from input port", impl->n_buffers, + impl->buffers); + } + } + + if (in_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) { + pw_log_debug("using %d buffers %p on input port", impl->n_buffers, impl->buffers); + if ((res = pw_port_use_buffers(this->input, + impl->buffers, impl->n_buffers)) < 0) { + asprintf(&error, "error use input buffers: %d", res); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res)) + pw_work_queue_add(impl->work, this->input->node, res, complete_paused, this->input); + } else if (out_flags & SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS) { + pw_log_debug("using %d buffers %p on output port", impl->n_buffers, impl->buffers); + if ((res = pw_port_use_buffers(this->output, + impl->buffers, impl->n_buffers)) < 0) { + asprintf(&error, "error use output buffers: %d", res); + goto error; + } + if (SPA_RESULT_IS_ASYNC(res)) + pw_work_queue_add(impl->work, this->output->node, res, complete_paused, this->output); + } else { + asprintf(&error, "no common buffer alloc found"); + goto error; + } + + return SPA_RESULT_OK; + + error: + this->output->buffers = NULL; + this->output->n_buffers = 0; + this->output->allocated = false; + this->input->buffers = NULL; + this->input->n_buffers = 0; + this->input->allocated = false; + pw_link_update_state(this, PW_LINK_STATE_ERROR, error); + return res; +} + +static int do_start(struct pw_link *this, uint32_t in_state, uint32_t out_state) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + char *error = NULL; + int res; + + if (in_state < PW_PORT_STATE_PAUSED || out_state < PW_PORT_STATE_PAUSED) + return SPA_RESULT_OK; + + pw_link_update_state(this, PW_LINK_STATE_PAUSED, NULL); + + if (in_state == PW_PORT_STATE_PAUSED) { + if ((res = pw_node_set_state(this->input->node, PW_NODE_STATE_RUNNING)) < 0) { + asprintf(&error, "error starting input node: %d", res); + goto error; + } + + if (SPA_RESULT_IS_ASYNC(res)) + pw_work_queue_add(impl->work, this->input->node, res, complete_streaming, + this->input); + else + complete_streaming(this->input->node, this->input, res, 0); + } + if (out_state == PW_PORT_STATE_PAUSED) { + if ((res = pw_node_set_state(this->output->node, PW_NODE_STATE_RUNNING)) < 0) { + asprintf(&error, "error starting output node: %d", res); + goto error; + } + + if (SPA_RESULT_IS_ASYNC(res)) + pw_work_queue_add(impl->work, this->output->node, res, complete_streaming, + this->output); + else + complete_streaming(this->output->node, this->output, res, 0); + } + return SPA_RESULT_OK; + + error: + pw_link_update_state(this, PW_LINK_STATE_ERROR, error); + return res; +} + +static int check_states(struct pw_link *this, void *user_data, int res) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + uint32_t in_state, out_state; + + if (this->state == PW_LINK_STATE_ERROR) + return SPA_RESULT_ERROR; + + if (this->input == NULL || this->output == NULL) + return SPA_RESULT_OK; + + if (this->input->node->info.state == PW_NODE_STATE_ERROR || + this->output->node->info.state == PW_NODE_STATE_ERROR) + return SPA_RESULT_ERROR; + + in_state = this->input->state; + out_state = this->output->state; + + pw_log_debug("link %p: input state %d, output state %d", this, in_state, out_state); + + if (in_state == PW_PORT_STATE_STREAMING && out_state == PW_PORT_STATE_STREAMING) { + pw_link_update_state(this, PW_LINK_STATE_RUNNING, NULL); + return SPA_RESULT_OK; + } + + if ((res = do_negotiate(this, in_state, out_state)) != SPA_RESULT_OK) + goto exit; + + if ((res = do_allocation(this, in_state, out_state)) != SPA_RESULT_OK) + goto exit; + + if ((res = do_start(this, in_state, out_state)) != SPA_RESULT_OK) + goto exit; + + exit: + if (SPA_RESULT_IS_ERROR(res)) { + pw_log_debug("link %p: got error result %d", this, res); + return res; + } + + pw_work_queue_add(impl->work, + this, SPA_RESULT_WAIT_SYNC, (pw_work_func_t) check_states, this); + return res; +} + +static void +on_input_async_complete_notify(struct pw_listener *listener, + struct pw_node *node, uint32_t seq, int res) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, input_async_complete); + + pw_log_debug("link %p: node %p async complete %d %d", impl, node, seq, res); + pw_work_queue_complete(impl->work, node, seq, res); +} + +static void +on_output_async_complete_notify(struct pw_listener *listener, + struct pw_node *node, uint32_t seq, int res) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, output_async_complete); + + pw_log_debug("link %p: node %p async complete %d %d", impl, node, seq, res); + pw_work_queue_complete(impl->work, node, seq, res); +} + +static void clear_port_buffers(struct pw_link *link, struct pw_port *port) +{ + struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); + + if (impl->buffer_owner != port) + pw_port_use_buffers(port, NULL, 0); +} + +static int +do_remove_input(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_link *this = user_data; + spa_graph_port_remove(&this->rt.in_port); + return SPA_RESULT_OK; +} + +static void input_remove(struct pw_link *this, struct pw_port *port) +{ + struct impl *impl = (struct impl *) this; + + pw_log_debug("link %p: remove input port %p", this, port); + pw_signal_remove(&impl->input_port_destroy); + pw_signal_remove(&impl->input_async_complete); + + pw_loop_invoke(port->node->data_loop, + do_remove_input, 1, 0, NULL, true, this); + + clear_port_buffers(this, this->input); +} + +static int +do_remove_output(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_link *this = user_data; + spa_graph_port_remove(&this->rt.out_port); + return SPA_RESULT_OK; +} + +static void output_remove(struct pw_link *this, struct pw_port *port) +{ + struct impl *impl = (struct impl *) this; + + pw_log_debug("link %p: remove output port %p", this, port); + pw_signal_remove(&impl->output_port_destroy); + pw_signal_remove(&impl->output_async_complete); + + pw_loop_invoke(port->node->data_loop, + do_remove_output, 1, 0, NULL, true, this); + + clear_port_buffers(this, this->output); +} + +static void on_port_destroy(struct pw_link *this, struct pw_port *port) +{ + struct impl *impl = (struct impl *) this; + struct pw_port *other; + + if (port == this->input) { + input_remove(this, port); + other = this->output; + } else if (port == this->output) { + output_remove(this, port); + other = this->input; + } else + return; + + if (impl->buffer_owner == port) { + impl->buffers = NULL; + impl->n_buffers = 0; + + pw_log_debug("link %p: clear allocated buffers on port %p", this, other); + pw_port_use_buffers(other, NULL, 0); + impl->buffer_owner = NULL; + } + + pw_signal_emit(&this->port_unlinked, this, port); + + pw_link_update_state(this, PW_LINK_STATE_UNLINKED, NULL); + pw_link_destroy(this); +} + +static void on_input_port_destroy(struct pw_listener *listener, struct pw_port *port) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, input_port_destroy); + + on_port_destroy(&impl->this, port); +} + +static void on_output_port_destroy(struct pw_listener *listener, struct pw_port *port) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, output_port_destroy); + + on_port_destroy(&impl->this, port); +} + +static int +do_activate_link(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_link *this = user_data; + spa_graph_port_link(&this->rt.out_port, &this->rt.in_port); + return SPA_RESULT_OK; +} + +bool pw_link_activate(struct pw_link *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + + if (impl->active) + return true; + + impl->active = true; + + pw_log_debug("link %p: activate", this); + pw_loop_invoke(this->output->node->data_loop, + do_activate_link, SPA_ID_INVALID, 0, NULL, false, this); + + this->output->node->n_used_output_links++; + this->input->node->n_used_input_links++; + + pw_work_queue_add(impl->work, + this, SPA_RESULT_WAIT_SYNC, (pw_work_func_t) check_states, this); + + return true; +} + +static int +do_deactivate_link(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_link *this = user_data; + spa_graph_port_unlink(&this->rt.out_port); + return SPA_RESULT_OK; +} + +bool pw_link_deactivate(struct pw_link *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + struct pw_node *input_node, *output_node; + + if (!impl->active) + return true; + + impl->active = false; + pw_log_debug("link %p: deactivate", this); + pw_loop_invoke(this->output->node->data_loop, + do_deactivate_link, SPA_ID_INVALID, 0, NULL, true, this); + + input_node = this->input->node; + output_node = this->output->node; + + input_node->n_used_input_links--; + output_node->n_used_output_links--; + + pw_log_debug("link %p: in %d %d, out %d %d, %d %d %d %d", this, + input_node->n_used_input_links, + input_node->n_used_output_links, + output_node->n_used_input_links, + output_node->n_used_output_links, + input_node->idle_used_input_links, + input_node->idle_used_output_links, + output_node->idle_used_input_links, + output_node->idle_used_output_links); + + if (input_node->n_used_input_links <= input_node->idle_used_input_links && + input_node->n_used_output_links <= input_node->idle_used_output_links && + input_node->info.state > PW_NODE_STATE_IDLE) { + pw_node_update_state(input_node, PW_NODE_STATE_IDLE, NULL); + this->input->state = PW_PORT_STATE_PAUSED; + } + + if (output_node->n_used_input_links <= output_node->idle_used_input_links && + output_node->n_used_output_links <= output_node->idle_used_output_links && + output_node->info.state > PW_NODE_STATE_IDLE) { + pw_node_update_state(output_node, PW_NODE_STATE_IDLE, NULL); + this->output->state = PW_PORT_STATE_PAUSED; + } + + return true; +} + +static void link_unbind_func(void *data) +{ + struct pw_resource *resource = data; + spa_list_remove(&resource->link); +} + +static int +link_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id) +{ + struct pw_link *this = global->object; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, global->type, 0); + + if (resource == NULL) + goto no_mem; + + pw_resource_set_implementation(resource, global->object, PW_VERSION_LINK, NULL, link_unbind_func); + + pw_log_debug("link %p: bound to %d", global->object, resource->id); + + spa_list_insert(this->resource_list.prev, &resource->link); + + this->info.change_mask = ~0; + pw_link_notify_info(resource, &this->info); + + return SPA_RESULT_OK; + + no_mem: + pw_log_error("can't create link resource"); + pw_core_notify_error(client->core_resource, + client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory"); + return SPA_RESULT_NO_MEMORY; +} + +static int +do_add_link(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_link *this = user_data; + struct pw_port *port = ((struct pw_port **) data)[0]; + + if (port->direction == PW_DIRECTION_OUTPUT) { + spa_graph_port_add(&port->rt.mix_node, &this->rt.out_port); + } else { + spa_graph_port_add(&port->rt.mix_node, &this->rt.in_port); + } + + return SPA_RESULT_OK; +} + +struct pw_link *pw_link_new(struct pw_core *core, + struct pw_port *output, + struct pw_port *input, + struct spa_format *format_filter, + struct pw_properties *properties, + char **error) +{ + struct impl *impl; + struct pw_link *this; + struct pw_node *input_node, *output_node; + + if (output == input) + goto same_ports; + + if (pw_link_find(output, input)) + goto link_exists; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + goto no_mem; + + this = &impl->this; + pw_log_debug("link %p: new", this); + + impl->work = pw_work_queue_new(core->main_loop); + + this->core = core; + this->properties = properties; + this->state = PW_LINK_STATE_INIT; + + this->input = input; + this->output = output; + + input_node = input->node; + output_node = output->node; + + spa_list_init(&this->resource_list); + pw_signal_init(&this->port_unlinked); + pw_signal_init(&this->state_changed); + pw_signal_init(&this->destroy_signal); + + impl->format_filter = format_filter; + + pw_signal_add(&input->destroy_signal, + &impl->input_port_destroy, on_input_port_destroy); + + pw_signal_add(&input_node->async_complete, + &impl->input_async_complete, on_input_async_complete_notify); + + pw_signal_add(&output->destroy_signal, + &impl->output_port_destroy, on_output_port_destroy); + + pw_signal_add(&output_node->async_complete, + &impl->output_async_complete, on_output_async_complete_notify); + + pw_log_debug("link %p: constructed %p:%d -> %p:%d", impl, + output_node, output->port_id, input_node, input->port_id); + + input_node->live = output_node->live; + if (output_node->clock) + input_node->clock = output_node->clock; + + pw_log_debug("link %p: output node %p clock %p, live %d", this, output_node, output_node->clock, + output_node->live); + + spa_list_insert(output->links.prev, &this->output_link); + spa_list_insert(input->links.prev, &this->input_link); + + spa_list_insert(core->link_list.prev, &this->link); + + pw_core_add_global(core, NULL, core->type.link, 0, this, link_bind_func, &this->global); + + this->info.id = this->global->id; + this->info.output_node_id = output ? output_node->global->id : -1; + this->info.output_port_id = output ? output->port_id : -1; + this->info.input_node_id = input ? input_node->global->id : -1; + this->info.input_port_id = input ? input->port_id : -1; + this->info.format = NULL; + + spa_graph_port_init(&this->rt.out_port, + PW_DIRECTION_OUTPUT, + this->rt.out_port.port_id, + 0, + &this->io); + spa_graph_port_init(&this->rt.in_port, + PW_DIRECTION_INPUT, + this->rt.in_port.port_id, + 0, + &this->io); + + pw_loop_invoke(output_node->data_loop, + do_add_link, + SPA_ID_INVALID, sizeof(struct pw_port *), &output, false, this); + pw_loop_invoke(input_node->data_loop, + do_add_link, + SPA_ID_INVALID, sizeof(struct pw_port *), &input, false, this); + + return this; + + same_ports: + asprintf(error, "can't link the same ports"); + return NULL; + link_exists: + asprintf(error, "link already exists"); + return NULL; + no_mem: + asprintf(error, "no memory"); + return NULL; +} + +void pw_link_destroy(struct pw_link *link) +{ + struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); + struct pw_resource *resource, *tmp; + + pw_log_debug("link %p: destroy", impl); + pw_signal_emit(&link->destroy_signal, link); + + pw_link_deactivate(link); + + pw_global_destroy(link->global); + spa_list_remove(&link->link); + + spa_list_for_each_safe(resource, tmp, &link->resource_list, link) + pw_resource_destroy(resource); + + input_remove(link, link->input); + spa_list_remove(&link->input_link); + link->input = NULL; + + output_remove(link, link->output); + spa_list_remove(&link->output_link); + link->output = NULL; + + pw_work_queue_destroy(impl->work); + + if (link->info.format) + free(link->info.format); + + if (impl->buffer_owner == link) + pw_memblock_free(&impl->buffer_mem); + + free(impl); +} + +struct pw_link *pw_link_find(struct pw_port *output_port, struct pw_port *input_port) +{ + struct pw_link *pl; + + spa_list_for_each(pl, &output_port->links, output_link) { + if (pl->input == input_port) + return pl; + } + return NULL; +} diff --git a/src/pipewire/link.h b/src/pipewire/link.h new file mode 100644 index 00000000..276e5376 --- /dev/null +++ b/src/pipewire/link.h @@ -0,0 +1,118 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_LINK_H__ +#define __PIPEWIRE_LINK_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/ringbuffer.h> + +#include <pipewire/mem.h> +#include <pipewire/introspect.h> + +#include <pipewire/core.h> +#include <pipewire/port.h> +#include <pipewire/main-loop.h> + +/** \page page_link Link + * + * \section page_link_overview Overview + * + * A link is the connection between 2 nodes (\ref page_node). Nodes are + * linked together on ports. + * + * The link is responsible for negotiating the format and buffers for + * the nodes. + */ + +/** \class pw_link + * + * PipeWire link interface. + */ +struct pw_link { + struct pw_core *core; /**< core object */ + struct spa_list link; /**< link in core link_list */ + struct pw_global *global; /**< global for this link */ + + struct pw_properties *properties; /**< extra link properties */ + + struct pw_link_info info; /**< introspectable link info */ + + enum pw_link_state state; /**< link state */ + char *error; /**< error message when state error */ + /** Emited when the link state changed */ + PW_SIGNAL(state_changed, (struct pw_listener *listener, + struct pw_link *link, + enum pw_link_state old, enum pw_link_state state)); + + /** Emited when the link is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *, struct pw_link *)); + + struct spa_list resource_list; /**< list of bound resources */ + + struct spa_port_io io; /**< link io area */ + + struct pw_port *output; /**< output port */ + struct spa_list output_link; /**< link in output port links */ + struct pw_port *input; /**< input port */ + struct spa_list input_link; /**< link in input port links */ + + /** Emited when the port is unlinked */ + PW_SIGNAL(port_unlinked, (struct pw_listener *listener, + struct pw_link *link, struct pw_port *port)); + + struct { + struct spa_graph_port out_port; + struct spa_graph_port in_port; + } rt; +}; + + +/** Make a new link between two ports \memberof pw_link + * \return a newly allocated link */ +struct pw_link * +pw_link_new(struct pw_core *core, /**< the core object */ + struct pw_port *output, /**< an output port */ + struct pw_port *input, /**< an input port */ + struct spa_format *format_filter, /**< an optional format filter */ + struct pw_properties *properties /**< extra properties */, + char **error /**< error string */); + +/** Destroy a link \memberof pw_link */ +void pw_link_destroy(struct pw_link *link); + +/** Find the link between 2 ports \memberof pw_link */ +struct pw_link * pw_link_find(struct pw_port *output, struct pw_port *input); + +/** Activate a link \memberof pw_link + * Starts the negotiation of formats and buffers on \a link and then + * starts data streaming */ +bool pw_link_activate(struct pw_link *link); + +/** Deactivate a link \memberof pw_link */ +bool pw_link_deactivate(struct pw_link *link); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_LINK_H__ */ diff --git a/src/pipewire/log.c b/src/pipewire/log.c new file mode 100644 index 00000000..80835281 --- /dev/null +++ b/src/pipewire/log.c @@ -0,0 +1,137 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <pipewire/log.h> + +#define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_ERROR + +enum spa_log_level pw_log_level = DEFAULT_LOG_LEVEL; + +static struct spa_log *global_log = NULL; + +/** Set the global log interface + * \param log the global log to set + * \memberof pw_log + */ +void pw_log_set(struct spa_log *log) +{ + global_log = log; + if (global_log) + global_log->level = pw_log_level; +} + +/** Get the global log interface + * \return the global log + * \memberof pw_log + */ +struct spa_log *pw_log_get(void) +{ + return global_log; +} + +/** Set the global log level + * \param level the new log level + * \memberof pw_log + */ +void pw_log_set_level(enum spa_log_level level) +{ + pw_log_level = level; + if (global_log) + global_log->level = level; +} + +/** Log a message + * \param level the log level + * \param file the file this message originated from + * \param line the line number + * \param func the function + * \param fmt the printf style format + * \param ... printf style arguments to log + * + * \memberof pw_log + */ +void +pw_log_log(enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, ...) +{ + if (SPA_UNLIKELY(pw_log_level_enabled(level) && global_log)) { + va_list args; + va_start(args, fmt); + global_log->logv(global_log, level, file, line, func, fmt, args); + va_end(args); + } +} + +/** Log a message with va_list + * \param level the log level + * \param file the file this message originated from + * \param line the line number + * \param func the function + * \param fmt the printf style format + * \param args a va_list of arguments + * + * \memberof pw_log + */ +void +pw_log_logv(enum spa_log_level level, + const char *file, + int line, + const char *func, + const char *fmt, + va_list args) +{ + if (SPA_UNLIKELY(pw_log_level_enabled(level) && global_log)) { + global_log->logv(global_log, level, file, line, func, fmt, args); + } +} + +/** \fn void pw_log_error (const char *format, ...) + * Log an error message + * \param format a printf style format + * \param ... printf style arguments + * \memberof pw_log + */ +/** \fn void pw_log_warn (const char *format, ...) + * Log a warning message + * \param format a printf style format + * \param ... printf style arguments + * \memberof pw_log + */ +/** \fn void pw_log_info (const char *format, ...) + * Log an info message + * \param format a printf style format + * \param ... printf style arguments + * \memberof pw_log + */ +/** \fn void pw_log_debug (const char *format, ...) + * Log a debug message + * \param format a printf style format + * \param ... printf style arguments + * \memberof pw_log + */ +/** \fn void pw_log_trace (const char *format, ...) + * Log a trace message. Trace messages may be generated from + * \param format a printf style format + * \param ... printf style arguments + * realtime threads + * \memberof pw_log + */ diff --git a/src/pipewire/log.h b/src/pipewire/log.h new file mode 100644 index 00000000..719dbbe6 --- /dev/null +++ b/src/pipewire/log.h @@ -0,0 +1,103 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_LOG_H__ +#define __PIPEWIRE_LOG_H__ + +#include <spa/log.h> +#include <spa/loop.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \class pw_log + * + * Logging functions of PipeWire + * + * Loggin is performed to stdout and stderr. Trace logging is performed + * in a lockfree ringbuffer and written out from the main thread as to not + * block the realtime threads. + */ + +/** The global log level */ +extern enum spa_log_level pw_log_level; + +void pw_log_set(struct spa_log *log); +struct spa_log *pw_log_get(void); + +void +pw_log_set_level(enum spa_log_level level); + + +void +pw_log_log(enum spa_log_level level, + const char *file, + int line, const char *func, + const char *fmt, ...) SPA_PRINTF_FUNC(5, 6); + +void +pw_log_logv(enum spa_log_level level, + const char *file, + int line, const char *func, + const char *fmt, va_list args) SPA_PRINTF_FUNC(5, 0); + + +/** Check if a loglevel is enabled \memberof pw_log */ +#define pw_log_level_enabled(lev) (pw_log_level >= (lev)) + +#if __STDC_VERSION__ >= 199901L + +#define pw_log_logc(lev,...) \ + if (SPA_UNLIKELY(pw_log_level_enabled (lev))) \ + pw_log_log(lev,__VA_ARGS__) + +#define pw_log_error(...) pw_log_logc(SPA_LOG_LEVEL_ERROR,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define pw_log_warn(...) pw_log_logc(SPA_LOG_LEVEL_WARN,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define pw_log_info(...) pw_log_logc(SPA_LOG_LEVEL_INFO,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define pw_log_debug(...) pw_log_logc(SPA_LOG_LEVEL_DEBUG,__FILE__,__LINE__,__func__,__VA_ARGS__) +#define pw_log_trace(...) pw_log_logc(SPA_LOG_LEVEL_TRACE,__FILE__,__LINE__,__func__,__VA_ARGS__) + +#else + +#include <stdarg.h> + +#define PW_LOG_FUNC(name,lev) \ +static inline void pw_log_##name (const char *format, ...) SPA_PRINTF_FUNC(1, 0); \ +{ \ + if (SPA_UNLIKELY(pw_log_level_enabled(lev))) { \ + va_list varargs; \ + va_start(varargs, format); \ + pw_log_logv(lev,__FILE__,__LINE__,__func__,format,varargs); \ + va_end(varargs); \ + } \ +} + +PW_LOG_FUNC(error, SPA_LOG_LEVEL_ERROR) +PW_LOG_FUNC(warn, SPA_LOG_LEVEL_WARN) +PW_LOG_FUNC(info, SPA_LOG_LEVEL_INFO) +PW_LOG_FUNC(debug, SPA_LOG_LEVEL_DEBUG) +PW_LOG_FUNC(trace, SPA_LOG_LEVEL_TRACE) + +#endif + +#ifdef __cplusplus +} +#endif +#endif /* __PIPEWIRE_LOG_H__ */ diff --git a/src/pipewire/loop.c b/src/pipewire/loop.c new file mode 100644 index 00000000..4fd3c8b5 --- /dev/null +++ b/src/pipewire/loop.c @@ -0,0 +1,127 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <spa/loop.h> +#include <spa/type-map.h> + +#include <pipewire/pipewire.h> +#include <pipewire/loop.h> +#include <pipewire/log.h> + +#define DATAS_SIZE (4096 * 8) + +/** \cond */ + +struct impl { + struct pw_loop this; + + struct spa_handle *handle; +}; +/** \endcond */ + +/** Create a new loop + * \returns a newly allocated loop + * \memberof pw_loop + */ +struct pw_loop *pw_loop_new(void) +{ + int res; + struct impl *impl; + struct pw_loop *this; + const struct spa_handle_factory *factory; + struct spa_type_map *map; + void *iface; + const struct spa_support *support; + uint32_t n_support; + + support = pw_get_support(&n_support); + if (support == NULL) + return NULL; + + map = spa_support_find(support, n_support, SPA_TYPE__TypeMap); + if (map == NULL) + return NULL; + + factory = pw_get_support_factory("loop"); + if (factory == NULL) + return NULL; + + impl = calloc(1, sizeof(struct impl) + factory->size); + if (impl == NULL) + return NULL; + + impl->handle = SPA_MEMBER(impl, sizeof(struct impl), struct spa_handle); + + this = &impl->this; + + pw_signal_init(&this->destroy_signal); + + if ((res = spa_handle_factory_init(factory, + impl->handle, + NULL, + support, + n_support)) < 0) { + fprintf(stderr, "can't make factory instance: %d\n", res); + goto failed; + } + + if ((res = spa_handle_get_interface(impl->handle, + spa_type_map_get_id(map, SPA_TYPE__Loop), + &iface)) < 0) { + fprintf(stderr, "can't get %s interface %d\n", SPA_TYPE__Loop, res); + goto failed; + } + this->loop = iface; + + if ((res = spa_handle_get_interface(impl->handle, + spa_type_map_get_id(map, SPA_TYPE__LoopControl), + &iface)) < 0) { + fprintf(stderr, "can't get %s interface %d\n", SPA_TYPE__LoopControl, res); + goto failed; + } + this->control = iface; + + if ((res = spa_handle_get_interface(impl->handle, + spa_type_map_get_id(map, SPA_TYPE__LoopUtils), + &iface)) < 0) { + fprintf(stderr, "can't get %s interface %d\n", SPA_TYPE__LoopUtils, res); + goto failed; + } + this->utils = iface; + + return this; + + failed: + free(impl); + return NULL; +} + +/** Destroy a loop + * \param loop a loop to destroy + * \memberof pw_loop + */ +void pw_loop_destroy(struct pw_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + pw_signal_emit(&loop->destroy_signal, loop); + + spa_handle_clear(impl->handle); + free(impl); +} diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h new file mode 100644 index 00000000..dceb4cfe --- /dev/null +++ b/src/pipewire/loop.h @@ -0,0 +1,78 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_LOOP_H__ +#define __PIPEWIRE_LOOP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/loop.h> + +#include <pipewire/sig.h> + +/** \class pw_loop + * + * PipeWire loop interface provides an implementation of + * the spa loop interfaces. It can be used to implement various + * event loops. + */ +struct pw_loop { + struct spa_loop *loop; /**< wrapped loop */ + struct spa_loop_control *control; /**< loop control */ + struct spa_loop_utils *utils; /**< loop utils */ + + /** Emited when the loop is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_loop *loop)); +}; + +struct pw_loop * +pw_loop_new(void); + +void +pw_loop_destroy(struct pw_loop *loop); + +#define pw_loop_add_source(l,...) spa_loop_add_source((l)->loop,__VA_ARGS__) +#define pw_loop_update_source(l,...) spa_loop_update_source(__VA_ARGS__) +#define pw_loop_remove_source(l,...) spa_loop_remove_source(__VA_ARGS__) +#define pw_loop_invoke(l,...) spa_loop_invoke((l)->loop,__VA_ARGS__) + +#define pw_loop_get_fd(l) spa_loop_control_get_fd((l)->control) +#define pw_loop_add_hooks(l,...) spa_loop_control_add_hooks((l)->control,__VA_ARGS__) +#define pw_loop_enter(l) spa_loop_control_enter((l)->control) +#define pw_loop_iterate(l,...) spa_loop_control_iterate((l)->control,__VA_ARGS__) +#define pw_loop_leave(l) spa_loop_control_leave((l)->control) + +#define pw_loop_add_io(l,...) spa_loop_utils_add_io((l)->utils,__VA_ARGS__) +#define pw_loop_update_io(l,...) spa_loop_utils_update_io((l)->utils,__VA_ARGS__) +#define pw_loop_add_idle(l,...) spa_loop_utils_add_idle((l)->utils,__VA_ARGS__) +#define pw_loop_enable_idle(l,...) spa_loop_utils_enable_idle((l)->utils,__VA_ARGS__) +#define pw_loop_add_event(l,...) spa_loop_utils_add_event((l)->utils,__VA_ARGS__) +#define pw_loop_signal_event(l,...) spa_loop_utils_signal_event((l)->utils,__VA_ARGS__) +#define pw_loop_add_timer(l,...) spa_loop_utils_add_timer((l)->utils,__VA_ARGS__) +#define pw_loop_update_timer(l,...) spa_loop_utils_update_timer((l)->utils,__VA_ARGS__) +#define pw_loop_add_signal(l,...) spa_loop_utils_add_signal((l)->utils,__VA_ARGS__) +#define pw_loop_destroy_source(l,...) spa_loop_utils_destroy_source((l)->utils,__VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_LOOP_H__ */ diff --git a/src/pipewire/main-loop.c b/src/pipewire/main-loop.c new file mode 100644 index 00000000..f1aca16c --- /dev/null +++ b/src/pipewire/main-loop.c @@ -0,0 +1,112 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "pipewire/log.h" +#include "pipewire/main-loop.h" + +/** \cond */ +struct impl { + struct pw_main_loop this; + + bool running; +}; +/** \endcond */ + +/** Create a new new main loop + * \return a newly allocated \ref pw_main_loop + * + * \memberof pw_main_loop + */ +struct pw_main_loop *pw_main_loop_new(void) +{ + struct impl *impl; + struct pw_main_loop *this; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + return NULL; + + pw_log_debug("main-loop %p: new", impl); + this = &impl->this; + + this->loop = pw_loop_new(); + if (this->loop == NULL) + goto no_loop; + + pw_signal_init(&this->destroy_signal); + + return this; + + no_loop: + free(impl); + return NULL; +} + +/** Destroy a main loop + * \param loop the main loop to destroy + * + * \memberof pw_main_loop + */ +void pw_main_loop_destroy(struct pw_main_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + pw_log_debug("main-loop %p: destroy", impl); + pw_signal_emit(&loop->destroy_signal, loop); + + pw_loop_destroy(loop->loop); + + free(impl); +} + +/** Stop a main loop + * \param loop a \ref pw_main_loop to stop + * + * The call to \ref pw_main_loop_run() will return + * + * \memberof pw_main_loop + */ +void pw_main_loop_quit(struct pw_main_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + pw_log_debug("main-loop %p: quit", impl); + impl->running = false; +} + +/** Start a main loop + * \param loop the main loop to start + * + * Start running \a loop. This function blocks until \ref pw_main_loop_quit() + * has been called + * + * \memberof pw_main_loop + */ +void pw_main_loop_run(struct pw_main_loop *loop) +{ + struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this); + + pw_log_debug("main-loop %p: run", impl); + + impl->running = true; + pw_loop_enter(loop->loop); + while (impl->running) { + pw_loop_iterate(loop->loop, -1); + } + pw_loop_leave(loop->loop); +} diff --git a/src/pipewire/main-loop.h b/src/pipewire/main-loop.h new file mode 100644 index 00000000..be5e279d --- /dev/null +++ b/src/pipewire/main-loop.h @@ -0,0 +1,60 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_MAIN_LOOP_H__ +#define __PIPEWIRE_MAIN_LOOP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/loop.h> + +/** \class pw_main_loop + * + * \brief PipeWire main-loop interface. + * + * A main loop object + */ + +/** A main loop object \memberof pw_main_loop */ +struct pw_main_loop { + struct pw_loop *loop; /**< the wrapped loop */ + + /** Emited when the loop is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_main_loop *loop)); +}; + +struct pw_main_loop * +pw_main_loop_new(void); + +void +pw_main_loop_destroy(struct pw_main_loop *loop); + +void +pw_main_loop_run(struct pw_main_loop *loop); + +void +pw_main_loop_quit(struct pw_main_loop *loop); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_MAIN_LOOP_H__ */ diff --git a/src/pipewire/map.h b/src/pipewire/map.h new file mode 100644 index 00000000..0f98c7e6 --- /dev/null +++ b/src/pipewire/map.h @@ -0,0 +1,184 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_MAP_H__ +#define __PIPEWIRE_MAP_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <string.h> +#include <stdio.h> + +#include <spa/defs.h> +#include <pipewire/array.h> +#include <pipewire/log.h> + +/** \class pw_map + * + * A map that holds objects indexed by id + */ + +/** An entry in the map \memberof pw_map */ +union pw_map_item { + uint32_t next; /**< next free index */ + void *data; /**< data of this item, must be an even address */ +}; + +/** A map \memberof pw_map */ +struct pw_map { + struct pw_array items; /**< an array with the map items */ + uint32_t free_list; /**< the free items */ +}; + +#define PW_MAP_INIT(extend) { PW_ARRAY_INIT(extend), 0 } + +#define pw_map_get_size(m) pw_array_get_len(&(m)->items, union pw_map_item) +#define pw_map_get_item(m,id) pw_array_get_unchecked(&(m)->items,id,union pw_map_item) +#define pw_map_item_is_free(item) ((item)->next & 0x1) +#define pw_map_id_is_free(m,id) (pw_map_item_is_free(pw_map_get_item(m,id))) +#define pw_map_check_id(m,id) ((id) < pw_map_get_size(m)) +#define pw_map_has_item(m,id) (pw_map_check_id(m,id) && !pw_map_id_is_free(m, id)) +#define pw_map_lookup_unchecked(m,id) pw_map_get_item(m,id)->data + +/** Convert an id to a pointer that can be inserted into the map \memberof pw_map */ +#define PW_MAP_ID_TO_PTR(id) (SPA_UINT32_TO_PTR((id)<<1)) +/** Convert a pointer to an id that can be retrieved from the map \memberof pw_map */ +#define PW_MAP_PTR_TO_ID(p) (SPA_PTR_TO_UINT32(p)>>1) + +/** Initialize a map + * \param map the map to initialize + * \param size the initial size of the map + * \param extend the amount to bytes to grow the map with when needed + * \memberof pw_map + */ +static inline void pw_map_init(struct pw_map *map, size_t size, size_t extend) +{ + pw_array_init(&map->items, extend); + pw_array_ensure_size(&map->items, size * sizeof(union pw_map_item)); + map->free_list = 0; +} + +/** Clear a map + * \param map the map to clear + * \memberof pw_map + */ +static inline void pw_map_clear(struct pw_map *map) +{ + pw_array_clear(&map->items); +} + +/** Insert data in the map + * \param map the map to insert into + * \param data the item to add + * \return the id where the item was inserted + * \memberof pw_map + */ +static inline uint32_t pw_map_insert_new(struct pw_map *map, void *data) +{ + union pw_map_item *start, *item; + uint32_t id; + + if (map->free_list) { + start = map->items.data; + item = &start[map->free_list >> 1]; + map->free_list = item->next; + } else { + item = pw_array_add(&map->items, sizeof(union pw_map_item)); + if (!item) + return SPA_ID_INVALID; + start = map->items.data; + } + item->data = data; + id = (item - start); + return id; +} + +/** Insert data in the map at an index + * \param map the map to inser into + * \param id the index to insert at + * \param data the data to insert + * \return true on success, false when the index is invalid + * \memberof pw_map + */ +static inline bool pw_map_insert_at(struct pw_map *map, uint32_t id, void *data) +{ + size_t size = pw_map_get_size(map); + union pw_map_item *item; + + if (id > size) + return false; + else if (id == size) + item = pw_array_add(&map->items, sizeof(union pw_map_item)); + else + item = pw_map_get_item(map, id); + + item->data = data; + return true; +} + +/** Remove and item at index + * \param map the map to remove from + * \param id the index to remove + * \memberof pw_map + */ +static inline void pw_map_remove(struct pw_map *map, uint32_t id) +{ + pw_map_get_item(map, id)->next = map->free_list; + map->free_list = (id << 1) | 1; +} + +/** Find an item in the map + * \param map the map to use + * \param id the index to look at + * \return the item at \a id or NULL when no such item exists + * \memberof pw_map + */ +static inline void *pw_map_lookup(struct pw_map *map, uint32_t id) +{ + if (SPA_LIKELY(pw_map_check_id(map, id))) { + union pw_map_item *item = pw_map_get_item(map, id); + if (!pw_map_item_is_free(item)) + return item->data; + } + return NULL; +} + +/** Iterate all map items + * \param map the map to iterate + * \param func the function to call for each item + * \param data data to pass to \a func + * \memberof pw_map + */ +static inline void pw_map_for_each(struct pw_map *map, void (*func) (void *, void *), void *data) +{ + union pw_map_item *item; + + pw_array_for_each(item, &map->items) { + if (item->data && !pw_map_item_is_free(item)) + func(item->data, data); + } +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_MAP_H__ */ diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c new file mode 100644 index 00000000..10d42d33 --- /dev/null +++ b/src/pipewire/mem.c @@ -0,0 +1,221 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <string.h> +#include <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/syscall.h> + +#include <pipewire/log.h> +#include <pipewire/mem.h> + +/* + * No glibc wrappers exist for memfd_create(2), so provide our own. + * + * Also define memfd fcntl sealing macros. While they are already + * defined in the kernel header file <linux/fcntl.h>, that file as + * a whole conflicts with the original glibc header <fnctl.h>. + */ + +static inline int memfd_create(const char *name, unsigned int flags) +{ + return syscall(SYS_memfd_create, name, flags); +} + +/* memfd_create(2) flags */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +/* fcntl() seals-related flags */ + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + + +#undef USE_MEMFD + +/** Map a memblock + * \param mem a memblock + * \return 0 on success, < 0 on error + * \memberof pw_memblock + */ +int pw_memblock_map(struct pw_memblock *mem) +{ + if (mem->ptr != NULL) + return SPA_RESULT_OK; + + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_READWRITE) { + int prot = 0; + + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_READ) + prot |= PROT_READ; + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_WRITE) + prot |= PROT_WRITE; + + if (mem->flags & PW_MEMBLOCK_FLAG_MAP_TWICE) { + void *ptr; + + mem->ptr = + mmap(NULL, mem->size << 1, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, + 0); + if (mem->ptr == MAP_FAILED) + return SPA_RESULT_NO_MEMORY; + + ptr = + mmap(mem->ptr, mem->size, prot, MAP_FIXED | MAP_SHARED, mem->fd, + mem->offset); + if (ptr != mem->ptr) { + munmap(mem->ptr, mem->size << 1); + return SPA_RESULT_NO_MEMORY; + } + + ptr = + mmap(mem->ptr + mem->size, mem->size, prot, MAP_FIXED | MAP_SHARED, + mem->fd, mem->offset); + if (ptr != mem->ptr + mem->size) { + munmap(mem->ptr, mem->size << 1); + return SPA_RESULT_NO_MEMORY; + } + } else { + mem->ptr = mmap(NULL, mem->size, prot, MAP_SHARED, mem->fd, 0); + if (mem->ptr == MAP_FAILED) + return SPA_RESULT_NO_MEMORY; + } + } else { + mem->ptr = NULL; + } + return SPA_RESULT_OK; +} + +/** Create a new memblock + * \param flags memblock flags + * \param size size to allocate + * \param[out] mem memblock structure to fill + * \return 0 on success, < 0 on error + * \memberof pw_memblock + */ +int pw_memblock_alloc(enum pw_memblock_flags flags, size_t size, struct pw_memblock *mem) +{ + bool use_fd; + + if (mem == NULL || size == 0) + return SPA_RESULT_INVALID_ARGUMENTS; + + mem->offset = 0; + mem->flags = flags; + mem->size = size; + mem->ptr = NULL; + + use_fd = ! !(flags & (PW_MEMBLOCK_FLAG_MAP_TWICE | PW_MEMBLOCK_FLAG_WITH_FD)); + + if (use_fd) { +#ifdef USE_MEMFD + mem->fd = memfd_create("pipewire-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (mem->fd == -1) { + pw_log_error("Failed to create memfd: %s\n", strerror(errno)); + return SPA_RESULT_ERRNO; + } +#else + char filename[] = "/dev/shm/pipewire-tmpfile.XXXXXX"; + mem->fd = mkostemp(filename, O_CLOEXEC); + if (mem->fd == -1) { + pw_log_error("Failed to create temporary file: %s\n", strerror(errno)); + return SPA_RESULT_ERRNO; + } + unlink(filename); +#endif + + if (ftruncate(mem->fd, size) < 0) { + pw_log_warn("Failed to truncate temporary file: %s", strerror(errno)); + close(mem->fd); + return SPA_RESULT_ERRNO; + } +#ifdef USE_MEMFD + if (flags & PW_MEMBLOCK_FLAG_SEAL) { + unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; + if (fcntl(mem->fd, F_ADD_SEALS, seals) == -1) { + pw_log_warn("Failed to add seals: %s", strerror(errno)); + } + } +#endif + if (pw_memblock_map(mem) != SPA_RESULT_OK) + goto mmap_failed; + } else { + mem->ptr = malloc(size); + if (mem->ptr == NULL) + return SPA_RESULT_NO_MEMORY; + mem->fd = -1; + } + if (!(flags & PW_MEMBLOCK_FLAG_WITH_FD) && mem->fd != -1) { + close(mem->fd); + mem->fd = -1; + } + return SPA_RESULT_OK; + + mmap_failed: + close(mem->fd); + return SPA_RESULT_NO_MEMORY; +} + +/** Free a memblock + * \param mem a memblock + * \memberof pw_memblock + */ +void pw_memblock_free(struct pw_memblock *mem) +{ + if (mem == NULL) + return; + + if (mem->flags & PW_MEMBLOCK_FLAG_WITH_FD) { + if (mem->ptr) + munmap(mem->ptr, mem->size); + if (mem->fd != -1) + close(mem->fd); + } else { + free(mem->ptr); + } + mem->ptr = NULL; + mem->fd = -1; +} diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h new file mode 100644 index 00000000..0e5240e2 --- /dev/null +++ b/src/pipewire/mem.h @@ -0,0 +1,64 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_MEM_H__ +#define __PIPEWIRE_MEM_H__ + +#include <spa/defs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** Flags passed to \ref pw_memblock_alloc() \memberof pw_memblock */ +enum pw_memblock_flags { + PW_MEMBLOCK_FLAG_NONE = 0, + PW_MEMBLOCK_FLAG_WITH_FD = (1 << 0), + PW_MEMBLOCK_FLAG_SEAL = (1 << 1), + PW_MEMBLOCK_FLAG_MAP_READ = (1 << 2), + PW_MEMBLOCK_FLAG_MAP_WRITE = (1 << 3), + PW_MEMBLOCK_FLAG_MAP_TWICE = (1 << 4), +}; + +#define PW_MEMBLOCK_FLAG_MAP_READWRITE (PW_MEMBLOCK_FLAG_MAP_READ | PW_MEMBLOCK_FLAG_MAP_WRITE) + +/** \class pw_memblock + * Memory block structure */ +struct pw_memblock { + enum pw_memblock_flags flags; /**< flags used when allocating */ + int fd; /**< memfd if any */ + off_t offset; /**< offset of mappable memory */ + void *ptr; /**< ptr to mapped memory */ + size_t size; /**< size of mapped memory */ +}; + +int +pw_memblock_alloc(enum pw_memblock_flags flags, size_t size, struct pw_memblock *mem); + +int +pw_memblock_map(struct pw_memblock *mem); + +void +pw_memblock_free(struct pw_memblock *mem); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_MEM_H__ */ diff --git a/src/pipewire/memfd-wrappers.h b/src/pipewire/memfd-wrappers.h new file mode 100644 index 00000000..05958fe4 --- /dev/null +++ b/src/pipewire/memfd-wrappers.h @@ -0,0 +1,60 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <sys/syscall.h> +#include <fcntl.h> + +/* + * No glibc wrappers exist for memfd_create(2), so provide our own. + * + * Also define memfd fcntl sealing macros. While they are already + * defined in the kernel header file <linux/fcntl.h>, that file as + * a whole conflicts with the original glibc header <fnctl.h>. + */ + +static inline int +memfd_create(const char *name, unsigned int flags) { + return syscall(SYS_memfd_create, name, flags); +} + +/* memfd_create(2) flags */ + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +/* fcntl() seals-related flags */ + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build new file mode 100644 index 00000000..782bc1f8 --- /dev/null +++ b/src/pipewire/meson.build @@ -0,0 +1,86 @@ +pipewire_headers = [ + 'array.h', + 'client.h', + 'command.h', + 'core.h', + 'data-loop.h', + 'interfaces.h', + 'introspect.h', + 'link.h', + 'log.h', + 'loop.h', + 'main-loop.h', + 'map.h', + 'mem.h', + 'module.h', + 'node.h', + 'node-factory.h', + 'pipewire.h', + 'port.h', + 'properties.h', + 'protocol.h', + 'proxy.h', + 'remote.h', + 'resource.h', + 'rtkit.h', + 'sig.h', + 'stream.h', + 'thread-loop.h', + 'transport.h', + 'type.h', + 'utils.h', + 'work-queue.h', +] + +pipewire_sources = [ + 'client.c', + 'command.c', + 'core.c', + 'data-loop.c', + 'introspect.c', + 'link.c', + 'log.c', + 'loop.c', + 'main-loop.c', + 'mem.c', + 'module.c', + 'node.c', + 'node-factory.c', + 'pipewire.c', + 'port.c', + 'properties.c', + 'protocol.c', + 'proxy.c', + 'remote.c', + 'resource.c', + 'stream.c', + 'rtkit.c', + 'thread-loop.c', + 'transport.c', + 'type.c', + 'utils.c', + 'work-queue.c', +] + +install_headers(pipewire_headers, subdir : 'pipewire') + +libpipewire_c_args = [ + '-DHAVE_CONFIG_H', + '-D_GNU_SOURCE', + '-D_POSIX_C_SOURCE', +] + +libpipewire = shared_library('pipewire-@0@'.format(apiversion), pipewire_sources, + version : libversion, + soversion : soversion, + c_args : libpipewire_c_args, + include_directories : [pipewire_inc, configinc, spa_inc], + link_with : spalib, + install : true, + dependencies : [dbus_dep, dl_lib, mathlib, pthread_lib], +) + +pipewire_dep = declare_dependency(link_with : libpipewire, + include_directories : [pipewire_inc, configinc, spa_inc], + dependencies : [pthread_lib,spalib_dep], +) diff --git a/src/pipewire/module.c b/src/pipewire/module.c new file mode 100644 index 00000000..c3d156b6 --- /dev/null +++ b/src/pipewire/module.c @@ -0,0 +1,235 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <dlfcn.h> +#include <dirent.h> +#include <sys/stat.h> +#include <errno.h> + +#include "pipewire/pipewire.h" +#include "pipewire/interfaces.h" +#include "pipewire/utils.h" +#include "pipewire/module.h" + +/** \cond */ +struct impl { + struct pw_module this; + void *hnd; +}; +/** \endcond */ + +static char *find_module(const char *path, const char *name) +{ + char *filename; + struct dirent *entry; + struct stat s; + DIR *dir; + + asprintf(&filename, "%s/%s.so", path, name); + + if (stat(filename, &s) == 0 && S_ISREG(s.st_mode)) { + /* found a regular file with name */ + return filename; + } + + free(filename); + filename = NULL; + + /* now recurse down in subdirectories and look for it there */ + + dir = opendir(path); + if (dir == NULL) { + pw_log_warn("could not open %s: %s", path, strerror(errno)); + return NULL; + } + + while ((entry = readdir(dir))) { + char *newpath; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + asprintf(&newpath, "%s/%s", path, entry->d_name); + + if (stat(newpath, &s) == 0 && S_ISDIR(s.st_mode)) { + filename = find_module(newpath, name); + } + free(newpath); + + if (filename != NULL) + break; + } + + closedir(dir); + + return filename; +} + +static int +module_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id) +{ + struct pw_module *this = global->object; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, global->type, 0); + if (resource == NULL) + goto no_mem; + + pw_resource_set_implementation(resource, global->object, PW_VERSION_MODULE, NULL, NULL); + + pw_log_debug("module %p: bound to %d", global->object, resource->id); + + this->info.change_mask = ~0; + pw_module_notify_info(resource, &this->info); + + return SPA_RESULT_OK; + + no_mem: + pw_log_error("can't create module resource"); + pw_core_notify_error(client->core_resource, + client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory"); + return SPA_RESULT_NO_MEMORY; +} + +/** Load a module + * + * \param core a \ref pw_core + * \param name name of the module to load + * \param args A string with arguments for the module + * \param[out] err Return location for an error string, or NULL + * \return A \ref pw_module if the module could be loaded, or NULL on failure. + * + * \memberof pw_module + */ +struct pw_module *pw_module_load(struct pw_core *core, + const char *name, const char *args, char **err) +{ + struct pw_module *this; + struct impl *impl; + void *hnd; + char *filename = NULL; + const char *module_dir; + pw_module_init_func_t init_func; + + module_dir = getenv("PIPEWIRE_MODULE_DIR"); + if (module_dir != NULL) { + char **l; + int i, n_paths; + + pw_log_debug("PIPEWIRE_MODULE_DIR set to: %s", module_dir); + + l = pw_split_strv(module_dir, "/", 0, &n_paths); + for (i = 0; l[i] != NULL; i++) { + filename = find_module(l[i], name); + if (filename != NULL) + break; + } + pw_free_strv(l); + } else { + pw_log_debug("moduledir set to: %s", MODULEDIR); + + filename = find_module(MODULEDIR, name); + } + + if (filename == NULL) + goto not_found; + + pw_log_debug("trying to load module: %s (%s)", name, filename); + + hnd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); + + if (hnd == NULL) + goto open_failed; + + if ((init_func = dlsym(hnd, PIPEWIRE_SYMBOL_MODULE_INIT)) == NULL) + goto no_pw_module; + + impl = calloc(1, sizeof(struct impl)); + if (impl == NULL) + goto no_mem; + + impl->hnd = hnd; + + this = &impl->this; + this->core = core; + + pw_signal_init(&this->destroy_signal); + + if (!init_func(this, (char *) args)) + goto init_failed; + + pw_core_add_global(core, NULL, core->type.module, 0, impl, module_bind_func, &this->global); + + this->info.id = this->global->id; + this->info.name = name ? strdup(name) : NULL; + this->info.filename = filename; + this->info.args = args ? strdup(args) : NULL; + this->info.props = NULL; + + pw_log_debug("loaded module: %s", this->info.name); + + return this; + + not_found: + if (err) + asprintf(err, "No module \"%s\" was found", name); + return NULL; + open_failed: + if (err) + asprintf(err, "Failed to open module: \"%s\" %s", filename, dlerror()); + free(filename); + return NULL; + no_mem: + no_pw_module: + if (err) + asprintf(err, "\"%s\" is not a pipewire module", name); + dlclose(hnd); + free(filename); + return NULL; + init_failed: + if (err) + asprintf(err, "\"%s\" failed to initialize", name); + pw_module_destroy(this); + return NULL; +} + +/** Destroy a module + * \param module the module to destroy + * \memberof pw_module + */ +void pw_module_destroy(struct pw_module *module) +{ + struct impl *impl = SPA_CONTAINER_OF(module, struct impl, this); + + pw_signal_emit(&module->destroy_signal, module); + + if (module->info.name) + free((char *) module->info.name); + if (module->info.filename) + free((char *) module->info.filename); + if (module->info.args) + free((char *) module->info.args); + dlclose(impl->hnd); + free(impl); +} diff --git a/src/pipewire/module.h b/src/pipewire/module.h new file mode 100644 index 00000000..fb390ca9 --- /dev/null +++ b/src/pipewire/module.h @@ -0,0 +1,73 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications <dev-gstreamer@axis.com> + * @author Linus Svensson <linus.svensson@axis.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_MODULE_H__ +#define __PIPEWIRE_MODULE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/core.h> + +#define PIPEWIRE_SYMBOL_MODULE_INIT "pipewire__module_init" + +/** \class pw_module + * + * A dynamically loadable module + */ +struct pw_module { + struct pw_core *core; /**< the core object */ + struct spa_list link; /**< link in the core module_list */ + struct pw_global *global; /**< global object for this module */ + + struct pw_module_info info; /**< introspectable module info */ + + void *user_data; /**< module user_data */ + + /** Emited when the module is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_module *module)); +}; + +/** Module init function signature + * + * \param module A \ref pw_module + * \param args Arguments to the module + * \return true on success, false otherwise + * + * A module should provide an init function with this signature. This function + * will be called when a module is loaded. + * + * \memberof pw_module + */ +typedef bool (*pw_module_init_func_t) (struct pw_module *module, char *args); + +struct pw_module * +pw_module_load(struct pw_core *core, + const char *name, const char *args, char **err); + +void +pw_module_destroy(struct pw_module *module); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_MODULE_H__ */ diff --git a/src/pipewire/node-factory.c b/src/pipewire/node-factory.c new file mode 100644 index 00000000..616384af --- /dev/null +++ b/src/pipewire/node-factory.c @@ -0,0 +1,21 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications AB + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "pipewire/pipewire.h" +#include "pipewire/node-factory.h" diff --git a/src/pipewire/node-factory.h b/src/pipewire/node-factory.h new file mode 100644 index 00000000..20452b2c --- /dev/null +++ b/src/pipewire/node-factory.h @@ -0,0 +1,63 @@ +/* PipeWire + * Copyright (C) 2016 Axis Communications AB + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_NODE_FACTORY_H__ +#define __PIPEWIRE_NODE_FACTORY_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PIPEWIRE_TYPE__NodeFactory "PipeWire:Object:NodeFactory" +#define PIPEWIRE_TYPE_NODE_FACTORY_BASE PIPEWIRE_TYPE__NodeFactory ":" + +#include <pipewire/core.h> +#include <pipewire/resource.h> + +/** \class pw_node_factory + * + * \brief PipeWire node factory interface. + * + * The factory object is used to make nodes on demand. + */ +struct pw_node_factory { + struct pw_core *core; /**< the core */ + struct spa_list link; /**< link in core node_factory_list */ + struct pw_global *global; /**< global for this factory */ + + const char *name; /**< the factory name */ + uint32_t type; /**< type of the created nodes */ + + /** Emited when the factory is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_node_factory *object)); + + /** The function to create a node from this factory */ + struct pw_node *(*create_node) (struct pw_node_factory *factory, + struct pw_resource *resource, + const char *name, + struct pw_properties *properties); +}; + +#define pw_node_factory_create_node(f,...) (f)->create_node((f),__VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_NODE_FACTORY_H__ */ diff --git a/src/pipewire/node.c b/src/pipewire/node.c new file mode 100644 index 00000000..0a573a00 --- /dev/null +++ b/src/pipewire/node.c @@ -0,0 +1,617 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include "pipewire/pipewire.h" +#include "pipewire/interfaces.h" + +#include "pipewire/node.h" +#include "pipewire/data-loop.h" +#include "pipewire/main-loop.h" +#include "pipewire/work-queue.h" + +/** \cond */ +struct impl { + struct pw_node this; + + struct pw_work_queue *work; + struct pw_listener on_async_complete; + struct pw_listener on_event; + + bool exported; +}; + +/** \endcond */ + +static int pause_node(struct pw_node *this) +{ + int res = SPA_RESULT_OK; + + if (this->info.state <= PW_NODE_STATE_IDLE) + return SPA_RESULT_OK; + + pw_log_debug("node %p: pause node", this); + if ((res = this->implementation->send_command(this, + &SPA_COMMAND_INIT(this->core->type.command_node.Pause))) < 0) + pw_log_debug("node %p: send command error %d", this, res); + + return res; +} + +static int start_node(struct pw_node *this) +{ + int res = SPA_RESULT_OK; + + pw_log_debug("node %p: start node", this); + if ((res = this->implementation->send_command(this, + &SPA_COMMAND_INIT(this->core->type.command_node.Start))) < 0) + pw_log_debug("node %p: send command error %d", this, res); + + return res; +} + +static int suspend_node(struct pw_node *this) +{ + int res = SPA_RESULT_OK; + struct pw_port *p; + + pw_log_debug("node %p: suspend node", this); + + spa_list_for_each(p, &this->input_ports, link) { + if ((res = pw_port_set_format(p, 0, NULL)) < 0) + pw_log_warn("error unset format input: %d", res); + p->state = PW_PORT_STATE_CONFIGURE; + } + + spa_list_for_each(p, &this->output_ports, link) { + if ((res = pw_port_set_format(p, 0, NULL)) < 0) + pw_log_warn("error unset format output: %d", res); + p->state = PW_PORT_STATE_CONFIGURE; + } + return res; +} + +static void on_async_complete(struct pw_listener *listener, + struct pw_node *node, uint32_t seq, int res) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, on_async_complete); + struct pw_node *this = &impl->this; + + pw_log_debug("node %p: async complete event %d %d", this, seq, res); + pw_work_queue_complete(impl->work, this, seq, res); +} + +static void send_clock_update(struct pw_node *this) +{ + int res; + struct spa_command_node_clock_update cu = + SPA_COMMAND_NODE_CLOCK_UPDATE_INIT(this->core->type.command_node.ClockUpdate, + SPA_COMMAND_NODE_CLOCK_UPDATE_TIME | + SPA_COMMAND_NODE_CLOCK_UPDATE_SCALE | + SPA_COMMAND_NODE_CLOCK_UPDATE_STATE | + SPA_COMMAND_NODE_CLOCK_UPDATE_LATENCY, /* change_mask */ + 1, /* rate */ + 0, /* ticks */ + 0, /* monotonic_time */ + 0, /* offset */ + (1 << 16) | 1, /* scale */ + SPA_CLOCK_STATE_RUNNING, /* state */ + 0, /* flags */ + 0); /* latency */ + + if (this->clock && this->live) { + cu.body.flags.value = SPA_COMMAND_NODE_CLOCK_UPDATE_FLAG_LIVE; + res = spa_clock_get_time(this->clock, + &cu.body.rate.value, + &cu.body.ticks.value, + &cu.body.monotonic_time.value); + } + if ((res = this->implementation->send_command(this, (struct spa_command *) &cu)) < 0) + pw_log_debug("node %p: send clock update error %d", this, res); +} + +static void on_event(struct pw_listener *listener, + struct pw_node *node, struct spa_event *event) +{ + struct impl *impl = SPA_CONTAINER_OF(listener, struct impl, on_event); + struct pw_node *this = &impl->this; + + pw_log_trace("node %p: event %d", this, SPA_EVENT_TYPE(event)); + if (SPA_EVENT_TYPE(event) == this->core->type.event_node.RequestClockUpdate) { + send_clock_update(this); + } +} + +static void node_unbind_func(void *data) +{ + struct pw_resource *resource = data; + spa_list_remove(&resource->link); +} + +static void +update_info(struct pw_node *this) +{ + this->info.id = this->global->id; + this->info.input_formats = NULL; + + if (!spa_list_is_empty(&this->input_ports)) { + struct pw_port *port = spa_list_first(&this->input_ports, struct pw_port, link); + + for (this->info.n_input_formats = 0;; this->info.n_input_formats++) { + struct spa_format *fmt; + + if (pw_port_enum_formats(port, &fmt, NULL, this->info.n_input_formats) < 0) + break; + + this->info.input_formats = + realloc(this->info.input_formats, + sizeof(struct spa_format *) * (this->info.n_input_formats + 1)); + this->info.input_formats[this->info.n_input_formats] = spa_format_copy(fmt); + } + } + + this->info.output_formats = NULL; + if (!spa_list_is_empty(&this->output_ports)) { + struct pw_port *port = spa_list_first(&this->output_ports, struct pw_port, link); + + for (this->info.n_output_formats = 0;; this->info.n_output_formats++) { + struct spa_format *fmt; + + if (pw_port_enum_formats(port, &fmt, NULL, this->info.n_output_formats) < 0) + break; + + this->info.output_formats = + realloc(this->info.output_formats, + sizeof(struct spa_format *) * (this->info.n_output_formats + 1)); + this->info.output_formats[this->info.n_output_formats] = spa_format_copy(fmt); + } + } + this->info.props = this->properties ? &this->properties->dict : NULL; +} + +static void +clear_info(struct pw_node *this) +{ + int i; + + free((char*)this->info.name); + if (this->info.input_formats) { + for (i = 0; i < this->info.n_input_formats; i++) + free(this->info.input_formats[i]); + free(this->info.input_formats); + } + + if (this->info.output_formats) { + for (i = 0; i < this->info.n_output_formats; i++) + free(this->info.output_formats[i]); + free(this->info.output_formats); + } + free((char*)this->info.error); + +} + +static int +node_bind_func(struct pw_global *global, struct pw_client *client, uint32_t version, uint32_t id) +{ + struct pw_node *this = global->object; + struct pw_resource *resource; + + resource = pw_resource_new(client, id, global->type, 0); + if (resource == NULL) + goto no_mem; + + pw_resource_set_implementation(resource, global->object, PW_VERSION_NODE, NULL, node_unbind_func); + + pw_log_debug("node %p: bound to %d", this, resource->id); + + spa_list_insert(this->resource_list.prev, &resource->link); + + this->info.change_mask = ~0; + pw_node_notify_info(resource, &this->info); + this->info.change_mask = 0; + + return SPA_RESULT_OK; + + no_mem: + pw_log_error("can't create node resource"); + pw_core_notify_error(client->core_resource, + client->core_resource->id, SPA_RESULT_NO_MEMORY, "no memory"); + return SPA_RESULT_NO_MEMORY; +} + +static int +do_node_add(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_node *this = user_data; + + spa_graph_node_add(this->rt.sched->graph, &this->rt.node); + + return SPA_RESULT_OK; +} + +void pw_node_export(struct pw_node *this) +{ + struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); + + pw_log_debug("node %p: export", this); + + spa_list_insert(this->core->node_list.prev, &this->link); + + pw_core_add_global(this->core, + this->owner, + this->core->type.node, 0, this, node_bind_func, &this->global); + + pw_loop_invoke(this->data_loop, do_node_add, 1, 0, NULL, false, this); + + update_info(this); + + impl->exported = true; + + pw_signal_emit(&this->initialized, this); + + pw_node_update_state(this, PW_NODE_STATE_SUSPENDED, NULL); +} + +static int +graph_impl_process_input(struct spa_graph_node *node, void *user_data) +{ + struct pw_node *this = user_data; + return this->implementation->process_input(this); +} + +static int +graph_impl_process_output(struct spa_graph_node *node, void *user_data) +{ + struct pw_node *this = user_data; + return this->implementation->process_output(this); +} + +static const struct spa_graph_node_methods graph_methods = { + SPA_VERSION_GRAPH_NODE_METHODS, + graph_impl_process_input, + graph_impl_process_output, +}; + +struct pw_node *pw_node_new(struct pw_core *core, + struct pw_resource *owner, + const char *name, + struct pw_properties *properties, + size_t user_data_size) +{ + struct impl *impl; + struct pw_node *this; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->core = core; + this->owner = owner; + pw_log_debug("node %p: new, owner %p", this, owner); + + if (user_data_size > 0) + this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void); + + impl->work = pw_work_queue_new(this->core->main_loop); + + this->info.name = strdup(name); + this->properties = properties; + + this->data_loop = core->data_loop; + + this->rt.sched = &core->rt.sched; + + spa_list_init(&this->resource_list); + + pw_signal_init(&this->state_request); + pw_signal_init(&this->state_changed); + pw_signal_init(&this->initialized); + pw_signal_init(&this->port_added); + pw_signal_init(&this->port_removed); + pw_signal_init(&this->destroy_signal); + pw_signal_init(&this->free_signal); + pw_signal_init(&this->async_complete); + pw_signal_init(&this->event); + + pw_signal_add(&this->async_complete, &impl->on_async_complete, on_async_complete); + pw_signal_add(&this->event, &impl->on_event, on_event); + + this->info.state = PW_NODE_STATE_CREATING; + + spa_list_init(&this->input_ports); + pw_map_init(&this->input_port_map, 64, 64); + spa_list_init(&this->output_ports); + pw_map_init(&this->output_port_map, 64, 64); + + spa_graph_node_init(&this->rt.node); + spa_graph_node_set_methods(&this->rt.node, + &graph_methods, + this); + + return this; +} + +static int +do_node_remove(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_node *this = user_data; + + pause_node(this); + + spa_graph_node_remove(&this->rt.node); + + return SPA_RESULT_OK; +} + +/** Destroy a node + * \param node a node to destroy + * + * Remove \a node. This will stop the transfer on the node and + * free the resources allocated by \a node. + * + * \memberof pw_node + */ +void pw_node_destroy(struct pw_node *node) +{ + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + struct pw_resource *resource, *tmp; + struct pw_port *port, *tmpp; + + pw_log_debug("node %p: destroy", impl); + pw_signal_emit(&node->destroy_signal, node); + + pw_loop_invoke(node->data_loop, do_node_remove, 1, 0, NULL, true, node); + + if (impl->exported) { + spa_list_remove(&node->link); + pw_global_destroy(node->global); + node->global = NULL; + } + + spa_list_for_each_safe(resource, tmp, &node->resource_list, link) + pw_resource_destroy(resource); + + pw_log_debug("node %p: destroy ports", node); + spa_list_for_each_safe(port, tmpp, &node->input_ports, link) { + pw_signal_emit(&node->port_removed, node, port); + pw_port_destroy(port); + } + + spa_list_for_each_safe(port, tmpp, &node->output_ports, link) { + pw_signal_emit(&node->port_removed, node, port); + pw_port_destroy(port); + } + + pw_log_debug("node %p: free", node); + pw_signal_emit(&node->free_signal, node); + + if (node->destroy) + node->destroy(node); + + pw_work_queue_destroy(impl->work); + + pw_map_clear(&node->input_port_map); + pw_map_clear(&node->output_port_map); + + if (node->properties) + pw_properties_free(node->properties); + + clear_info(node); + + free(impl); +} + +/** + * pw_node_get_free_port: + * \param node a \ref pw_node + * \param direction a \ref pw_direction + * \return the new port or NULL on error + * + * Find a new unused port in \a node with \a direction + * + * \memberof pw_node + */ +struct pw_port *pw_node_get_free_port(struct pw_node *node, enum pw_direction direction) +{ + uint32_t n_ports, max_ports; + struct spa_list *ports; + struct pw_port *port = NULL, *p, *mixport = NULL; + struct pw_map *portmap; + + if (direction == PW_DIRECTION_INPUT) { + max_ports = node->info.max_input_ports; + n_ports = node->info.n_input_ports; + ports = &node->input_ports; + portmap = &node->input_port_map; + } else { + max_ports = node->info.max_output_ports; + n_ports = node->info.n_output_ports; + ports = &node->output_ports; + portmap = &node->output_port_map; + } + + pw_log_debug("node %p: direction %d max %u, n %u", node, direction, max_ports, n_ports); + + /* first try to find an unlinked port */ + spa_list_for_each(p, ports, link) { + if (spa_list_is_empty(&p->links)) + return p; + /* for output we can reuse an existing port, for input only + * when there is a multiplex */ + if (direction == PW_DIRECTION_OUTPUT || p->mix != NULL) + mixport = p; + } + + /* no port, can we create one ? */ + if (n_ports < max_ports) { + uint32_t port_id = pw_map_insert_new(portmap, NULL); + + pw_log_debug("node %p: creating port direction %d %u", node, direction, port_id); + + port = node->implementation->add_port(node, direction, port_id); + if (port == NULL) + goto no_mem; + } else { + port = mixport; + } + return port; + + no_mem: + pw_log_error("node %p: can't create new port", node); + return NULL; +} + +static void on_state_complete(struct pw_node *node, void *data, int res) +{ + enum pw_node_state state = SPA_PTR_TO_INT(data); + char *error = NULL; + + pw_log_debug("node %p: state complete %d", node, res); + if (SPA_RESULT_IS_ERROR(res)) { + asprintf(&error, "error changing node state: %d", res); + state = PW_NODE_STATE_ERROR; + } + pw_node_update_state(node, state, error); +} + +static void node_deactivate(struct pw_node *this) +{ + struct pw_port *port; + + pw_log_debug("node %p: deactivate", this); + spa_list_for_each(port, &this->input_ports, link) { + struct pw_link *link; + spa_list_for_each(link, &port->links, input_link) + pw_link_deactivate(link); + } + spa_list_for_each(port, &this->output_ports, link) { + struct pw_link *link; + spa_list_for_each(link, &port->links, output_link) + pw_link_deactivate(link); + } +} + +static void node_activate(struct pw_node *this) +{ + struct pw_port *port; + + pw_log_debug("node %p: activate", this); + spa_list_for_each(port, &this->input_ports, link) { + struct pw_link *link; + spa_list_for_each(link, &port->links, input_link) + pw_link_activate(link); + } + spa_list_for_each(port, &this->output_ports, link) { + struct pw_link *link; + spa_list_for_each(link, &port->links, output_link) + pw_link_activate(link); + } +} + +/** Set th node state + * \param node a \ref pw_node + * \param state a \ref pw_node_state + * \return 0 on success < 0 on error + * + * Set the state of \a node to \a state. + * + * \memberof pw_node + */ +int pw_node_set_state(struct pw_node *node, enum pw_node_state state) +{ + int res = SPA_RESULT_OK; + struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); + + pw_signal_emit(&node->state_request, node, state); + + pw_log_debug("node %p: set state %s", node, pw_node_state_as_string(state)); + + switch (state) { + case PW_NODE_STATE_CREATING: + return SPA_RESULT_ERROR; + + case PW_NODE_STATE_SUSPENDED: + res = suspend_node(node); + break; + + case PW_NODE_STATE_IDLE: + res = pause_node(node); + break; + + case PW_NODE_STATE_RUNNING: + node_activate(node); + send_clock_update(node); + res = start_node(node); + break; + + case PW_NODE_STATE_ERROR: + break; + } + if (SPA_RESULT_IS_ERROR(res)) + return res; + + pw_work_queue_add(impl->work, + node, res, (pw_work_func_t) on_state_complete, SPA_INT_TO_PTR(state)); + + return res; +} + +/** Update the node state + * \param node a \ref pw_node + * \param state a \ref pw_node_state + * \param error error when \a state is \ref PW_NODE_STATE_ERROR + * + * Update the state of a node. This method is used from inside \a node + * itself. + * + * \memberof pw_node + */ +void pw_node_update_state(struct pw_node *node, enum pw_node_state state, char *error) +{ + enum pw_node_state old; + + old = node->info.state; + if (old != state) { + struct pw_resource *resource; + + pw_log_debug("node %p: update state from %s -> %s", node, + pw_node_state_as_string(old), pw_node_state_as_string(state)); + + if (node->info.error) + free((char*)node->info.error); + node->info.error = error; + node->info.state = state; + + if (state == PW_NODE_STATE_IDLE) + node_deactivate(node); + + pw_signal_emit(&node->state_changed, node, old, state); + + node->info.change_mask |= 1 << 5; + spa_list_for_each(resource, &node->resource_list, link) + pw_node_notify_info(resource, &node->info); + node->info.change_mask = 0; + } +} diff --git a/src/pipewire/node.h b/src/pipewire/node.h new file mode 100644 index 00000000..69eff6f8 --- /dev/null +++ b/src/pipewire/node.h @@ -0,0 +1,179 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_NODE_H__ +#define __PIPEWIRE_NODE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PIPEWIRE_TYPE__Node "PipeWire:Object:Node" +#define PIPEWIRE_TYPE_NODE_BASE PIPEWIRE_TYPE__Node ":" + +#include <spa/clock.h> +#include <spa/node.h> + +#include <pipewire/mem.h> +#include <pipewire/introspect.h> +#include <pipewire/transport.h> + +#include <pipewire/core.h> +#include <pipewire/port.h> +#include <pipewire/link.h> +#include <pipewire/client.h> +#include <pipewire/data-loop.h> + +struct pw_node; + + +#define PW_VERSION_NODE_IMPLEMENTATION 0 + +struct pw_node_implementation { + uint32_t version; + + int (*get_props) (struct pw_node *node, struct spa_props **props); + + int (*set_props) (struct pw_node *node, const struct spa_props *props); + + int (*send_command) (struct pw_node *node, + struct spa_command *command); + + struct pw_port* (*add_port) (struct pw_node *node, + enum pw_direction direction, + uint32_t port_id); + + int (*process_input) (struct pw_node *node); + + int (*process_output) (struct pw_node *node); +}; + +/** \page page_node Node + * + * \section page_node_overview Overview + * + * The node object processes data. The node has a list of + * input and output ports (\ref page_port) on which it + * will receive and send out buffers respectively. + * + * The node wraps an SPA node object. + */ +/** \class pw_node + * + * PipeWire node class. + */ +struct pw_node { + struct pw_core *core; /**< core object */ + struct spa_list link; /**< link in core node_list */ + struct pw_global *global; /**< global for this node */ + + struct pw_resource *owner; /**< owner resource if any */ + struct pw_properties *properties; /**< properties of the node */ + + struct pw_node_info info; /**< introspectable node info */ + + /** Emited when a state change is started */ + PW_SIGNAL(state_request, (struct pw_listener *listener, + struct pw_node *object, enum pw_node_state state)); + /** Emited when a stat change is completed */ + PW_SIGNAL(state_changed, (struct pw_listener *listener, + struct pw_node *object, + enum pw_node_state old, enum pw_node_state state)); + + bool live; /**< if the node is live */ + struct spa_clock *clock; /**< handle to SPA clock if any */ + + struct spa_list resource_list; /**< list of resources for this node */ + + /** Implementation of core node functions */ + const struct pw_node_implementation *implementation; + + /** Emited when the node is initialized */ + PW_SIGNAL(initialized, (struct pw_listener *listener, struct pw_node *object)); + + struct spa_list input_ports; /**< list of input ports */ + struct pw_map input_port_map; /**< map from port_id to port */ + uint32_t n_used_input_links; /**< number of active input links */ + uint32_t idle_used_input_links; /**< number of active input to be idle */ + + struct spa_list output_ports; /**< list of output ports */ + struct pw_map output_port_map; /**< map from port_id to port */ + uint32_t n_used_output_links; /**< number of active output links */ + uint32_t idle_used_output_links; /**< number of active output to be idle */ + + /** Emited when a new port is added */ + PW_SIGNAL(port_added, (struct pw_listener *listener, + struct pw_node *node, struct pw_port *port)); + /** Emited when a port is removed */ + PW_SIGNAL(port_removed, (struct pw_listener *listener, + struct pw_node *node, struct pw_port *port)); + + /** Emited when the node is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_node *object)); + /** Emited when the node is free */ + PW_SIGNAL(free_signal, (struct pw_listener *listener, struct pw_node *object)); + + /** an async operation on the node completed */ + PW_SIGNAL(async_complete, (struct pw_listener *listener, + struct pw_node *node, uint32_t seq, int res)); + + /** an event is emited */ + PW_SIGNAL(event, (struct pw_listener *listener, + struct pw_node *node, struct spa_event *event)); + + struct pw_loop *data_loop; /**< the data loop for this node */ + + struct { + struct spa_graph_scheduler *sched; + struct spa_graph_node node; + } rt; + + void *user_data; /**< extra user data */ + pw_destroy_t destroy; /**< function to clean up the object */ +}; + +/** Create a new node \memberof pw_node */ +struct pw_node * +pw_node_new(struct pw_core *core, /**< the core */ + struct pw_resource *owner, /**< optional owner */ + const char *name, /**< node name */ + struct pw_properties *properties, /**< extra properties */ + size_t user_data_size /**< user data size */); + +/** Complete initialization of the node */ +void pw_node_export(struct pw_node *node); + +/** Destroy a node */ +void pw_node_destroy(struct pw_node *node); + +/** Get a free unused port from the node */ +struct pw_port * +pw_node_get_free_port(struct pw_node *node, enum pw_direction direction); + +/** Change the state of the node */ +int pw_node_set_state(struct pw_node *node, enum pw_node_state state); + +/** Update the state of the node, mostly used by node implementations */ +void pw_node_update_state(struct pw_node *node, enum pw_node_state state, char *error); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_NODE_H__ */ diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c new file mode 100644 index 00000000..5e5ae5f9 --- /dev/null +++ b/src/pipewire/pipewire.c @@ -0,0 +1,346 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <limits.h> +#include <stdio.h> +#include <sys/prctl.h> +#include <pwd.h> +#include <errno.h> +#include <dlfcn.h> + +#include "pipewire/pipewire.h" + +static char **categories = NULL; + +static struct support_info { + void *hnd; + spa_handle_factory_enum_func_t enum_func; + struct spa_support support[4]; + uint32_t n_support; +} support_info; + +static bool +open_support(const char *path, + const char *lib, + struct support_info *info) +{ + char *filename; + + if (asprintf(&filename, "%s/%s.so", path, lib) < 0) + goto no_filename; + + if ((info->hnd = dlopen(filename, RTLD_NOW)) == NULL) { + fprintf(stderr, "can't load %s: %s\n", filename, dlerror()); + goto open_failed; + } + if ((info->enum_func = dlsym(info->hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + fprintf(stderr, "can't find enum function\n"); + goto no_symbol; + } + free(filename); + return true; + + no_filename: + return false; + no_symbol: + dlclose(info->hnd); + open_failed: + free(filename); + return false; +} + +static void * +load_interface(struct support_info *info, + const char *factory_name, + const char *type) +{ + int res; + struct spa_handle *handle; + uint32_t type_id; + const struct spa_handle_factory *factory; + void *iface; + struct spa_type_map *map = NULL; + + factory = pw_get_support_factory(factory_name); + if (factory == NULL) + goto not_found; + + handle = calloc(1, factory->size); + if ((res = spa_handle_factory_init(factory, + handle, NULL, info->support, info->n_support)) < 0) { + fprintf(stderr, "can't make factory instance: %d\n", res); + goto init_failed; + } + + map = pw_get_support_interface(SPA_TYPE__TypeMap); + type_id = map ? spa_type_map_get_id(map, type) : 0; + + if ((res = spa_handle_get_interface(handle, type_id, &iface)) < 0) { + fprintf(stderr, "can't get %s interface %d\n", type, res); + goto interface_failed; + } + return iface; + + interface_failed: + spa_handle_clear(handle); + init_failed: + free(handle); + not_found: + return NULL; +} + +static void configure_debug(const char *str) +{ + char **level; + int n_tokens; + + level = pw_split_strv(str, ":", INT_MAX, &n_tokens); + if (n_tokens > 0) + pw_log_set_level(atoi(level[0])); + + if (n_tokens > 1) + categories = pw_split_strv(level[1], ",", INT_MAX, &n_tokens); +} + +static void configure_support(struct support_info *info) +{ + void *iface; + + iface = load_interface(info, "mapper", SPA_TYPE__TypeMap); + if (iface != NULL) { + info->support[info->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE__TypeMap, iface); + } + + iface = load_interface(info, "logger", SPA_TYPE__Log); + if (iface != NULL) { + info->support[info->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE__Log, iface); + pw_log_set(iface); + } +} + +/** Get a support interface + * \param type the interface type + * \return the interface or NULL when not configured + */ +void *pw_get_support_interface(const char *type) +{ + int i; + + for (i = 0; i < support_info.n_support; i++) { + if (strcmp(support_info.support->type, type) == 0) + return support_info.support->data; + } + return NULL; +} + +const struct spa_handle_factory *pw_get_support_factory(const char *factory_name) +{ + int res; + uint32_t index; + const struct spa_handle_factory *factory; + + for (index = 0;; index++) { + if ((res = support_info.enum_func(&factory, index)) < 0) { + if (res != SPA_RESULT_ENUM_END) + fprintf(stderr, "can't enumerate factories: %d\n", res); + break; + } + if (strcmp(factory->name, factory_name) == 0) + return factory; + } + return NULL; +} + +const struct spa_support *pw_get_support(uint32_t *n_support) +{ + *n_support = support_info.n_support; + return support_info.support; +} + +/** Initialize PipeWire + * + * \param argc pointer to argc + * \param argv pointer to argv + * + * Initialize the PipeWire system, parse and modify any parameters given + * by \a argc and \a argv and set up debugging. + * + * The environment variable \a PIPEWIRE_DEBUG + * + * \memberof pw_pipewire + */ +void pw_init(int *argc, char **argv[]) +{ + const char *str; + + if ((str = getenv("PIPEWIRE_DEBUG"))) + configure_debug(str); + + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) + str = PLUGINDIR; + + if (open_support(str, "support/libspa-support", &support_info)) + configure_support(&support_info); +} + +/** Check if a debug category is enabled + * + * \param name the name of the category to check + * \return true if enabled + * + * Debugging categories can be enabled by using the PIPEWIRE_DEBUG + * environment variable + * + * \memberof pw_pipewire + */ +bool pw_debug_is_category_enabled(const char *name) +{ + int i; + + if (categories == NULL) + return false; + + for (i = 0; categories[i]; i++) { + if (strcmp (categories[i], name) == 0) + return true; + } + return false; +} + +/** Get the application name \memberof pw_pipewire */ +const char *pw_get_application_name(void) +{ + return NULL; +} + +/** Get the program name \memberof pw_pipewire */ +const char *pw_get_prgname(void) +{ + static char tcomm[16 + 1]; + spa_zero(tcomm); + + if (prctl(PR_GET_NAME, (unsigned long) tcomm, 0, 0, 0) == 0) + return tcomm; + + return NULL; +} + +/** Get the user name \memberof pw_pipewire */ +const char *pw_get_user_name(void) +{ + struct passwd *pw; + + if ((pw = getpwuid(getuid()))) + return pw->pw_name; + + return NULL; +} + +/** Get the host name \memberof pw_pipewire */ +const char *pw_get_host_name(void) +{ + static char hname[256]; + + if (gethostname(hname, 256) < 0) + return NULL; + + hname[255] = 0; + return hname; +} + +/** Get the client name + * + * Make a new PipeWire client name that can be used to construct a remote. + * + * \memberof pw_pipewire + */ +char *pw_get_client_name(void) +{ + char *c; + const char *cc; + + if ((cc = pw_get_application_name())) + return strdup(cc); + else if ((cc = pw_get_prgname())) + return strdup(cc); + else { + if (asprintf(&c, "pipewire-pid-%zd", (size_t) getpid()) < 0) + return NULL; + return c; + } +} + +/** Fill remote properties + * \param properties a \ref pw_properties + * + * Fill \a properties with a set of default remote properties. + * + * \memberof pw_pipewire + */ +void pw_fill_remote_properties(struct pw_properties *properties) +{ + if (!pw_properties_get(properties, "application.name")) + pw_properties_set(properties, "application.name", pw_get_application_name()); + + if (!pw_properties_get(properties, "application.prgname")) + pw_properties_set(properties, "application.prgname", pw_get_prgname()); + + if (!pw_properties_get(properties, "application.language")) { + pw_properties_set(properties, "application.language", getenv("LANG")); + } + if (!pw_properties_get(properties, "application.process.id")) { + pw_properties_setf(properties, "application.process.id", "%zd", (size_t) getpid()); + } + if (!pw_properties_get(properties, "application.process.user")) + pw_properties_set(properties, "application.process.user", pw_get_user_name()); + + if (!pw_properties_get(properties, "application.process.host")) + pw_properties_set(properties, "application.process.host", pw_get_host_name()); + + if (!pw_properties_get(properties, "application.process.session_id")) { + pw_properties_set(properties, "application.process.session_id", + getenv("XDG_SESSION_ID")); + } +} + +/** Fill stream properties + * \param properties a \ref pw_properties + * + * Fill \a properties with a set of default stream properties. + * + * \memberof pw_pipewire + */ +void pw_fill_stream_properties(struct pw_properties *properties) +{ +} + +/** Reverse the direction \memberof pw_pipewire */ +enum pw_direction pw_direction_reverse(enum pw_direction direction) +{ + if (direction == PW_DIRECTION_INPUT) + return PW_DIRECTION_OUTPUT; + else if (direction == PW_DIRECTION_OUTPUT) + return PW_DIRECTION_INPUT; + return direction; +} diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h new file mode 100644 index 00000000..5668d9c5 --- /dev/null +++ b/src/pipewire/pipewire.h @@ -0,0 +1,132 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_H__ +#define __PIPEWIRE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/introspect.h> +#include <pipewire/log.h> +#include <pipewire/loop.h> +#include <pipewire/mem.h> +#include <pipewire/thread-loop.h> +#include <pipewire/properties.h> +#include <pipewire/stream.h> +#include <pipewire/utils.h> + +#include <spa/type-map.h> + +/** \mainpage + * + * \section sec_intro Introduction + * + * This document describes the API for the PipeWire multimedia server. + * The API consists of two parts: + * + * \li The client side API (See \subpage page_client_api) + * \li The server side API and tools to build new modules (See + * \subpage page_server_api) + * + * \section sec_errors Error reporting + * + * Functions return either NULL or a negative int error code when an + * error occurs. Error codes are used from the SPA plugin library on + * which PipeWire is built. + * + * Some functions might return asynchronously. The error code for such + * functions is positive and SPA_RESULT_IS_ASYNC() will return true. + * SPA_RESULT_ASYNC_SEQ() can be used to get the unique sequence number + * associated with the async operation. + * + * The object returning the async result code will have some way to + * signal the completion of the async operation (with, for example, a + * callback). The sequence number can be used to see which operation + * completed. + * + * \section sec_logging Logging + * + * The 'PIPEWIRE_DEBUG' environment variable can be used to enable + * more debugging. The format is: + * + * <level>[:<category>,...] + * + * - <level>: specifies the log level: + * + `0`: no logging is enabled + * + `1`: Error logging is enabled + * + `2`: Warnings are enabled + * + `3`: Informational messages are enabled + * + `4`: Debug messages are enabled + * + `5`: Trace messages are enabled. These messages can be logged + * from the realtime threads. + * + * - <category>: Specifies a string category to enable. Many categories + * can be separated by commas. Current categories are: + * + `connection`: to log connection messages + */ + +/** \class pw_pipewire + * + * \brief PipeWire initalization and infrasctructure functions + */ +void +pw_init(int *argc, char **argv[]); + +bool +pw_debug_is_category_enabled(const char *name); + +const char * +pw_get_application_name(void); + +const char * +pw_get_prgname(void); + +const char * +pw_get_user_name(void); + +const char * +pw_get_host_name(void); + +char * +pw_get_client_name(void); + +void +pw_fill_remote_properties(struct pw_properties *properties); + +void +pw_fill_stream_properties(struct pw_properties *properties); + +enum pw_direction +pw_direction_reverse(enum pw_direction direction); + +void * +pw_get_support_interface(const char *type); + +const struct spa_handle_factory * +pw_get_support_factory(const char *factory_name); + +const struct spa_support * +pw_get_support(uint32_t *n_support); + +#ifdef __cplusplus +} +#endif +#endif /* __PIPEWIRE_H__ */ diff --git a/src/pipewire/port.c b/src/pipewire/port.c new file mode 100644 index 00000000..537747ad --- /dev/null +++ b/src/pipewire/port.c @@ -0,0 +1,393 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +#include "pipewire/pipewire.h" +#include "pipewire/port.h" + +/** \cond */ +struct impl { + struct pw_port this; +}; +/** \endcond */ + + +static void port_update_state(struct pw_port *port, enum pw_port_state state) +{ + if (port->state != state) { + pw_log_debug("port %p: state %d -> %d", port, port->state, state); + port->state = state; + pw_signal_emit(&port->state_changed, port); + } +} + +static int schedule_tee_input(struct spa_graph_node *node, void *user_data) +{ + int res; + struct pw_port *this = user_data; + struct spa_graph_port *p; + struct spa_port_io *io = this->rt.mix_port.io; + + if (spa_list_is_empty(&node->ports[SPA_DIRECTION_OUTPUT])) { + io->status = SPA_RESULT_NEED_BUFFER; + res = SPA_RESULT_NEED_BUFFER; + } + else { + spa_list_for_each(p, &node->ports[SPA_DIRECTION_OUTPUT], link) + *p->io = *io; + io->status = SPA_RESULT_OK; + io->buffer_id = SPA_ID_INVALID; + res = SPA_RESULT_HAVE_BUFFER; + } + return res; +} +static int schedule_tee_output(struct spa_graph_node *node, void *user_data) +{ + struct pw_port *this = user_data; + struct spa_graph_port *p; + struct spa_port_io *io = this->rt.mix_port.io; + + spa_list_for_each(p, &node->ports[SPA_DIRECTION_OUTPUT], link) + *io = *p->io; + io->status = SPA_RESULT_NEED_BUFFER; + + return SPA_RESULT_NEED_BUFFER; +} + +static const struct spa_graph_node_methods schedule_tee_node = { + SPA_VERSION_GRAPH_NODE_METHODS, + schedule_tee_input, + schedule_tee_output, +}; + +static int schedule_tee_reuse_buffer(struct spa_graph_port *port, uint32_t buffer_id, void *user_data) +{ + return SPA_RESULT_OK; +} + +static const struct spa_graph_port_methods schedule_tee_port = { + SPA_VERSION_GRAPH_PORT_METHODS, + schedule_tee_reuse_buffer, +}; + +static int schedule_mix_input(struct spa_graph_node *node, void *user_data) +{ + struct pw_port *this = user_data; + struct spa_graph_port *p; + struct spa_port_io *io = this->rt.mix_port.io; + + spa_list_for_each(p, &node->ports[SPA_DIRECTION_INPUT], link) { + *io = *p->io; + p->io->status = SPA_RESULT_OK; + p->io->buffer_id = SPA_ID_INVALID; + } + return SPA_RESULT_HAVE_BUFFER; +} + +static int schedule_mix_output(struct spa_graph_node *node, void *user_data) +{ + struct pw_port *this = user_data; + struct spa_graph_port *p; + struct spa_port_io *io = this->rt.mix_port.io; + + io->status = SPA_RESULT_NEED_BUFFER; + spa_list_for_each(p, &node->ports[SPA_DIRECTION_INPUT], link) + *p->io = *io; + io->buffer_id = SPA_ID_INVALID; + + return SPA_RESULT_NEED_BUFFER; +} + +static const struct spa_graph_node_methods schedule_mix_node = { + SPA_VERSION_GRAPH_NODE_METHODS, + schedule_mix_input, + schedule_mix_output, +}; + +static int schedule_mix_reuse_buffer(struct spa_graph_port *port, uint32_t buffer_id, void *user_data) +{ + return SPA_RESULT_OK; +} +static const struct spa_graph_port_methods schedule_mix_port = { + SPA_VERSION_GRAPH_PORT_METHODS, + schedule_mix_reuse_buffer, +}; + +struct pw_port *pw_port_new(enum pw_direction direction, + uint32_t port_id, + size_t user_data_size) +{ + struct impl *impl; + struct pw_port *this; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + return NULL; + + this = &impl->this; + pw_log_debug("port %p: new", this); + + this->direction = direction; + this->port_id = port_id; + this->state = PW_PORT_STATE_INIT; + this->io.status = SPA_RESULT_OK; + this->io.buffer_id = SPA_ID_INVALID; + + if (user_data_size > 0) + this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void); + + spa_list_init(&this->links); + + pw_signal_init(&this->state_changed); + pw_signal_init(&this->destroy_signal); + + spa_graph_port_init(&this->rt.port, + this->direction, + this->port_id, + 0, + &this->io); + spa_graph_node_init(&this->rt.mix_node); + spa_graph_node_set_methods(&this->rt.mix_node, + this->direction == PW_DIRECTION_INPUT ? + &schedule_mix_node : + &schedule_tee_node, + this); + spa_graph_port_init(&this->rt.mix_port, + pw_direction_reverse(this->direction), + 0, + 0, + &this->io); + spa_graph_port_set_methods(&this->rt.mix_port, + this->direction == PW_DIRECTION_INPUT ? + &schedule_mix_port : + &schedule_tee_port, + this); + return this; +} + +static int do_add_port(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_port *this = user_data; + + spa_graph_port_add(&this->node->rt.node, &this->rt.port); + spa_graph_node_add(this->rt.graph, &this->rt.mix_node); + spa_graph_port_add(&this->rt.mix_node, &this->rt.mix_port); + spa_graph_port_link(&this->rt.port, &this->rt.mix_port); + + return SPA_RESULT_OK; +} + +void pw_port_add(struct pw_port *port, struct pw_node *node) +{ + port->node = node; + + pw_log_debug("port %p: add to node %p", port, node); + if (port->direction == PW_DIRECTION_INPUT) { + spa_list_insert(&node->input_ports, &port->link); + pw_map_insert_at(&node->input_port_map, port->port_id, port); + node->info.n_input_ports++; + node->info.change_mask |= 1 << 1; + } + else { + spa_list_insert(&node->output_ports, &port->link); + pw_map_insert_at(&node->output_port_map, port->port_id, port); + node->info.n_output_ports++; + node->info.change_mask |= 1 << 3; + } + + port->rt.graph = node->rt.sched->graph; + pw_loop_invoke(node->data_loop, do_add_port, SPA_ID_INVALID, 0, NULL, false, port); + + port_update_state(port, PW_PORT_STATE_CONFIGURE); + + pw_signal_emit(&node->port_added, node, port); +} + +static int do_remove_port(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_port *this = user_data; + struct spa_graph_port *p; + + spa_graph_port_unlink(&this->rt.port); + spa_graph_port_remove(&this->rt.port); + + spa_list_for_each(p, &this->rt.mix_node.ports[this->direction], link) + spa_graph_port_remove(p); + + spa_graph_port_remove(&this->rt.mix_port); + spa_graph_node_remove(&this->rt.mix_node); + + return SPA_RESULT_OK; +} + +void pw_port_destroy(struct pw_port *port) +{ + struct pw_node *node = port->node; + + pw_log_debug("port %p: destroy", port); + + pw_signal_emit(&port->destroy_signal, port); + + if (node) { + pw_loop_invoke(port->node->data_loop, do_remove_port, SPA_ID_INVALID, 0, NULL, true, port); + + if (port->direction == PW_DIRECTION_INPUT) { + pw_map_remove(&node->input_port_map, port->port_id); + node->info.n_input_ports--; + } + else { + pw_map_remove(&node->output_port_map, port->port_id); + node->info.n_output_ports--; + } + spa_list_remove(&port->link); + pw_signal_emit(&node->port_removed, node, port); + } + + if (port->destroy) + port->destroy(port); + + free(port); +} + +static int +do_port_pause(struct spa_loop *loop, + bool async, uint32_t seq, size_t size, void *data, void *user_data) +{ + struct pw_port *port = user_data; + + return port->implementation->send_command(port, + &SPA_COMMAND_INIT(port->node->core->type.command_node.Pause)); +} + +int pw_port_enum_formats(struct pw_port *port, + struct spa_format **format, + const struct spa_format *filter, + int32_t index) +{ + return port->implementation->enum_formats(port, format, filter, index); +} + +int pw_port_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format) +{ + int res; + + res = port->implementation->set_format(port, flags, format); + pw_log_debug("port %p: set format %d", port, res); + + if (!SPA_RESULT_IS_ASYNC(res)) { + if (format == NULL) { + port->buffers = NULL; + port->n_buffers = 0; + if (port->allocated) + pw_memblock_free(&port->buffer_mem); + port->allocated = false; + port_update_state (port, PW_PORT_STATE_CONFIGURE); + } + else { + port_update_state (port, PW_PORT_STATE_READY); + } + } + return res; +} + +int pw_port_get_format(struct pw_port *port, const struct spa_format **format) +{ + return port->implementation->get_format(port, format); +} + +int pw_port_get_info(struct pw_port *port, const struct spa_port_info **info) +{ + return port->implementation->get_info(port, info); +} + +int pw_port_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param) +{ + return port->implementation->enum_params(port, index, param); +} + +int pw_port_set_param(struct pw_port *port, struct spa_param *param) +{ + return port->implementation->set_param(port, param); +} + +int pw_port_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers) +{ + int res; + + if (n_buffers == 0 && port->state <= PW_PORT_STATE_READY) + return SPA_RESULT_OK; + + if (n_buffers > 0 && port->state < PW_PORT_STATE_READY) + return SPA_RESULT_NO_FORMAT; + + if (port->state > PW_PORT_STATE_PAUSED) { + pw_loop_invoke(port->node->data_loop, + do_port_pause, 0, 0, NULL, true, port); + port_update_state (port, PW_PORT_STATE_PAUSED); + } + + pw_log_debug("port %p: use %d buffers", port, n_buffers); + res = port->implementation->use_buffers(port, buffers, n_buffers); + + port->buffers = buffers; + port->n_buffers = n_buffers; + if (port->allocated) + pw_memblock_free(&port->buffer_mem); + port->allocated = false; + + if (port->n_buffers == 0) + port_update_state (port, PW_PORT_STATE_READY); + else if (!SPA_RESULT_IS_ASYNC(res)) + port_update_state (port, PW_PORT_STATE_PAUSED); + + return res; +} + +int pw_port_alloc_buffers(struct pw_port *port, + struct spa_param **params, uint32_t n_params, + struct spa_buffer **buffers, uint32_t *n_buffers) +{ + int res; + + if (port->state < PW_PORT_STATE_READY) + return SPA_RESULT_NO_FORMAT; + + if (port->state > PW_PORT_STATE_PAUSED) { + pw_loop_invoke(port->node->data_loop, + do_port_pause, 0, 0, NULL, true, port); + port_update_state (port, PW_PORT_STATE_PAUSED); + } + + pw_log_debug("port %p: alloc %d buffers", port, *n_buffers); + res = port->implementation->alloc_buffers(port, params, n_params, buffers, n_buffers); + + port->buffers = buffers; + port->n_buffers = *n_buffers; + port->allocated = true; + + if (!SPA_RESULT_IS_ASYNC(res)) + port_update_state (port, PW_PORT_STATE_PAUSED); + + return res; +} diff --git a/src/pipewire/port.h b/src/pipewire/port.h new file mode 100644 index 00000000..b56b27f4 --- /dev/null +++ b/src/pipewire/port.h @@ -0,0 +1,174 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_PORT_H__ +#define __PIPEWIRE_PORT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PIPEWIRE_TYPE__Port "PipeWire:Object:Port" +#define PIPEWIRE_TYPE_PORT_BASE PIPEWIRE_TYPE__Port ":" + +#include <spa/node.h> + +#include <pipewire/introspect.h> +#include <pipewire/mem.h> + +#include <pipewire/core.h> +#include <pipewire/link.h> + +enum pw_port_state { + PW_PORT_STATE_ERROR = -1, + PW_PORT_STATE_INIT = 0, + PW_PORT_STATE_CONFIGURE = 1, + PW_PORT_STATE_READY = 2, + PW_PORT_STATE_PAUSED = 3, + PW_PORT_STATE_STREAMING = 4, +}; + +struct pw_port; + +#define PW_VERSION_PORT_IMPLEMENTATION 0 + +struct pw_port_implementation { + uint32_t version; + + int (*enum_formats) (struct pw_port *port, + struct spa_format **format, + const struct spa_format *filter, + int32_t index); + + int (*set_format) (struct pw_port *port, uint32_t flags, struct spa_format *format); + + int (*get_format) (struct pw_port *port, const struct spa_format **format); + + int (*get_info) (struct pw_port *port, const struct spa_port_info **info); + + int (*enum_params) (struct pw_port *port, uint32_t index, struct spa_param **param); + + int (*set_param) (struct pw_port *port, struct spa_param *param); + + int (*use_buffers) (struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers); + + int (*alloc_buffers) (struct pw_port *port, + struct spa_param **params, uint32_t n_params, + struct spa_buffer **buffers, uint32_t *n_buffers); + int (*reuse_buffer) (struct pw_port *port, uint32_t buffer_id); + + int (*send_command) (struct pw_port *port, struct spa_command *command); +}; + +/** \page page_port Port + * + * \section page_node_overview Overview + * + * A port can be used to link two nodes. + */ +/** \class pw_port + * + * The port object + */ +struct pw_port { + struct spa_list link; /**< link in node port_list */ + + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_port *)); + + struct pw_node *node; /**< owner node */ + + enum pw_direction direction; /**< port direction */ + uint32_t port_id; /**< port id */ + + enum pw_port_state state; /**< state of the port */ + /** Emited when the port state changes */ + PW_SIGNAL(state_changed, (struct pw_listener *listener, struct pw_port *port)); + + const struct pw_port_implementation *implementation; + + struct spa_port_io io; /**< io area of the port */ + + bool allocated; /**< if buffers are allocated */ + struct pw_memblock buffer_mem; /**< allocated buffer memory */ + struct spa_buffer **buffers; /**< port buffers */ + uint32_t n_buffers; /**< number of port buffers */ + + struct spa_list links; /**< list of \ref pw_link */ + + void *mix; /**< optional port buffer mix/split */ + + struct { + struct spa_graph *graph; + struct spa_graph_port port; + struct spa_graph_port mix_port; + struct spa_graph_node mix_node; + } rt; /**< data only accessed from the data thread */ + + void *user_data; /**< extra user data */ + pw_destroy_t destroy; /**< function to clean up the object */ +}; + +/** Create a new port \memberof pw_port + * \return a newly allocated port */ +struct pw_port * +pw_port_new(enum pw_direction direction, + uint32_t port_id, + size_t user_data_size); + +/** Add a port to a node \memberof pw_port */ +void pw_port_add(struct pw_port *port, struct pw_node *node); + +/** Destroy a port \memberof pw_port */ +void pw_port_destroy(struct pw_port *port); + +/** Get the current format on a port \memberof pw_port */ +int pw_port_enum_formats(struct pw_port *port, + struct spa_format **format, + const struct spa_format *filter, + int32_t index); + +/** Set a format on a port \memberof pw_port */ +int pw_port_set_format(struct pw_port *port, uint32_t flags, struct spa_format *format); + +/** Get the current format on a port \memberof pw_port */ +int pw_port_get_format(struct pw_port *port, const struct spa_format **format); + +/** Get the info on a port \memberof pw_port */ +int pw_port_get_info(struct pw_port *port, const struct spa_port_info **info); + +/** Enumerate the port parameters \memberof pw_port */ +int pw_port_enum_params(struct pw_port *port, uint32_t index, struct spa_param **param); + +/** Set a port parameter \memberof pw_port */ +int pw_port_set_param(struct pw_port *port, struct spa_param *param); + +/** Use buffers on a port \memberof pw_port */ +int pw_port_use_buffers(struct pw_port *port, struct spa_buffer **buffers, uint32_t n_buffers); + +/** Allocate memory for buffers on a port \memberof pw_port */ +int pw_port_alloc_buffers(struct pw_port *port, + struct spa_param **params, uint32_t n_params, + struct spa_buffer **buffers, uint32_t *n_buffers); + + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_PORT_H__ */ diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c new file mode 100644 index 00000000..374d18e9 --- /dev/null +++ b/src/pipewire/properties.c @@ -0,0 +1,318 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "pipewire/pipewire.h" +#include "pipewire/properties.h" + +/** \cond */ +struct properties { + struct pw_properties this; + + struct pw_array items; +}; +/** \endcond */ + +static void add_func(struct pw_properties *this, char *key, char *value) +{ + struct spa_dict_item *item; + struct properties *impl = SPA_CONTAINER_OF(this, struct properties, this); + + item = pw_array_add(&impl->items, sizeof(struct spa_dict_item)); + item->key = key; + item->value = value; + + this->dict.items = impl->items.data; + this->dict.n_items = pw_array_get_len(&impl->items, struct spa_dict_item); +} + +static void clear_item(struct spa_dict_item *item) +{ + free((char *) item->key); + free((char *) item->value); +} + +static int find_index(struct pw_properties *this, const char *key) +{ + struct properties *impl = SPA_CONTAINER_OF(this, struct properties, this); + int i, len = pw_array_get_len(&impl->items, struct spa_dict_item); + + for (i = 0; i < len; i++) { + struct spa_dict_item *item = + pw_array_get_unchecked(&impl->items, i, struct spa_dict_item); + if (strcmp(item->key, key) == 0) + return i; + } + return -1; +} + +/** Make a new properties object + * + * \param key a first key + * \param ... value and more keys NULL terminated + * \return a newly allocated properties object + * + * \memberof pw_properties + */ +struct pw_properties *pw_properties_new(const char *key, ...) +{ + struct properties *impl; + va_list varargs; + const char *value; + + impl = calloc(1, sizeof(struct properties)); + if (impl == NULL) + return NULL; + + pw_array_init(&impl->items, 16); + + va_start(varargs, key); + while (key != NULL) { + value = va_arg(varargs, char *); + add_func(&impl->this, strdup(key), strdup(value)); + key = va_arg(varargs, char *); + } + va_end(varargs); + + return &impl->this; +} + +/** Make a new properties object from the given dictionary + * + * \param dict a dictionary. keys and values are copied + * \return a new properties object + * + * \memberof pw_properties + */ +struct pw_properties *pw_properties_new_dict(const struct spa_dict *dict) +{ + uint32_t i; + struct properties *impl; + + impl = calloc(1, sizeof(struct properties)); + if (impl == NULL) + return NULL; + + pw_array_init(&impl->items, 16); + + for (i = 0; i < dict->n_items; i++) + add_func(&impl->this, strdup(dict->items[i].key), strdup(dict->items[i].value)); + + return &impl->this; +} + +/** Copy a properties object + * + * \param properties properties to copy + * \return a new properties object + * + * \memberof pw_properties + */ +struct pw_properties *pw_properties_copy(struct pw_properties *properties) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + struct pw_properties *copy; + struct spa_dict_item *item; + + copy = pw_properties_new(NULL, NULL); + if (copy == NULL) + return NULL; + + pw_array_for_each(item, &impl->items) + add_func(copy, strdup(item->key), strdup(item->value)); + + return copy; +} + +/** Merge properties into one + * + * \param oldprops properties to merge into + * \param newprops properties to merge + * \return a newly allocated \ref pw_properties + * + * A new \ref pw_properties is allocated and the properties of + * \a oldprops and \a newprops are copied into it in that order. + * + * \memberof pw_properties + */ +struct pw_properties *pw_properties_merge(struct pw_properties *oldprops, + struct pw_properties *newprops) +{ + struct pw_properties *res = NULL; + + if (oldprops == NULL) { + if (newprops == NULL) + res = NULL; + else + res = pw_properties_copy(newprops); + } else if (newprops == NULL) { + res = pw_properties_copy(oldprops); + } else { + const char *key; + void *state = NULL; + + res = pw_properties_copy(oldprops); + if (res == NULL) + return NULL; + + while ((key = pw_properties_iterate(newprops, &state))) { + pw_properties_set(res, key, pw_properties_get(newprops, key)); + } + } + return res; +} + +/** Free a properties object + * + * \param properties the properties to free + * + * \memberof pw_properties + */ +void pw_properties_free(struct pw_properties *properties) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + struct spa_dict_item *item; + + pw_array_for_each(item, &impl->items) + clear_item(item); + + pw_array_clear(&impl->items); + free(impl); +} + +static void do_replace(struct pw_properties *properties, char *key, char *value) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + int index = find_index(properties, key); + + if (index == -1) { + add_func(properties, key, value); + } else { + struct spa_dict_item *item = + pw_array_get_unchecked(&impl->items, index, struct spa_dict_item); + + clear_item(item); + if (value == NULL) { + struct spa_dict_item *other = pw_array_get_unchecked(&impl->items, + pw_array_get_len + (&impl->items, + struct spa_dict_item) + - 1, + struct spa_dict_item); + item->key = other->key; + item->value = other->value; + impl->items.size -= sizeof(struct spa_dict_item); + } else { + item->key = key; + item->value = value; + } + } +} + +/** Set a property value + * + * \param properties the properties to change + * \param key a key + * \param value a value or NULL to remove the key + * + * Set the property in \a properties with \a key to \a value. Any previous value + * of \a key will be overwritten. When \a value is NULL, the key will be + * removed. + * + * \memberof pw_properties + */ +void pw_properties_set(struct pw_properties *properties, const char *key, const char *value) +{ + do_replace(properties, strdup(key), value ? strdup(value) : NULL); +} + +/** Set a property value by format + * + * \param properties a \ref pw_properties + * \param key a key + * \param format a value + * \param ... extra arguments + * + * Set the property in \a properties with \a key to the value in printf style \a format + * Any previous value of \a key will be overwritten. + * + * \memberof pw_properties + */ +void pw_properties_setf(struct pw_properties *properties, const char *key, const char *format, ...) +{ + va_list varargs; + char *value; + + va_start(varargs, format); + vasprintf(&value, format, varargs); + va_end(varargs); + + do_replace(properties, strdup(key), value); +} + +/** Get a property + * + * \param properties a \ref pw_properties + * \param key a key + * \return the property for \a key or NULL when the key was not found + * + * Get the property in \a properties with \a key. + * + * \memberof pw_properties + */ +const char *pw_properties_get(struct pw_properties *properties, const char *key) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + int index = find_index(properties, key); + + if (index == -1) + return NULL; + + return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->value; +} + +/** Iterate property values + * + * \param properties a \ref pw_properties + * \param state state + * \return The next key or NULL when there are no more keys to iterate. + * + * Iterate over \a properties, returning each key in turn. \a state should point + * to a pointer holding NULL to get the first element and will be updated + * after each iteration. When NULL is returned, all elements have been + * iterated. + * + * \memberof pw_properties + */ +const char *pw_properties_iterate(struct pw_properties *properties, void **state) +{ + struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this); + uint32_t index; + + if (*state == NULL) + index = 0; + else + index = SPA_PTR_TO_INT(*state); + + if (!pw_array_check_index(&impl->items, index, struct spa_dict_item)) + return NULL; + + *state = SPA_INT_TO_PTR(index + 1); + + return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->key; +} diff --git a/src/pipewire/properties.h b/src/pipewire/properties.h new file mode 100644 index 00000000..9d79e8a0 --- /dev/null +++ b/src/pipewire/properties.h @@ -0,0 +1,74 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_PROPERTIES_H__ +#define __PIPEWIRE_PROPERTIES_H__ + +#ifdef __cplusplus +//extern "C" { +#endif + +#include <spa/dict.h> + +/** \class pw_properties + * + * \brief A collection of key/value pairs + * + * Properties are used to pass around arbitrary key/value pairs. + * Both keys and values are strings which keeps things simple. + * Encoding of arbitrary values should be done by using a string + * serialization such as base64 for binary blobs. + */ +struct pw_properties { + struct spa_dict dict; +}; + +struct pw_properties * +pw_properties_new(const char *key, ...); + +struct pw_properties * +pw_properties_new_dict(const struct spa_dict *dict); + +struct pw_properties * +pw_properties_copy(struct pw_properties *properties); + +struct pw_properties * +pw_properties_merge(struct pw_properties *oldprops, + struct pw_properties *newprops); + +void +pw_properties_free(struct pw_properties *properties); + +void +pw_properties_set(struct pw_properties *properties, const char *key, const char *value); + +void +pw_properties_setf(struct pw_properties *properties, + const char *key, const char *format, ...) SPA_PRINTF_FUNC(3, 4); +const char * +pw_properties_get(struct pw_properties *properties, const char *key); + +const char * +pw_properties_iterate(struct pw_properties *properties, void **state); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_PROPERTIES_H__ */ diff --git a/src/pipewire/protocol.c b/src/pipewire/protocol.c new file mode 100644 index 00000000..6fcc77d7 --- /dev/null +++ b/src/pipewire/protocol.c @@ -0,0 +1,96 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <pipewire/protocol.h> + +static struct impl { + bool init; + struct spa_list protocol_list; +} protocols; + +static struct impl *get_impl(void) +{ + if (!protocols.init) { + spa_list_init(&protocols.protocol_list); + protocols.init = true; + } + return &protocols; +} + +struct pw_protocol *pw_protocol_get(const char *name) +{ + struct impl *impl = get_impl(); + struct pw_protocol *protocol; + + spa_list_for_each(protocol, &impl->protocol_list, link) { + if (strcmp(protocol->name, name) == 0) + return protocol; + } + + protocol = calloc(1, sizeof(struct pw_protocol)); + protocol->name = name; + spa_list_init(&protocol->iface_list); + spa_list_init(&protocol->connection_list); + spa_list_init(&protocol->listener_list); + spa_list_insert(impl->protocol_list.prev, &protocol->link); + + pw_log_info("Created protocol %s", name); + + return protocol; +} + +void +pw_protocol_add_interfaces(struct pw_protocol *protocol, + const struct pw_interface *client_iface, + const struct pw_interface *server_iface) +{ + struct pw_protocol_iface *iface; + const char *type; + uint32_t version; + + iface = calloc(1, sizeof(struct pw_protocol_iface)); + iface->client_iface = client_iface; + iface->server_iface = server_iface; + + spa_list_insert(protocol->iface_list.prev, &iface->link); + + type = client_iface ? client_iface->type : server_iface->type; + version = client_iface ? client_iface->version : server_iface->version; + + pw_log_info("Add iface %s:%d to protocol %s", type, version, protocol->name); +} + +const struct pw_interface * +pw_protocol_get_interface(struct pw_protocol *protocol, + const char *type, + bool server) +{ + struct pw_protocol_iface *protocol_iface; + + if (protocol == NULL) + return NULL; + + spa_list_for_each(protocol_iface, &protocol->iface_list, link) { + const struct pw_interface *iface = server ? protocol_iface->server_iface : + protocol_iface->client_iface; + if (strcmp(iface->type, type) == 0) + return iface; + } + return NULL; +} diff --git a/src/pipewire/protocol.h b/src/pipewire/protocol.h new file mode 100644 index 00000000..2c79deb1 --- /dev/null +++ b/src/pipewire/protocol.h @@ -0,0 +1,94 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_PROTOCOL_H__ +#define __PIPEWIRE_PROTOCOL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/defs.h> +#include <spa/list.h> + +#include <pipewire/type.h> +#include <pipewire/properties.h> + +#define PW_TYPE_PROTOCOL__Native PW_TYPE_PROTOCOL_BASE "Native" + +struct pw_protocol_connection { + struct spa_list link; + struct pw_remote *remote; + + int (*connect) (struct pw_protocol_connection *conn); + int (*connect_fd) (struct pw_protocol_connection *conn, int fd); + int (*disconnect) (struct pw_protocol_connection *conn); + int (*destroy) (struct pw_protocol_connection *conn); +}; + +struct pw_protocol_listener { + struct spa_list link; + struct pw_core *core; + + int (*destroy) (struct pw_protocol_listener *listen); +}; + +struct pw_protocol_iface { + struct spa_list link; + const struct pw_interface *client_iface; + const struct pw_interface *server_iface; +}; + +struct pw_protocol { + struct spa_list link; + const char *name; + struct spa_list iface_list; + struct spa_list connection_list; + struct spa_list listener_list; + + struct pw_protocol_connection * (*new_connection) (struct pw_protocol *protocol, + struct pw_remote *remote, + struct pw_properties *properties); + struct pw_protocol_listener * (*add_listener) (struct pw_protocol *protocol, + struct pw_core *core, + struct pw_properties *properties); + void *protocol_private; +}; + +struct pw_protocol *pw_protocol_get(const char *name); + +/** \class pw_protocol + * + * \brief Manages protocols and their implementation + */ +void +pw_protocol_add_interfaces(struct pw_protocol *protocol, + const struct pw_interface *client_iface, + const struct pw_interface *server_iface); + +const struct pw_interface * +pw_protocol_get_interface(struct pw_protocol *protocol, + const char *type, + bool server); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_PROTOCOL_H__ */ diff --git a/src/pipewire/proxy.c b/src/pipewire/proxy.c new file mode 100644 index 00000000..6687a879 --- /dev/null +++ b/src/pipewire/proxy.c @@ -0,0 +1,122 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <pipewire/log.h> +#include <pipewire/proxy.h> +#include <pipewire/core.h> + +/** \cond */ +struct proxy { + struct pw_proxy this; +}; +/** \endcond */ + +/** Create a proxy object with a given id and type + * + * \param remote Remote object + * \param id Id of the new object, SPA_ID_INVALID will choose a new id + * \param type Type of the proxy object + * \return A newly allocated proxy object or NULL on failure + * + * This function creates a new proxy object with the supplied id and type. The + * proxy object will have an id assigned from the client id space. + * + * \sa pw_remote + * + * \memberof pw_proxy + */ +struct pw_proxy *pw_proxy_new(struct pw_remote *remote, + uint32_t id, uint32_t type, + size_t user_data_size) +{ + struct proxy *impl; + struct pw_proxy *this; + + impl = calloc(1, sizeof(struct proxy) + user_data_size); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->remote = remote; + this->type = type; + + pw_signal_init(&this->destroy_signal); + + if (id == SPA_ID_INVALID) { + id = pw_map_insert_new(&remote->objects, this); + } else if (!pw_map_insert_at(&remote->objects, id, this)) + goto in_use; + + this->id = id; + + if (user_data_size > 0) + this->user_data = SPA_MEMBER(impl, sizeof(struct proxy), void); + + this->iface = pw_protocol_get_interface(remote->protocol, + spa_type_map_get_type(remote->core->type.map, type), + false); + + spa_list_insert(&this->remote->proxy_list, &this->link); + + pw_log_debug("proxy %p: new %u, remote %p", this, this->id, remote); + + return this; + + in_use: + pw_log_error("proxy %p: id %u in use for remote %p", this, id, remote); + free(impl); + return NULL; +} + +int pw_proxy_set_implementation(struct pw_proxy *proxy, + void *object, + uint32_t version, + const void *implementation, + pw_destroy_t destroy) +{ + proxy->object = object; + proxy->version = version; + proxy->implementation = implementation; + proxy->destroy = destroy; + return SPA_RESULT_OK; +} + +/** Destroy a proxy object + * + * \param proxy Proxy object to destroy + * + * \note This is normally called by \ref pw_remote when the server + * decides to destroy the server side object + * \memberof pw_proxy + */ +void pw_proxy_destroy(struct pw_proxy *proxy) +{ + struct proxy *impl = SPA_CONTAINER_OF(proxy, struct proxy, this); + + pw_log_debug("proxy %p: destroy %u", proxy, proxy->id); + pw_signal_emit(&proxy->destroy_signal, proxy); + + pw_map_remove(&proxy->remote->objects, proxy->id); + spa_list_remove(&proxy->link); + + if (proxy->destroy) + proxy->destroy(proxy); + + free(impl); +} diff --git a/src/pipewire/proxy.h b/src/pipewire/proxy.h new file mode 100644 index 00000000..66553576 --- /dev/null +++ b/src/pipewire/proxy.h @@ -0,0 +1,132 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_PROXY_H__ +#define __PIPEWIRE_PROXY_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/type.h> +#include <pipewire/utils.h> +#include <pipewire/remote.h> + +/** \page page_proxy Proxy + * + * \section sec_page_proxy_overview Overview + * + * The proxy object is a client side representation of a resource + * that lives on the server. + * + * It is used to communicate with the server side object. + * + * \section sec_page_proxy_create Create + * + * A client first creates a new proxy object with pw_proxy_new(). A + * type must be provided for this object. + * + * The protocol of the context will usually install an interface to + * translate method calls and events to the wire format. + * + * The creator of the proxy will usually also install an event + * implementation of the particular object type. + * + * \section sec_page_proxy_bind Bind + * + * To actually use the proxy object, one needs to create a server + * side resource for it. This can be done by, for example, binding + * to a global object or by calling a method that creates and binds + * to a new remote object. In all cases, the local id is passed to + * the server and is used to create a resource with the same id. + * + * \section sec_page_proxy_methods Methods + * + * To call a method on the proxy use the interface methods. Calling + * any interface method will result in a request to the server to + * perform the requested action on the corresponding resource. + * + * \section sec_page_proxy_events Events + * + * Events send from the server to the proxy will be demarshalled by + * the protocol and will then result in a call to the installed + * implementation of the proxy. + * + * \section sec_page_proxy_destroy Destroy + * + * Use pw_proxy_destroy() to destroy the client side object. This + * is usually done automatically when the server removes the resource + * associated to the proxy. + */ + +/** \class pw_proxy + * + * \brief Represents an object on the client side. + * + * A pw_proxy acts as a client side proxy to an object existing in the + * pipewire server. The proxy is responsible for converting interface functions + * invoked by the client to PipeWire messages. Events will call the handlers + * set in implementation. + * + * See \ref page_proxy + */ +struct pw_proxy { + struct pw_remote *remote; /**< the owner remote of this proxy */ + struct spa_list link; /**< link in the remote */ + + uint32_t id; /**< client side id */ + uint32_t type; /**< object type id */ + const struct pw_interface *iface; /**< methods/events marshal/demarshal functions */ + + void *object; /**< client side object */ + uint32_t version; + const void *implementation; /**< event handler implementation */ + pw_destroy_t destroy; /**< optional destroy function to clean up the object */ + + /** destroy is emited when the proxy is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_proxy *proxy)); + + void *user_data; /**< extra user data */ +}; + +struct pw_proxy * +pw_proxy_new(struct pw_remote *remote, + uint32_t id, + uint32_t type, + size_t user_data_size); + +int +pw_proxy_set_implementation(struct pw_proxy *proxy, + void *object, + uint32_t version, + const void *implementation, + pw_destroy_t destroy); + +void pw_proxy_destroy(struct pw_proxy *proxy); + +#define pw_proxy_notify(p,type,event,...) ((type*) (p)->implementation)->event(p, __VA_ARGS__) +#define pw_proxy_notify_na(p,type,event) ((type*) (p)->implementation)->event(p) +#define pw_proxy_do(p,type,method,...) ((type*) (p)->iface->methods)->method(p, __VA_ARGS__) +#define pw_proxy_do_na(p,type,method) ((type*) (p)->iface->methods)->method(p) + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_PROXY_H__ */ diff --git a/src/pipewire/remote.c b/src/pipewire/remote.c new file mode 100644 index 00000000..c49ec75c --- /dev/null +++ b/src/pipewire/remote.c @@ -0,0 +1,293 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#include "pipewire/pipewire.h" +#include "pipewire/introspect.h" +#include "pipewire/interfaces.h" +#include "pipewire/remote.h" +#include "pipewire/core.h" +#include "pipewire/module.h" + +#include <spa/lib/debug.h> + +/** \cond */ +struct remote { + struct pw_remote this; +}; +/** \endcond */ + +const char *pw_remote_state_as_string(enum pw_remote_state state) +{ + switch (state) { + case PW_REMOTE_STATE_ERROR: + return "error"; + case PW_REMOTE_STATE_UNCONNECTED: + return "unconnected"; + case PW_REMOTE_STATE_CONNECTING: + return "connecting"; + case PW_REMOTE_STATE_CONNECTED: + return "connected"; + } + return "invalid-state"; +} + +static void +remote_update_state(struct pw_remote *remote, enum pw_remote_state state, const char *fmt, ...) +{ + if (remote->state != state) { + if (remote->error) + free(remote->error); + + if (fmt) { + va_list varargs; + + va_start(varargs, fmt); + if (vasprintf(&remote->error, fmt, varargs) < 0) { + pw_log_debug("remote %p: error formating message: %m", remote); + remote->error = NULL; + } + va_end(varargs); + } else { + remote->error = NULL; + } + pw_log_debug("remote %p: update state from %s -> %s (%s)", remote, + pw_remote_state_as_string(remote->state), + pw_remote_state_as_string(state), remote->error); + + remote->state = state; + pw_signal_emit(&remote->state_changed, remote); + } +} + +static void core_event_info(void *object, struct pw_core_info *info) +{ + struct pw_proxy *proxy = object; + struct pw_remote *this = proxy->remote; + + pw_log_debug("got core info"); + this->info = pw_core_info_update(this->info, info); + pw_signal_emit(&this->info_changed, this); +} + +static void core_event_done(void *object, uint32_t seq) +{ + struct pw_proxy *proxy = object; + struct pw_remote *this = proxy->remote; + + pw_log_debug("core event done %d", seq); + if (seq == 0) + remote_update_state(this, PW_REMOTE_STATE_CONNECTED, NULL); + + pw_signal_emit(&this->sync_reply, this, seq); +} + +static void core_event_error(void *object, uint32_t id, int res, const char *error, ...) +{ + struct pw_proxy *proxy = object; + struct pw_remote *this = proxy->remote; + remote_update_state(this, PW_REMOTE_STATE_ERROR, error); +} + +static void core_event_remove_id(void *object, uint32_t id) +{ + struct pw_proxy *core_proxy = object; + struct pw_remote *this = core_proxy->remote; + struct pw_proxy *proxy; + + proxy = pw_map_lookup(&this->objects, id); + if (proxy) { + pw_log_debug("remote %p: object remove %u", this, id); + pw_proxy_destroy(proxy); + } +} + +static void +core_event_update_types(void *object, uint32_t first_id, uint32_t n_types, const char **types) +{ + struct pw_proxy *proxy = object; + struct pw_remote *this = proxy->remote; + int i; + + for (i = 0; i < n_types; i++, first_id++) { + uint32_t this_id = spa_type_map_get_id(this->core->type.map, types[i]); + if (!pw_map_insert_at(&this->types, first_id, PW_MAP_ID_TO_PTR(this_id))) + pw_log_error("can't add type for client"); + } +} + +static const struct pw_core_events core_events = { + &core_event_update_types, + &core_event_done, + &core_event_error, + &core_event_remove_id, + &core_event_info, +}; + +struct pw_remote *pw_remote_new(struct pw_core *core, + struct pw_properties *properties) +{ + struct remote *impl; + struct pw_remote *this; + + impl = calloc(1, sizeof(struct remote)); + if (impl == NULL) + return NULL; + + this = &impl->this; + pw_log_debug("remote %p: new", impl); + + this->core = core; + + if (properties == NULL) + properties = pw_properties_new(NULL, NULL); + if (properties == NULL) + goto no_mem; + + pw_fill_remote_properties(properties); + this->properties = properties; + + this->state = PW_REMOTE_STATE_UNCONNECTED; + + pw_map_init(&this->objects, 64, 32); + pw_map_init(&this->types, 64, 32); + + spa_list_init(&this->proxy_list); + spa_list_init(&this->stream_list); + + pw_signal_init(&this->info_changed); + pw_signal_init(&this->sync_reply); + pw_signal_init(&this->state_changed); + pw_signal_init(&this->destroy_signal); + + pw_module_load(core, "libpipewire-module-protocol-native", NULL, NULL); + pw_module_load(core, "libpipewire-module-client-node", NULL, NULL); + this->protocol = pw_protocol_get(PW_TYPE_PROTOCOL__Native); + if (this->protocol == NULL || this->protocol->new_connection == NULL) + goto no_protocol; + + this->conn = this->protocol->new_connection(this->protocol, this, properties); + if (this->conn == NULL) + goto no_connection; + + spa_list_insert(core->remote_list.prev, &this->link); + + return this; + + no_connection: + no_protocol: + pw_properties_free(properties); + no_mem: + free(impl); + return NULL; +} + +void pw_remote_destroy(struct pw_remote *remote) +{ + struct remote *impl = SPA_CONTAINER_OF(remote, struct remote, this); + struct pw_proxy *proxy, *t2; + + pw_log_debug("remote %p: destroy", remote); + pw_signal_emit(&remote->destroy_signal, remote); + + if (remote->state != PW_REMOTE_STATE_UNCONNECTED) + pw_remote_disconnect(remote); + + remote->conn->destroy(remote->conn); + + spa_list_for_each_safe(proxy, t2, &remote->proxy_list, link) + pw_proxy_destroy(proxy); + + pw_map_clear(&remote->objects); + pw_map_clear(&remote->types); + + spa_list_remove(&remote->link); + + if (remote->info) + pw_core_info_free (remote->info); + + if (remote->properties) + pw_properties_free(remote->properties); + free(remote->error); + free(impl); +} + +static int do_connect(struct pw_remote *remote) +{ + remote->core_proxy = pw_proxy_new(remote, 0, remote->core->type.core, 0); + if (remote->core_proxy == NULL) + goto no_proxy; + + pw_proxy_set_implementation(remote->core_proxy, remote, PW_VERSION_CORE, + &core_events, NULL); + + pw_core_do_client_update(remote->core_proxy, &remote->properties->dict); + pw_core_do_sync(remote->core_proxy, 0); + + return 0; + + no_proxy: + remote->conn->disconnect(remote->conn); + remote_update_state(remote, PW_REMOTE_STATE_ERROR, "can't connect: no memory"); + return -1; +} + +int pw_remote_connect(struct pw_remote *remote) +{ + int res; + + remote_update_state(remote, PW_REMOTE_STATE_CONNECTING, NULL); + + if ((res = remote->conn->connect(remote->conn)) < 0) { + remote_update_state(remote, PW_REMOTE_STATE_ERROR, "connect failed"); + return res; + } + + return do_connect(remote); +} + +int pw_remote_connect_fd(struct pw_remote *remote, int fd) +{ + int res; + + remote_update_state(remote, PW_REMOTE_STATE_CONNECTING, NULL); + + if ((res = remote->conn->connect_fd(remote->conn, fd)) < 0) { + remote_update_state(remote, PW_REMOTE_STATE_ERROR, "connect_fd failed"); + return res; + } + + return do_connect(remote); +} + +void pw_remote_disconnect(struct pw_remote *remote) +{ + remote->conn->disconnect(remote->conn); + + if (remote->core_proxy) + pw_proxy_destroy(remote->core_proxy); + remote->core_proxy = NULL; + + remote_update_state(remote, PW_REMOTE_STATE_UNCONNECTED, NULL); +} diff --git a/src/pipewire/remote.h b/src/pipewire/remote.h new file mode 100644 index 00000000..5a11fc59 --- /dev/null +++ b/src/pipewire/remote.h @@ -0,0 +1,181 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_REMOTE_H__ +#define __PIPEWIRE_REMOTE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +struct pw_remote; + +#include <pipewire/map.h> +#include <pipewire/loop.h> +#include <pipewire/properties.h> +#include <pipewire/protocol.h> +#include <pipewire/proxy.h> +#include <pipewire/type.h> +#include <pipewire/core.h> + +/** \page page_remote_api Remote API + * + * \section sec_remote_api_overview Overview + * + * The remote API allows you to connect to a remote PipeWire and + * perform actions on the PipeWire graph. This includes + * + * \li introspecting the objects on the server + * \li Creating nodes + * \li Linking nodes on their ports + * \li providing media to the server for playback or consumption + * \li retrieving media from the server + * + * \section sec_remote_api_loop Event Loop Abstraction + * + * Most API is asynchronous and based around an event loop. Methods will + * start an operation which will cause a state change of the \ref pw_context + * object. Connect to the state_changed signal to be notified of these + * state changes. + * + * The most convenient way to deal with the asynchronous calls is probably + * with the thread loop (See \subpage page_thread_loop for more details). + * + * \subsection ssec_remote_api_proxy Proxy + * + * Proxies are local representations of remote resources. They + * allow communication between local and remote objects. + * + * The \ref pw_remote maintains a list of all proxies, including a core + * proxy that is used to get access to other proxy objects. + * + * See also \subpage page_proxy + * + * \section sec_remote_api_remote Remote + * + * \subsection ssec_remote_create Create + * + * To create a new remote use pw_remote_new(). You will + * need to pass a \ref pw_core implementation for event and data loop. + * + * A typical loop would be created with pw_thread_loop_new() but + * other implementation are possible. + * + * You will also need to pass properties for the remote. Use + * pw_fill_remote_properties() to get a default set of properties. + * + * After creating the remote, you can track the state of the remote + * by listening for the state_changed signal. + * + * \subsection ssec_remote_api_remote_connect Connecting + * + * A remote must be connected to a server before any operation can be + * issued. Calling pw_remote_connect() will initiate the connection + * procedure. + * + * When connecting, the remote will automatically create a core + * proxy to get access to the registry and types. + * + * \subsection ssec_remote_api_remote_disconnect Disconnect + * + * Use pw_remote_disconnect() to disconnect from the remote. + */ + +/** \enum pw_remote_state The state of a \ref pw_remote \memberof pw_remote */ +enum pw_remote_state { + PW_REMOTE_STATE_ERROR = -1, /**< remote is in error */ + PW_REMOTE_STATE_UNCONNECTED = 0, /**< not connected */ + PW_REMOTE_STATE_CONNECTING = 1, /**< connecting to remote PipeWire */ + PW_REMOTE_STATE_CONNECTED = 2, /**< remote is connected and ready */ +}; + +/** Convert a \ref pw_remote_state to a readable string \memberof pw_remote */ +const char *pw_remote_state_as_string(enum pw_remote_state state); + +/** \class pw_remote + * + * \brief Represents a connection with the PipeWire server + * + * a \ref pw_remote is created and used to connect to the server. + * A \ref pw_proxy for the core object will automatically be created + * when connecting. + * + * See also \ref page_client_api + */ +struct pw_remote { + struct pw_core *core; /**< core */ + struct spa_list link; /**< link in core remote_list */ + struct pw_properties *properties; /**< extra properties */ + + struct pw_proxy *core_proxy; /**< proxy for the core object */ + struct pw_map objects; /**< map of client side proxy objects + * indexed with the client id */ + struct pw_core_info *info; /**< info about the remote core */ + /** Signal emited when the core info changed */ + PW_SIGNAL(info_changed, (struct pw_listener *listener, struct pw_remote *remote)); + + /** Signal emited when a reply to a sync was received */ + PW_SIGNAL(sync_reply, (struct pw_listener *listener, struct pw_remote *remote, uint32_t seq)); + + uint32_t n_types; /**< number of client types */ + struct pw_map types; /**< client types */ + + struct spa_list proxy_list; /**< list of \ref pw_proxy objects */ + struct spa_list stream_list; /**< list of \ref pw_proxy objects */ + + struct pw_protocol *protocol; /**< the protocol in use */ + void *protocol_private; /**< private data for the protocol */ + struct pw_protocol_connection *conn; /**< the protocol connection */ + + enum pw_remote_state state; + char *error; + /** Signal emited when the state changes */ + PW_SIGNAL(state_changed, (struct pw_listener *listener, struct pw_remote *remote)); + + /** Signal emited when the remote is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_remote *remote)); +}; + +/** Create a new unconnected remote \memberof pw_remote + * \return a new unconnected remote */ +struct pw_remote * +pw_remote_new(struct pw_core *core, /**< a \ref pw_core */ + struct pw_properties *properties /**< optional properties, ownership of + * the properties is taken.*/ ); + +/** Destroy a remote \memberof pw_remote */ +void pw_remote_destroy(struct pw_remote *remote); + +/** Connect to a remote PipeWire \memberof pw_remote + * \return true on success. */ +int pw_remote_connect(struct pw_remote *remote); + +/** Connect to a remote PipeWire on the given socket \memberof pw_remote + * \param fd the connected socket to use + * \return true on success. */ +int pw_remote_connect_fd(struct pw_remote *remote, int fd); + +/** Disconnect from the remote PipeWire. \memberof pw_remote */ +void pw_remote_disconnect(struct pw_remote *remote); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_REMOTE_H__ */ diff --git a/src/pipewire/resource.c b/src/pipewire/resource.c new file mode 100644 index 00000000..f808b6cb --- /dev/null +++ b/src/pipewire/resource.c @@ -0,0 +1,111 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> + +#include "pipewire/interfaces.h" +#include "pipewire/protocol.h" +#include "pipewire/resource.h" + +/** \cond */ +struct impl { + struct pw_resource this; +}; +/** \endcond */ + +struct pw_resource *pw_resource_new(struct pw_client *client, + uint32_t id, + uint32_t type, + size_t user_data_size) +{ + struct impl *impl; + struct pw_resource *this; + + impl = calloc(1, sizeof(struct impl) + user_data_size); + if (impl == NULL) + return NULL; + + this = &impl->this; + this->core = client->core; + this->client = client; + this->type = type; + + pw_signal_init(&this->destroy_signal); + + if (id == SPA_ID_INVALID) { + id = pw_map_insert_new(&client->objects, this); + } else if (!pw_map_insert_at(&client->objects, id, this)) + goto in_use; + + this->id = id; + + if (user_data_size > 0) + this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void); + + this->iface = pw_protocol_get_interface(client->protocol, + spa_type_map_get_type(client->core->type.map, type), + true); + + pw_log_debug("resource %p: new for client %p id %u", this, client, id); + pw_signal_emit(&client->resource_added, client, this); + + return this; + + in_use: + pw_log_debug("resource %p: id %u in use for client %p", this, id, client); + free(impl); + return NULL; +} + +int +pw_resource_set_implementation(struct pw_resource *resource, + void *object, + uint32_t version, + const void *implementation, + pw_destroy_t destroy) +{ + struct pw_client *client = resource->client; + + resource->object = object; + resource->version = version; + resource->implementation = implementation; + resource->destroy = destroy; + pw_signal_emit(&client->resource_impl, client, resource); + + return SPA_RESULT_OK; +} + +void pw_resource_destroy(struct pw_resource *resource) +{ + struct pw_client *client = resource->client; + + pw_log_trace("resource %p: destroy %u", resource, resource->id); + pw_signal_emit(&resource->destroy_signal, resource); + + pw_map_insert_at(&client->objects, resource->id, NULL); + pw_signal_emit(&client->resource_removed, client, resource); + + if (resource->destroy) + resource->destroy(resource); + + if (client->core_resource) + pw_core_notify_remove_id(client->core_resource, resource->id); + + free(resource); +} diff --git a/src/pipewire/resource.h b/src/pipewire/resource.h new file mode 100644 index 00000000..38cf6ea5 --- /dev/null +++ b/src/pipewire/resource.h @@ -0,0 +1,107 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_RESOURCE_H__ +#define __PIPEWIRE_RESOURCE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PIPEWIRE_TYPE__Resource "PipeWire:Object:Resource" +#define PIPEWIRE_TYPE_RESOURCE_BASE PIPEWIRE_TYPE__Resource ":" + +#include <spa/list.h> + +#include <pipewire/sig.h> +#include <pipewire/utils.h> +#include <pipewire/core.h> + +/** \page page_resource Resource + * + * \section sec_page_resource Overview + * + * Resources represent objects owned by a \ref pw_client. They are + * the result of binding to a global resource or by calling API that + * creates client owned objects. + * + * The client usually has a proxy object associated with the resource + * that it can use to communicate with the resource. See \ref page_proxy. + * + * Resources are destroyed when the client or the bound object is + * destroyed. + * + */ +/** \class pw_resource + * + * \brief Client owned objects + * + * Resources are objects owned by a client and are destroyed when the + * client disappears. + * + * See also \ref page_resource + */ +struct pw_resource { + struct pw_core *core; /**< the core object */ + struct spa_list link; /**< link in object resource_list */ + + struct pw_client *client; /**< owner client */ + + uint32_t id; /**< per client unique id, index in client objects */ + uint32_t type; /**< type id of the object */ + const struct pw_interface *iface; /**< protocol specific interface functions */ + + void *object; /**< pointer to the object */ + uint32_t version; /**< interface version */ + const void *implementation; /**< method implementation */ + pw_destroy_t destroy; /**< function to clean up the object */ + + /** Emited when the resource is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_resource *resource)); + + void *access_private; /**< private data for access control */ + void *user_data; /**< extra user data */ +}; + +struct pw_resource * +pw_resource_new(struct pw_client *client, + uint32_t id, + uint32_t type, + size_t user_data_size); + +int +pw_resource_set_implementation(struct pw_resource *resource, + void *object, + uint32_t version, + const void *implementation, + pw_destroy_t destroy); + +void +pw_resource_destroy(struct pw_resource *resource); + +#define pw_resource_do(r,type,method,...) ((type*) r->implementation)->method(r, __VA_ARGS__) +#define pw_resource_do_na(r,type,method) ((type*) r->implementation)->method(r) +#define pw_resource_notify(r,type,event,...) ((type*) r->iface->events)->event(r, __VA_ARGS__) +#define pw_resource_notify_na(r,type,event) ((type*) r->iface->events)->event(r) + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_RESOURCE_H__ */ diff --git a/src/pipewire/rtkit.c b/src/pipewire/rtkit.c new file mode 100644 index 00000000..98fa9225 --- /dev/null +++ b/src/pipewire/rtkit.c @@ -0,0 +1,378 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +/*** + Copyright 2009 Lennart Poettering + Copyright 2010 David Henningsson <diwic@ubuntu.com> + + 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 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 <errno.h> +#include <stdlib.h> +#include <stdbool.h> + +#include <dbus/dbus.h> + +#include <pipewire/log.h> + +#include "rtkit.h" + +/** \cond */ +struct pw_rtkit_bus { + DBusConnection *bus; +}; +/** \endcond */ + +struct pw_rtkit_bus *pw_rtkit_bus_get_system(void) +{ + struct pw_rtkit_bus *bus; + DBusError error; + + dbus_error_init(&error); + + bus = calloc(1, sizeof(struct pw_rtkit_bus)); + if (bus == NULL) + return NULL; + + bus->bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (bus->bus == NULL) + goto error; + + dbus_connection_set_exit_on_disconnect(bus->bus, false); + + return bus; + + error: + pw_log_error("Failed to connect to system bus: %s", error.message); + dbus_error_free(&error); + return NULL; +} + +void pw_rtkit_bus_free(struct pw_rtkit_bus *system_bus) +{ + dbus_connection_close(system_bus->bus); + dbus_connection_unref(system_bus->bus); + free(system_bus); +} + +#if defined(__linux__) && !defined(__ANDROID__) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/syscall.h> + + +static pid_t _gettid(void) +{ + return (pid_t) syscall(SYS_gettid); +} + +static int translate_error(const char *name) +{ + if (0 == strcmp(name, DBUS_ERROR_NO_MEMORY)) + return -ENOMEM; + if (0 == strcmp(name, DBUS_ERROR_SERVICE_UNKNOWN) || + 0 == strcmp(name, DBUS_ERROR_NAME_HAS_NO_OWNER)) + return -ENOENT; + if (0 == strcmp(name, DBUS_ERROR_ACCESS_DENIED) || + 0 == strcmp(name, DBUS_ERROR_AUTH_FAILED)) + return -EACCES; + + return -EIO; +} + +static long long rtkit_get_int_property(struct pw_rtkit_bus *connection, const char *propname, + long long *propval) +{ + DBusMessage *m = NULL, *r = NULL; + DBusMessageIter iter, subiter; + dbus_int64_t i64; + dbus_int32_t i32; + DBusError error; + int current_type; + long long ret; + const char *interfacestr = "org.freedesktop.RealtimeKit1"; + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, + RTKIT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", "Get"))) { + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, + DBUS_TYPE_STRING, &interfacestr, + DBUS_TYPE_STRING, &propname, DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) { + ret = translate_error(error.name); + goto finish; + } + + if (dbus_set_error_from_message(&error, r)) { + ret = translate_error(error.name); + goto finish; + } + + ret = -EBADMSG; + dbus_message_iter_init(r, &iter); + while ((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) { + + if (current_type == DBUS_TYPE_VARIANT) { + dbus_message_iter_recurse(&iter, &subiter); + + while ((current_type = + dbus_message_iter_get_arg_type(&subiter)) != DBUS_TYPE_INVALID) { + + if (current_type == DBUS_TYPE_INT32) { + dbus_message_iter_get_basic(&subiter, &i32); + *propval = i32; + ret = 0; + } + + if (current_type == DBUS_TYPE_INT64) { + dbus_message_iter_get_basic(&subiter, &i64); + *propval = i64; + ret = 0; + } + + dbus_message_iter_next(&subiter); + } + } + dbus_message_iter_next(&iter); + } + + finish: + + if (m) + dbus_message_unref(m); + + if (r) + dbus_message_unref(r); + + dbus_error_free(&error); + + return ret; +} + +int pw_rtkit_get_max_realtime_priority(struct pw_rtkit_bus *connection) +{ + long long retval; + int err; + + err = rtkit_get_int_property(connection, "MaxRealtimePriority", &retval); + return err < 0 ? err : retval; +} + +int pw_rtkit_get_min_nice_level(struct pw_rtkit_bus *connection, int *min_nice_level) +{ + long long retval; + int err; + + err = rtkit_get_int_property(connection, "MinNiceLevel", &retval); + if (err >= 0) + *min_nice_level = retval; + return err; +} + +long long pw_rtkit_get_rttime_usec_max(struct pw_rtkit_bus *connection) +{ + long long retval; + int err; + + err = rtkit_get_int_property(connection, "RTTimeUSecMax", &retval); + return err < 0 ? err : retval; +} + +int pw_rtkit_make_realtime(struct pw_rtkit_bus *connection, pid_t thread, int priority) +{ + DBusMessage *m = NULL, *r = NULL; + dbus_uint64_t u64; + dbus_uint32_t u32; + DBusError error; + int ret; + + dbus_error_init(&error); + + if (thread == 0) + thread = _gettid(); + + if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, + RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", + "MakeThreadRealtime"))) { + ret = -ENOMEM; + goto finish; + } + + u64 = (dbus_uint64_t) thread; + u32 = (dbus_uint32_t) priority; + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT64, &u64, + DBUS_TYPE_UINT32, &u32, DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) { + ret = translate_error(error.name); + goto finish; + } + + + if (dbus_set_error_from_message(&error, r)) { + ret = translate_error(error.name); + goto finish; + } + + ret = 0; + + finish: + + if (m) + dbus_message_unref(m); + + if (r) + dbus_message_unref(r); + + dbus_error_free(&error); + + return ret; +} + +int pw_rtkit_make_high_priority(struct pw_rtkit_bus *connection, pid_t thread, int nice_level) +{ + DBusMessage *m = NULL, *r = NULL; + dbus_uint64_t u64; + dbus_int32_t s32; + DBusError error; + int ret; + + dbus_error_init(&error); + + if (thread == 0) + thread = _gettid(); + + if (!(m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, + RTKIT_OBJECT_PATH, + "org.freedesktop.RealtimeKit1", + "MakeThreadHighPriority"))) { + ret = -ENOMEM; + goto finish; + } + + u64 = (dbus_uint64_t) thread; + s32 = (dbus_int32_t) nice_level; + + if (!dbus_message_append_args(m, + DBUS_TYPE_UINT64, &u64, + DBUS_TYPE_INT32, &s32, DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + + + if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) { + ret = translate_error(error.name); + goto finish; + } + + + if (dbus_set_error_from_message(&error, r)) { + ret = translate_error(error.name); + goto finish; + } + + ret = 0; + + finish: + + if (m) + dbus_message_unref(m); + + if (r) + dbus_message_unref(r); + + dbus_error_free(&error); + + return ret; +} + +#else + +int pw_rtkit_make_realtime(struct pw_rtkit_bus *connection, pid_t thread, int priority) +{ + return -ENOTSUP; +} + +int pw_rtkit_make_high_priority(struct pw_rtkit_bus *connection, pid_t thread, int nice_level) +{ + return -ENOTSUP; +} + +int pw_rtkit_get_max_realtime_priority(struct pw_rtkit_bus *connection) +{ + return -ENOTSUP; +} + +int pw_rtkit_get_min_nice_level(struct pw_rtkit_bus *connection, int *min_nice_level) +{ + return -ENOTSUP; +} + +long long pw_rtkit_get_rttime_usec_max(struct pw_rtkit_bus *connection) +{ + return -ENOTSUP; +} + +#endif diff --git a/src/pipewire/rtkit.h b/src/pipewire/rtkit.h new file mode 100644 index 00000000..a40e2d12 --- /dev/null +++ b/src/pipewire/rtkit.h @@ -0,0 +1,93 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_RTKIT_H__ +#define __PIPEWIRE_RTKIT_H__ + +#include <sys/types.h> +#include <unistd.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" +#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" + +/** \class pw_rtkit + * + * RTKit helpers + */ +/** Get an RTKit bus \memberof pw_rtkit */ +struct pw_rtkit_bus * +pw_rtkit_bus_get_system(void); + +/** Free an RTKit bus \memberof pw_rtkit */ +void +pw_rtkit_bus_free(struct pw_rtkit_bus *system_bus); + + +/** This is mostly equivalent to sched_setparam(thread, SCHED_RR, { + * .sched_priority = priority }). 'thread' needs to be a kernel thread + * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the + * current thread is used. The returned value is a negative errno + * style error code, or 0 on success. + * \memberof pw_rtkit + */ +int +pw_rtkit_make_realtime(struct pw_rtkit_bus *system_bus, pid_t thread, int priority); + + +/** This is mostly equivalent to setpriority(PRIO_PROCESS, thread, + * nice_level). 'thread' needs to be a kernel thread id as returned by + * gettid(), not a pthread_t! If 'thread' is 0 the current thread is + * used. The returned value is a negative errno style error code, or + * 0 on success. + * \memberof pw_rtkit + */ +int +pw_rtkit_make_high_priority(struct pw_rtkit_bus *system_bus, pid_t thread, int nice_level); + +/** Return the maximum value of realtime priority available. Realtime requests + * above this value will fail. A negative value is an errno style error code. + * \memberof pw_rtkit + */ +int +pw_rtkit_get_max_realtime_priority(struct pw_rtkit_bus *system_bus); + +/** Retreive the minimum value of nice level available. High prio requests + * below this value will fail. The returned value is a negative errno + * style error code, or 0 on success. + * \memberof pw_rtkit + */ +int +pw_rtkit_get_min_nice_level(struct pw_rtkit_bus *system_bus, int *min_nice_level); + +/** Return the maximum value of RLIMIT_RTTIME to set before attempting a + * realtime request. A negative value is an errno style error code. + * \memberof pw_rtkit + */ +long long +pw_rtkit_get_rttime_usec_max(struct pw_rtkit_bus *system_bus); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_RTKIT_H__ */ diff --git a/src/pipewire/sig.h b/src/pipewire/sig.h new file mode 100644 index 00000000..ef850e5a --- /dev/null +++ b/src/pipewire/sig.h @@ -0,0 +1,76 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_SIGNAL_H__ +#define __PIPEWIRE_SIGNAL_H__ + +#include <spa/list.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \class pw_signal + * Signal emission helpers + */ + +/** A listener \memberof pw_signal */ +struct pw_listener { + struct spa_list link; /**< link in the signal listeners */ + void (*notify) (void *); /**< notify function */ +}; + +/** A signal definition \memberof pw_signal */ +#define PW_SIGNAL(name,func) \ + union { \ + struct spa_list listeners; \ + void (*notify) func; \ + } name; + +/** Initialize a signal \memberof pw_signal */ +#define pw_signal_init(signal) \ + spa_list_init(&(signal)->listeners); + +/** Add a signal listener \memberof pw_signal */ +#define pw_signal_add(signal,listener,func) \ +do { \ + __typeof__((signal)->notify) n = (func); \ + (listener)->notify = (void(*)(void *)) n; \ + spa_list_insert((signal)->listeners.prev, &(listener)->link); \ +} while (false); + +/** Remove a signal listener \memberof pw_signal */ +static inline void pw_signal_remove(struct pw_listener *listener) +{ + spa_list_remove(&listener->link); +} + +/** Emit a signal \memberof pw_signal */ +#define pw_signal_emit(signal,...) \ +do { \ + struct pw_listener *l, *next; \ + spa_list_for_each_safe(l, next, &(signal)->listeners, link) \ + ((__typeof__((signal)->notify))l->notify)(l,__VA_ARGS__); \ +} while (false); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_SIGNAL_H__ */ diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c new file mode 100644 index 00000000..e7f5dcde --- /dev/null +++ b/src/pipewire/stream.c @@ -0,0 +1,1062 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <unistd.h> +#include <sys/socket.h> +#include <string.h> +#include <sys/mman.h> +#include <errno.h> +#include <time.h> + +#include "spa/lib/debug.h" + +#include "pipewire/pipewire.h" +#include "pipewire/interfaces.h" +#include "pipewire/array.h" +#include "pipewire/stream.h" +#include "pipewire/transport.h" +#include "pipewire/utils.h" +#include "pipewire/stream.h" +#include "extensions/client-node.h" + +/** \cond */ + +#define MAX_BUFFER_SIZE 4096 +#define MAX_FDS 32 +#define MAX_INPUTS 64 +#define MAX_OUTPUTS 64 + +struct mem_id { + uint32_t id; + int fd; + uint32_t flags; + void *ptr; + uint32_t offset; + uint32_t size; +}; + +struct buffer_id { + struct spa_list link; + uint32_t id; + bool used; + void *buf_ptr; + struct spa_buffer *buf; +}; + +struct stream { + struct pw_stream this; + + uint32_t type_client_node; + + uint32_t n_possible_formats; + struct spa_format **possible_formats; + + uint32_t n_params; + struct spa_param **params; + + struct spa_format *format; + struct spa_port_info port_info; + enum spa_direction direction; + uint32_t port_id; + uint32_t pending_seq; + + enum pw_stream_mode mode; + + int rtreadfd; + int rtwritefd; + struct spa_source *rtsocket_source; + + struct pw_proxy *node_proxy; + bool disconnecting; + struct pw_listener node_proxy_destroy; + struct pw_listener node_proxy_sync_done; + + struct pw_transport *trans; + + struct spa_source *timeout_source; + + struct pw_array mem_ids; + struct pw_array buffer_ids; + bool in_order; + + struct spa_list free; + bool in_need_buffer; + + int64_t last_ticks; + int32_t last_rate; + int64_t last_monotonic; +}; +/** \endcond */ + +static void clear_memid(struct mem_id *mid) +{ + if (mid->ptr != NULL) + munmap(mid->ptr, mid->size + mid->offset); + mid->ptr = NULL; + close(mid->fd); +} + +static void clear_mems(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct mem_id *mid; + + pw_array_for_each(mid, &impl->mem_ids) + clear_memid(mid); + impl->mem_ids.size = 0; +} + +static void clear_buffers(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer_id *bid; + + pw_log_debug("stream %p: clear buffers", stream); + + pw_array_for_each(bid, &impl->buffer_ids) { + pw_signal_emit(&stream->remove_buffer, stream, bid->id); + free(bid->buf); + bid->buf = NULL; + bid->used = false; + } + impl->buffer_ids.size = 0; + impl->in_order = true; + spa_list_init(&impl->free); +} + +static bool stream_set_state(struct pw_stream *stream, enum pw_stream_state state, char *error) +{ + bool res = stream->state != state; + if (res) { + if (stream->error) + free(stream->error); + stream->error = error; + + pw_log_debug("stream %p: update state from %s -> %s (%s)", stream, + pw_stream_state_as_string(stream->state), + pw_stream_state_as_string(state), stream->error); + + stream->state = state; + pw_signal_emit(&stream->state_changed, stream); + } + return res; +} + +const char *pw_stream_state_as_string(enum pw_stream_state state) +{ + switch (state) { + case PW_STREAM_STATE_ERROR: + return "error"; + case PW_STREAM_STATE_UNCONNECTED: + return "unconnected"; + case PW_STREAM_STATE_CONNECTING: + return "connecting"; + case PW_STREAM_STATE_CONFIGURE: + return "configure"; + case PW_STREAM_STATE_READY: + return "ready"; + case PW_STREAM_STATE_PAUSED: + return "paused"; + case PW_STREAM_STATE_STREAMING: + return "streaming"; + } + return "invalid-state"; +} + +struct pw_stream *pw_stream_new(struct pw_remote *remote, + const char *name, struct pw_properties *props) +{ + struct stream *impl; + struct pw_stream *this; + + impl = calloc(1, sizeof(struct stream)); + if (impl == NULL) + return NULL; + + this = &impl->this; + pw_log_debug("stream %p: new", impl); + + if (props == NULL) { + props = pw_properties_new("media.name", name, NULL); + } else if (!pw_properties_get(props, "media.name")) { + pw_properties_set(props, "media.name", name); + } + if (props == NULL) + goto no_mem; + + this->properties = props; + + this->remote = remote; + this->name = strdup(name); + impl->type_client_node = spa_type_map_get_id(remote->core->type.map, PIPEWIRE_TYPE_NODE_BASE "Client"); + + pw_signal_init(&this->destroy_signal); + pw_signal_init(&this->state_changed); + pw_signal_init(&this->format_changed); + pw_signal_init(&this->add_buffer); + pw_signal_init(&this->remove_buffer); + pw_signal_init(&this->new_buffer); + pw_signal_init(&this->need_buffer); + + this->state = PW_STREAM_STATE_UNCONNECTED; + + pw_array_init(&impl->mem_ids, 64); + pw_array_ensure_size(&impl->mem_ids, sizeof(struct mem_id) * 64); + pw_array_init(&impl->buffer_ids, 32); + pw_array_ensure_size(&impl->buffer_ids, sizeof(struct buffer_id) * 64); + impl->pending_seq = SPA_ID_INVALID; + spa_list_init(&impl->free); + + spa_list_insert(&remote->stream_list, &this->link); + + return this; + + no_mem: + free(impl); + return NULL; +} + +static void unhandle_socket(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + if (impl->rtsocket_source) { + pw_loop_destroy_source(stream->remote->core->data_loop, impl->rtsocket_source); + impl->rtsocket_source = NULL; + } + if (impl->timeout_source) { + pw_loop_destroy_source(stream->remote->core->data_loop, impl->timeout_source); + impl->timeout_source = NULL; + } +} + +static void +set_possible_formats(struct pw_stream *stream, + int n_possible_formats, const struct spa_format **possible_formats) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + int i; + + if (impl->possible_formats) { + for (i = 0; i < impl->n_possible_formats; i++) + free(impl->possible_formats[i]); + free(impl->possible_formats); + impl->possible_formats = NULL; + } + impl->n_possible_formats = n_possible_formats; + if (n_possible_formats > 0) { + impl->possible_formats = malloc(n_possible_formats * sizeof(struct spa_format *)); + for (i = 0; i < n_possible_formats; i++) + impl->possible_formats[i] = spa_format_copy(possible_formats[i]); + } +} + +static void set_params(struct pw_stream *stream, int n_params, struct spa_param **params) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + int i; + + if (impl->params) { + for (i = 0; i < impl->n_params; i++) + free(impl->params[i]); + free(impl->params); + impl->params = NULL; + } + impl->n_params = n_params; + if (n_params > 0) { + impl->params = malloc(n_params * sizeof(struct spa_param *)); + for (i = 0; i < n_params; i++) + impl->params[i] = spa_param_copy(params[i]); + } +} + +void pw_stream_destroy(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + pw_log_debug("stream %p: destroy", stream); + + pw_signal_emit(&stream->destroy_signal, stream); + + unhandle_socket(stream); + + spa_list_remove(&stream->link); + + if (impl->node_proxy) + pw_signal_remove(&impl->node_proxy_destroy); + + set_possible_formats(stream, 0, NULL); + set_params(stream, 0, NULL); + + if (impl->format) + free(impl->format); + + if (stream->error) + free(stream->error); + + clear_buffers(stream); + pw_array_clear(&impl->buffer_ids); + + clear_mems(stream); + pw_array_clear(&impl->mem_ids); + + if (stream->properties) + pw_properties_free(stream->properties); + + if (impl->trans) + pw_transport_destroy(impl->trans); + + if (stream->name) + free(stream->name); + + close(impl->rtwritefd); + + free(impl); +} + +static void add_node_update(struct pw_stream *stream, uint32_t change_mask) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + uint32_t max_input_ports = 0, max_output_ports = 0; + + if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_INPUTS) + max_input_ports = impl->direction == SPA_DIRECTION_INPUT ? 1 : 0; + if (change_mask & PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS) + max_output_ports = impl->direction == SPA_DIRECTION_OUTPUT ? 1 : 0; + + pw_client_node_do_update(impl->node_proxy, + change_mask, max_input_ports, max_output_ports, NULL); +} + +static void add_port_update(struct pw_stream *stream, uint32_t change_mask) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + + pw_client_node_do_port_update(impl->node_proxy, + impl->direction, + impl->port_id, + change_mask, + impl->n_possible_formats, + (const struct spa_format **) impl->possible_formats, + impl->format, + impl->n_params, + (const struct spa_param **) impl->params, &impl->port_info); +} + +static inline void send_need_input(struct pw_stream *stream) +{ +#if 0 + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + uint64_t cmd = 1; + + pw_transport_add_event(impl->trans, + &SPA_EVENT_INIT(stream->remote->core->type.event_transport.NeedInput)); + write(impl->rtwritefd, &cmd, 8); +#endif +} + +static inline void send_have_output(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + uint64_t cmd = 1; + + pw_transport_add_event(impl->trans, + &SPA_EVENT_INIT(stream->remote->core->type.event_transport.HaveOutput)); + write(impl->rtwritefd, &cmd, 8); +} + +static void add_request_clock_update(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + pw_client_node_do_event(impl->node_proxy, (struct spa_event *) + &SPA_EVENT_NODE_REQUEST_CLOCK_UPDATE_INIT(stream->remote->core->type. + event_node. + RequestClockUpdate, + SPA_EVENT_NODE_REQUEST_CLOCK_UPDATE_TIME, + 0, 0)); +} + +static void add_async_complete(struct pw_stream *stream, uint32_t seq, int res) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + pw_client_node_do_done(impl->node_proxy, seq, res); +} + +static void do_node_init(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + add_node_update(stream, PW_CLIENT_NODE_UPDATE_MAX_INPUTS | + PW_CLIENT_NODE_UPDATE_MAX_OUTPUTS); + + impl->port_info.flags = SPA_PORT_INFO_FLAG_CAN_USE_BUFFERS; + add_port_update(stream, PW_CLIENT_NODE_PORT_UPDATE_POSSIBLE_FORMATS | + PW_CLIENT_NODE_PORT_UPDATE_INFO); + add_async_complete(stream, 0, SPA_RESULT_OK); +} + +static void on_timeout(struct spa_loop_utils *utils, struct spa_source *source, void *data) +{ + struct pw_stream *stream = data; + add_request_clock_update(stream); +} + +static struct mem_id *find_mem(struct pw_stream *stream, uint32_t id) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct mem_id *mid; + + pw_array_for_each(mid, &impl->mem_ids) { + if (mid->id == id) + return mid; + } + return NULL; +} + +static struct buffer_id *find_buffer(struct pw_stream *stream, uint32_t id) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + if (impl->in_order && pw_array_check_index(&impl->buffer_ids, id, struct buffer_id)) { + return pw_array_get_unchecked(&impl->buffer_ids, id, struct buffer_id); + } else { + struct buffer_id *bid; + + pw_array_for_each(bid, &impl->buffer_ids) { + if (bid->id == id) + return bid; + } + } + return NULL; +} + +static inline void reuse_buffer(struct pw_stream *stream, uint32_t id) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer_id *bid; + + if ((bid = find_buffer(stream, id)) && bid->used) { + pw_log_trace("stream %p: reuse buffer %u", stream, id); + bid->used = false; + spa_list_insert(impl->free.prev, &bid->link); + pw_signal_emit(&stream->new_buffer, stream, id); + } +} + +static void handle_rtnode_event(struct pw_stream *stream, struct spa_event *event) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct pw_remote *remote = impl->this.remote; + + if (SPA_EVENT_TYPE(event) == remote->core->type.event_transport.HaveOutput) { + int i; + + for (i = 0; i < impl->trans->area->n_input_ports; i++) { + struct spa_port_io *input = &impl->trans->inputs[i]; + + pw_log_trace("stream %p: have output %d %d", stream, input->status, + input->buffer_id); + if (input->buffer_id == SPA_ID_INVALID) + continue; + + pw_signal_emit(&stream->new_buffer, stream, input->buffer_id); + input->buffer_id = SPA_ID_INVALID; + } + send_need_input(stream); + } else if (SPA_EVENT_TYPE(event) == remote->core->type.event_transport.NeedInput) { + int i; + + for (i = 0; i < impl->trans->area->n_output_ports; i++) { + struct spa_port_io *output = &impl->trans->outputs[i]; + + if (output->buffer_id == SPA_ID_INVALID) + continue; + + reuse_buffer(stream, output->buffer_id); + output->buffer_id = SPA_ID_INVALID; + } + pw_log_trace("stream %p: need input", stream); + impl->in_need_buffer = true; + pw_signal_emit(&stream->need_buffer, stream); + impl->in_need_buffer = false; + } else if (SPA_EVENT_TYPE(event) == remote->core->type.event_transport.ReuseBuffer) { + struct pw_event_transport_reuse_buffer *p = + (struct pw_event_transport_reuse_buffer *) event; + + if (p->body.port_id.value != impl->port_id) + return; + if (impl->direction != SPA_DIRECTION_OUTPUT) + return; + + reuse_buffer(stream, p->body.buffer_id.value); + } else { + pw_log_warn("unexpected node event %d", SPA_EVENT_TYPE(event)); + } +} + +static void +on_rtsocket_condition(struct spa_loop_utils *utils, + struct spa_source *source, int fd, enum spa_io mask, void *data) +{ + struct pw_stream *stream = data; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { + pw_log_warn("got error"); + unhandle_socket(stream); + return; + } + + if (mask & SPA_IO_IN) { + struct spa_event event; + uint64_t cmd; + + read(impl->rtreadfd, &cmd, 8); + + while (pw_transport_next_event(impl->trans, &event) == SPA_RESULT_OK) { + struct spa_event *ev = alloca(SPA_POD_SIZE(&event)); + pw_transport_parse_event(impl->trans, ev); + handle_rtnode_event(stream, ev); + } + } +} + +static void handle_socket(struct pw_stream *stream, int rtreadfd, int rtwritefd) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct timespec interval; + + impl->rtreadfd = rtreadfd; + impl->rtwritefd = rtwritefd; + impl->rtsocket_source = pw_loop_add_io(stream->remote->core->data_loop, + impl->rtreadfd, + SPA_IO_ERR | SPA_IO_HUP, + true, on_rtsocket_condition, stream); + + impl->timeout_source = pw_loop_add_timer(stream->remote->core->main_loop, on_timeout, stream); + interval.tv_sec = 0; + interval.tv_nsec = 100000000; + pw_loop_update_timer(stream->remote->core->main_loop, impl->timeout_source, NULL, &interval, false); + return; +} + +static bool +handle_node_command(struct pw_stream *stream, uint32_t seq, const struct spa_command *command) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct pw_remote *remote = stream->remote; + + if (SPA_COMMAND_TYPE(command) == remote->core->type.command_node.Pause) { + add_async_complete(stream, seq, SPA_RESULT_OK); + + if (stream->state == PW_STREAM_STATE_STREAMING) { + pw_log_debug("stream %p: pause %d", stream, seq); + + pw_loop_update_io(stream->remote->core->data_loop, + impl->rtsocket_source, SPA_IO_ERR | SPA_IO_HUP); + + stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL); + } + } else if (SPA_COMMAND_TYPE(command) == remote->core->type.command_node.Start) { + add_async_complete(stream, seq, SPA_RESULT_OK); + + if (stream->state == PW_STREAM_STATE_PAUSED) { + pw_log_debug("stream %p: start %d %d", stream, seq, impl->direction); + + pw_loop_update_io(stream->remote->core->data_loop, + impl->rtsocket_source, + SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP); + + if (impl->direction == SPA_DIRECTION_INPUT) + send_need_input(stream); + else { + impl->in_need_buffer = true; + pw_signal_emit(&stream->need_buffer, stream); + impl->in_need_buffer = false; + } + stream_set_state(stream, PW_STREAM_STATE_STREAMING, NULL); + } + } else if (SPA_COMMAND_TYPE(command) == remote->core->type.command_node.ClockUpdate) { + struct spa_command_node_clock_update *cu = (__typeof__(cu)) command; + + if (cu->body.flags.value & SPA_COMMAND_NODE_CLOCK_UPDATE_FLAG_LIVE) { + pw_properties_set(stream->properties, "pipewire.latency.is-live", "1"); + pw_properties_setf(stream->properties, + "pipewire.latency.min", "%" PRId64, + cu->body.latency.value); + } + impl->last_ticks = cu->body.ticks.value; + impl->last_rate = cu->body.rate.value; + impl->last_monotonic = cu->body.monotonic_time.value; + } else { + pw_log_warn("unhandled node command %d", SPA_COMMAND_TYPE(command)); + add_async_complete(stream, seq, SPA_RESULT_NOT_IMPLEMENTED); + } + return true; +} + +static void +client_node_set_props(void *object, uint32_t seq, const struct spa_props *props) +{ + pw_log_warn("set property not implemented"); +} + +static void client_node_event(void *object, const struct spa_event *event) +{ + pw_log_warn("unhandled node event %d", SPA_EVENT_TYPE(event)); +} + +static void +client_node_add_port(void *object, uint32_t seq, enum spa_direction direction, uint32_t port_id) +{ + pw_log_warn("add port not supported"); +} + +static void +client_node_remove_port(void *object, uint32_t seq, enum spa_direction direction, uint32_t port_id) +{ + pw_log_warn("remove port not supported"); +} + +static void +client_node_set_format(void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, uint32_t flags, const struct spa_format *format) +{ + struct pw_proxy *proxy = object; + struct pw_stream *stream = proxy->object; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + pw_log_debug("stream %p: format changed %d", stream, seq); + + if (impl->format) + free(impl->format); + impl->format = format ? spa_format_copy(format) : NULL; + impl->pending_seq = seq; + + pw_signal_emit(&stream->format_changed, stream, impl->format); + + if (format) + stream_set_state(stream, PW_STREAM_STATE_READY, NULL); + else + stream_set_state(stream, PW_STREAM_STATE_CONFIGURE, NULL); +} + +static void +client_node_set_param(void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, + const struct spa_param *param) +{ + pw_log_warn("set param not implemented"); +} + +static void +client_node_add_mem(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t mem_id, + uint32_t type, int memfd, uint32_t flags, uint32_t offset, uint32_t size) +{ + struct pw_proxy *proxy = object; + struct pw_stream *stream = proxy->object; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct mem_id *m; + + m = find_mem(stream, mem_id); + if (m) { + pw_log_debug("update mem %u, fd %d, flags %d, off %d, size %d", + mem_id, memfd, flags, offset, size); + clear_memid(m); + } else { + m = pw_array_add(&impl->mem_ids, sizeof(struct mem_id)); + pw_log_debug("add mem %u, fd %d, flags %d, off %d, size %d", + mem_id, memfd, flags, offset, size); + } + m->id = mem_id; + m->fd = memfd; + m->flags = flags; + m->ptr = NULL; + m->offset = offset; + m->size = size; +} + +static void +client_node_use_buffers(void *object, + uint32_t seq, + enum spa_direction direction, + uint32_t port_id, uint32_t n_buffers, struct pw_client_node_buffer *buffers) +{ + struct pw_proxy *proxy = object; + struct pw_stream *stream = proxy->object; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer_id *bid; + uint32_t i, j, len; + struct spa_buffer *b; + + /* clear previous buffers */ + clear_buffers(stream); + + for (i = 0; i < n_buffers; i++) { + off_t offset; + + struct mem_id *mid = find_mem(stream, buffers[i].mem_id); + if (mid == NULL) { + pw_log_warn("unknown memory id %u", buffers[i].mem_id); + continue; + } + + if (mid->ptr == NULL) { + mid->ptr = + mmap(NULL, mid->size + mid->offset, PROT_READ | PROT_WRITE, MAP_SHARED, + mid->fd, 0); + if (mid->ptr == MAP_FAILED) { + mid->ptr = NULL; + pw_log_warn("Failed to mmap memory %d %p: %s", mid->size, mid, + strerror(errno)); + continue; + } + } + len = pw_array_get_len(&impl->buffer_ids, struct buffer_id); + bid = pw_array_add(&impl->buffer_ids, sizeof(struct buffer_id)); + if (impl->direction == SPA_DIRECTION_OUTPUT) { + bid->used = false; + spa_list_insert(impl->free.prev, &bid->link); + } else { + bid->used = true; + } + + b = buffers[i].buffer; + + bid->buf_ptr = SPA_MEMBER(mid->ptr, mid->offset + buffers[i].offset, void); + { + size_t size; + + size = sizeof(struct spa_buffer); + for (j = 0; j < buffers[i].buffer->n_metas; j++) + size += sizeof(struct spa_meta); + for (j = 0; j < buffers[i].buffer->n_datas; j++) + size += sizeof(struct spa_data); + + b = bid->buf = malloc(size); + memcpy(b, buffers[i].buffer, sizeof(struct spa_buffer)); + + b->metas = SPA_MEMBER(b, sizeof(struct spa_buffer), struct spa_meta); + b->datas = + SPA_MEMBER(b->metas, sizeof(struct spa_meta) * b->n_metas, + struct spa_data); + } + bid->id = b->id; + + if (bid->id != len) { + pw_log_warn("unexpected id %u found, expected %u", bid->id, len); + impl->in_order = false; + } + pw_log_debug("add buffer %d %d %u", mid->id, bid->id, buffers[i].offset); + + offset = 0; + for (j = 0; j < b->n_metas; j++) { + struct spa_meta *m = &b->metas[j]; + memcpy(m, &buffers[i].buffer->metas[j], sizeof(struct spa_meta)); + m->data = SPA_MEMBER(bid->buf_ptr, offset, void); + offset += m->size; + } + + for (j = 0; j < b->n_datas; j++) { + struct spa_data *d = &b->datas[j]; + + memcpy(d, &buffers[i].buffer->datas[j], sizeof(struct spa_data)); + d->chunk = + SPA_MEMBER(bid->buf_ptr, offset + sizeof(struct spa_chunk) * j, + struct spa_chunk); + + if (d->type == stream->remote->core->type.data.Id) { + struct mem_id *bmid = find_mem(stream, SPA_PTR_TO_UINT32(d->data)); + d->type = stream->remote->core->type.data.MemFd; + d->data = NULL; + d->fd = bmid->fd; + pw_log_debug(" data %d %u -> fd %d", j, bmid->id, bmid->fd); + } else if (d->type == stream->remote->core->type.data.MemPtr) { + d->data = SPA_MEMBER(bid->buf_ptr, SPA_PTR_TO_INT(d->data), void); + d->fd = -1; + pw_log_debug(" data %d %u -> mem %p", j, bid->id, d->data); + } else { + pw_log_warn("unknown buffer data type %d", d->type); + } + } + pw_signal_emit(&stream->add_buffer, stream, bid->id); + } + + add_async_complete(stream, seq, SPA_RESULT_OK); + + if (n_buffers) + stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL); + else { + clear_mems(stream); + stream_set_state(stream, PW_STREAM_STATE_READY, NULL); + } +} + +static void client_node_node_command(void *object, uint32_t seq, const struct spa_command *command) +{ + struct pw_proxy *proxy = object; + struct pw_stream *stream = proxy->object; + handle_node_command(stream, seq, command); +} + +static void +client_node_port_command(void *object, + uint32_t direction, + uint32_t port_id, + const struct spa_command *command) +{ + pw_log_warn("port command not supported"); +} + +static void client_node_transport(void *object, uint32_t node_id, + int readfd, int writefd, int memfd, uint32_t offset, uint32_t size) +{ + struct pw_proxy *proxy = object; + struct pw_stream *stream = proxy->object; + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct pw_transport_info info; + + stream->node_id = node_id; + + info.memfd = memfd; + if (info.memfd == -1) + return; + info.offset = offset; + info.size = size; + + if (impl->trans) + pw_transport_destroy(impl->trans); + impl->trans = pw_transport_new_from_info(&info); + + pw_log_info("stream %p: create client transport %p with fds %d %d for node %u", + stream, impl->trans, readfd, writefd, node_id); + handle_socket(stream, readfd, writefd); + + stream_set_state(stream, PW_STREAM_STATE_CONFIGURE, NULL); +} + +static const struct pw_client_node_events client_node_events = { + &client_node_transport, + &client_node_set_props, + &client_node_event, + &client_node_add_port, + &client_node_remove_port, + &client_node_set_format, + &client_node_set_param, + &client_node_add_mem, + &client_node_use_buffers, + &client_node_node_command, + &client_node_port_command, +}; + +static void on_node_proxy_destroy(struct pw_listener *listener, struct pw_proxy *proxy) +{ + struct stream *impl = SPA_CONTAINER_OF(listener, struct stream, node_proxy_destroy); + struct pw_stream *this = &impl->this; + + impl->disconnecting = false; + impl->node_proxy = NULL; + pw_signal_remove(&impl->node_proxy_destroy); + stream_set_state(this, PW_STREAM_STATE_UNCONNECTED, NULL); +} + +bool +pw_stream_connect(struct pw_stream *stream, + enum pw_direction direction, + enum pw_stream_mode mode, + const char *port_path, + enum pw_stream_flags flags, + uint32_t n_possible_formats, + const struct spa_format **possible_formats) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + impl->direction = + direction == PW_DIRECTION_INPUT ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT; + impl->port_id = 0; + impl->mode = mode; + + set_possible_formats(stream, n_possible_formats, possible_formats); + + stream_set_state(stream, PW_STREAM_STATE_CONNECTING, NULL); + + if (stream->properties == NULL) + stream->properties = pw_properties_new(NULL, NULL); + if (port_path) + pw_properties_set(stream->properties, "pipewire.target.node", port_path); + if (flags & PW_STREAM_FLAG_AUTOCONNECT) + pw_properties_set(stream->properties, "pipewire.autoconnect", "1"); + + impl->node_proxy = pw_proxy_new(stream->remote, + SPA_ID_INVALID, + impl->type_client_node, + 0); + + if (impl->node_proxy == NULL) + return false; + + pw_proxy_set_implementation(impl->node_proxy, stream, PW_VERSION_CLIENT_NODE, + &client_node_events, NULL); + + pw_signal_add(&impl->node_proxy->destroy_signal, + &impl->node_proxy_destroy, on_node_proxy_destroy); + + pw_core_do_create_node(stream->remote->core_proxy, + "client-node", + "client-node", + &stream->properties->dict, + impl->node_proxy->id); + + do_node_init(stream); + + return true; +} + +void +pw_stream_finish_format(struct pw_stream *stream, + int res, struct spa_param **params, uint32_t n_params) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + pw_log_debug("stream %p: finish format %d %d", stream, res, impl->pending_seq); + + set_params(stream, n_params, params); + + if (SPA_RESULT_IS_OK(res)) { + add_port_update(stream, (n_params ? PW_CLIENT_NODE_PORT_UPDATE_PARAMS : 0) | + PW_CLIENT_NODE_PORT_UPDATE_FORMAT); + + if (!impl->format) { + clear_buffers(stream); + clear_mems(stream); + } + } + add_async_complete(stream, impl->pending_seq, res); + + impl->pending_seq = SPA_ID_INVALID; +} + +void pw_stream_disconnect(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + + impl->disconnecting = true; + + unhandle_socket(stream); + + if (impl->node_proxy) { + pw_client_node_do_destroy(impl->node_proxy); + impl->node_proxy = NULL; + } +} + +bool pw_stream_get_time(struct pw_stream *stream, struct pw_time *time) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + int64_t elapsed; + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + time->now = SPA_TIMESPEC_TO_TIME(&ts); + elapsed = (time->now - impl->last_monotonic) / 1000; + + time->ticks = impl->last_ticks + (elapsed * impl->last_rate) / SPA_USEC_PER_SEC; + time->rate = impl->last_rate; + + return true; +} + +uint32_t pw_stream_get_empty_buffer(struct pw_stream *stream) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer_id *bid; + + if (spa_list_is_empty(&impl->free)) + return SPA_ID_INVALID; + + bid = spa_list_first(&impl->free, struct buffer_id, link); + + return bid->id; +} + +bool pw_stream_recycle_buffer(struct pw_stream *stream, uint32_t id) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct pw_event_transport_reuse_buffer rb = PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT + (stream->remote->core->type.event_transport.ReuseBuffer, impl->port_id, id); + struct buffer_id *bid; + uint64_t cmd = 1; + + if ((bid = find_buffer(stream, id)) == NULL || !bid->used) + return false; + + bid->used = false; + spa_list_insert(impl->free.prev, &bid->link); + + pw_transport_add_event(impl->trans, (struct spa_event *) &rb); + write(impl->rtwritefd, &cmd, 8); + + return true; +} + +struct spa_buffer *pw_stream_peek_buffer(struct pw_stream *stream, uint32_t id) +{ + struct buffer_id *bid; + + if ((bid = find_buffer(stream, id))) + return bid->buf; + + return NULL; +} + +bool pw_stream_send_buffer(struct pw_stream *stream, uint32_t id) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer_id *bid; + + if (impl->trans->outputs[0].buffer_id != SPA_ID_INVALID) { + pw_log_debug("can't send %u, pending buffer %u", id, + impl->trans->outputs[0].buffer_id); + return false; + } + + if ((bid = find_buffer(stream, id)) && !bid->used) { + bid->used = true; + spa_list_remove(&bid->link); + impl->trans->outputs[0].buffer_id = id; + impl->trans->outputs[0].status = SPA_RESULT_HAVE_BUFFER; + pw_log_trace("stream %p: send buffer %d", stream, id); + if (!impl->in_need_buffer) + send_have_output(stream); + } else { + pw_log_debug("stream %p: output %u was used", stream, id); + } + + return true; +} diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h new file mode 100644 index 00000000..1463ff71 --- /dev/null +++ b/src/pipewire/stream.h @@ -0,0 +1,315 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_STREAM_H__ +#define __PIPEWIRE_STREAM_H__ + +#include <spa/buffer.h> +#include <spa/format.h> + +#include <pipewire/remote.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \page page_streams Media Streams + * + * \section sec_overview Overview + * + * Media streams are used to exchange data with the PipeWire server. A + * stream is a wrapper around a proxy for a \ref pw_client_node with + * just one port. + * + * Streams can be used to: + * + * \li Consume a stream from PipeWire. This is a PW_DIRECTION_INPUT stream. + * \li Produce a stream to PipeWire. This is a PW_DIRECTION_OUTPUT stream + * + * You can connect the stream port to a specific server port or let PipeWire + * choose a port for you. + * + * For more complicated nodes such as filters or ports with multiple + * inputs and/or outputs you will need to manage the \ref pw_client_node proxy + * yourself. + * + * \section sec_create Create + * + * Make a new stream with \ref pw_stream_new(). You will need to specify + * a name for the stream and extra properties. You can use \ref + * pw_fill_stream_properties() to get a basic set of properties for the + * stream. + * + * Once the stream is created, the state_changed signal should be used to + * track the state of the stream. + * + * \section sec_connect Connect + * + * The stream is initially unconnected. To connect the stream, use + * \ref pw_stream_connect(). Pass the desired direction as an argument. + * + * \subsection ssec_stream_mode Stream modes + * + * The stream mode specifies how the data will be exchanged with PipeWire. + * The following stream modes are available + * + * \li \ref PW_STREAM_MODE_BUFFER: data is exchanged with fixed size + * buffers. This is ideal for video frames or equal sized audio + * frames. + * \li \ref PW_STREAM_MODE_RINGBUFFER: data is exhanged with a fixed + * size ringbuffer. This is ideal for variable sized audio packets + * or compressed media. + * + * \subsection ssec_stream_target Stream target + * + * To make the newly connected stream automatically connect to an existing + * PipeWire node, use the \ref PW_STREAM_FLAG_AUTOCONNECT and the port_path + * argument while connecting. + * + * \subsection ssec_stream_formats Stream formats + * + * An array of possible formats that this stream can consume or provide + * must be specified. + * + * \section sec_format Format negotiation + * + * After connecting the stream, it will transition to the \ref + * PW_STREAM_STATE_CONFIGURE state. In this state the format will be + * negotiated by the PipeWire server. + * + * Once the format has been selected, the format_changed signal is + * emited with the configured format as a parameter. + * + * The client should now prepare itself to deal with the format and + * complete the negotiation procedure with a call to \ref + * pw_stream_finish_format(). + * + * As arguments to \ref pw_stream_finish_format() an array of spa_param + * structures must be given. They contain parameters such as buffer size, + * number of buffers, required metadata and other parameters for the + * media buffers. + * + * \section sec_buffers Buffer negotiation + * + * After completing the format negotiation, PipeWire will allocate and + * notify the stream of the buffers that will be used to exchange data + * between client and server. + * + * With the add_buffer signal, a stream will be notified of a new buffer + * that can be used for data transport. + * + * Afer the buffers are negotiated, the stream will transition to the + * \ref PW_STREAM_STATE_PAUSED state. + * + * \section sec_streaming Streaming + * + * From the \ref PW_STREAM_STATE_PAUSED state, the stream can be set to + * the \ref PW_STREAM_STATE_STREAMING state by the PipeWire server when + * data transport is started. + * + * Depending on how the stream was connected it will need to Produce or + * Consume data for/from PipeWire as explained in the following + * subsections. + * + * \subsection ssec_consume Consume data + * + * The new_buffer signal is emited for each new buffer can can be + * consumed. + * + * \ref pw_stream_peek_buffer() should be used to get the data and metadata + * of the buffer. + * + * When the buffer is no longer in use, call \ref pw_stream_recycle_buffer() + * to let PipeWire reuse the buffer. + * + * \subsection ssec_produce Produce data + * + * The need_buffer signal is emited when PipeWire needs a new buffer for this + * stream. + * + * \ref pw_stream_get_empty_buffer() gives the id of an empty buffer. + * Use \ref pw_stream_peek_buffer() to get the data and metadata that should + * be filled. + * + * To send the filled buffer, use \ref pw_stream_send_buffer(). + * + * The new_buffer signal is emited when PipeWire no longer uses the buffer + * and it can be safely reused. + * + * \section sec_stream_disconnect Disconnect + * + * Use \ref pw_stream_disconnect() to disconnect a stream after use. + */ + +/** \enum pw_stream_state The state of a stream \memberof pw_stream */ +enum pw_stream_state { + PW_STREAM_STATE_ERROR = -1, /**< the strean is in error */ + PW_STREAM_STATE_UNCONNECTED = 0, /**< unconnected */ + PW_STREAM_STATE_CONNECTING = 1, /**< connection is in progress */ + PW_STREAM_STATE_CONFIGURE = 2, /**< stream is being configured */ + PW_STREAM_STATE_READY = 3, /**< stream is ready */ + PW_STREAM_STATE_PAUSED = 4, /**< paused, fully configured but not + * processing data yet */ + PW_STREAM_STATE_STREAMING = 5 /**< streaming */ +}; + +/** Convert a stream state to a readable string \memberof pw_stream */ +const char * pw_stream_state_as_string(enum pw_stream_state state); + +/** \enum pw_stream_flags Extra flags that can be used in \ref pw_stream_connect() \memberof pw_stream */ +enum pw_stream_flags { + PW_STREAM_FLAG_NONE = 0, /**< no flags */ + PW_STREAM_FLAG_AUTOCONNECT = (1 << 0), /**< try to automatically connect + * this stream */ + PW_STREAM_FLAG_CLOCK_UPDATE = (1 << 1), /**< request periodic clock updates for + * this stream */ +}; + +/** \enum pw_stream_mode The method for transfering data for a stream \memberof pw_stream */ +enum pw_stream_mode { + PW_STREAM_MODE_BUFFER = 0, /**< data is placed in buffers */ + PW_STREAM_MODE_RINGBUFFER = 1, /**< a ringbuffer is used to exchange data */ +}; + +/** A time structure \memberof pw_stream */ +struct pw_time { + int64_t now; /**< the monotonic time */ + int64_t ticks; /**< the ticks at \a now */ + int32_t rate; /**< the rate of \a ticks */ +}; + +/** \class pw_stream + * + * \brief PipeWire stream object class + * + * The stream object provides a convenient way to send and + * receive data streams from/to PipeWire. + * + * See also \ref page_streams and \ref page_client_api + */ +struct pw_stream { + struct pw_remote *remote; /**< the owner remote */ + struct spa_list link; /**< link in the remote */ + + char *name; /**< the name of the stream */ + uint32_t node_id; /**< node id for remote node, available from + * CONFIGURE state and higher */ + struct pw_properties *properties; /**< properties of the stream */ + + /** Emited when the stream is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_stream *stream)); + + enum pw_stream_state state; /**< stream state */ + char *error; /**< error reason when state is in error */ + /** Emited when the stream state changes */ + PW_SIGNAL(state_changed, (struct pw_listener *listener, struct pw_stream *stream)); + + /** Emited when the format changed. The listener should call + * pw_stream_finish_format() from within this signal or later to complete + * the format negotiation */ + PW_SIGNAL(format_changed, (struct pw_listener *listener, + struct pw_stream *stream, struct spa_format *format)); + + /** Emited when a new buffer was created for this stream */ + PW_SIGNAL(add_buffer, (struct pw_listener *listener, + struct pw_stream *stream, uint32_t id)); + /** Emited when a buffer was destroyed for this stream */ + PW_SIGNAL(remove_buffer, (struct pw_listener *listener, + struct pw_stream *stream, uint32_t id)); + /** Emited when a buffer can be reused (for playback streams) or + * is filled (for capture streams */ + PW_SIGNAL(new_buffer, (struct pw_listener *listener, + struct pw_stream *stream, uint32_t id)); + /** Emited when a buffer is needed (for playback streams) */ + PW_SIGNAL(need_buffer, (struct pw_listener *listener, struct pw_stream *stream)); +}; + +/** Create a new unconneced \ref pw_stream \memberof pw_stream + * \return a newly allocated \ref pw_stream */ +struct pw_stream * +pw_stream_new(struct pw_remote *remote, /**< a \ref pw_remote */ + const char *name, /**< a stream name */ + struct pw_properties *props /**< stream properties, ownership is taken */); + +/** Destroy a stream \memberof pw_stream */ +void pw_stream_destroy(struct pw_stream *stream); + +/** Connect a stream for input or output on \a port_path. \memberof pw_stream + * \return true on success. + * + * When \a mode is \ref PW_STREAM_MODE_BUFFER, you should connect to the new-buffer + * signal and use pw_stream_peek_buffer() to get the latest metadata and + * data. */ +bool +pw_stream_connect(struct pw_stream *stream, /**< a \ref pw_stream */ + enum pw_direction direction, /**< the stream direction */ + enum pw_stream_mode mode, /**< a \ref pw_stream_mode */ + const char *port_path, /**< the port path to connect to or NULL + * to let the server choose a port */ + enum pw_stream_flags flags, /**< stream flags */ + uint32_t n_possible_formats, /**< number of items in \a possible_formats */ + const struct spa_format **possible_formats /**< an array with possible accepted formats */); + +/** Disconnect \a stream \memberof pw_stream */ +void pw_stream_disconnect(struct pw_stream *stream); + +/** Complete the negotiation process with result code \a res \memberof pw_stream + * + * This function should be called after notification of the format. + + * When \a res indicates success, \a params contain the parameters for the + * allocation state. */ +void +pw_stream_finish_format(struct pw_stream *stream, /**< a \ref pw_stream */ + int res, /**< a result code */ + struct spa_param **params, /**< an array of pointers to \ref spa_param */ + uint32_t n_params /**< number of elements in \a params */); + +/** Query the time on the stream \memberof pw_stream */ +bool pw_stream_get_time(struct pw_stream *stream, struct pw_time *time); + +/** Get the id of an empty buffer that can be filled \memberof pw_stream + * \return the id of an empty buffer or \ref SPA_ID_INVALID when no buffer is + * available. */ +uint32_t pw_stream_get_empty_buffer(struct pw_stream *stream); + +/** Recycle the buffer with \a id \memberof pw_stream + * \return true on success, false when \a id is invalid or not a used buffer + * Let the PipeWire server know that it can reuse the buffer with \a id. */ +bool pw_stream_recycle_buffer(struct pw_stream *stream, uint32_t id); + +/** Get the buffer with \a id from \a stream \memberof pw_stream + * \return a \ref spa_buffer or NULL when there is no buffer + * + * This function should be called from the new-buffer signal callback. */ +struct spa_buffer * +pw_stream_peek_buffer(struct pw_stream *stream, uint32_t id); + +/** Send a buffer with \a id to \a stream \memberof pw_stream + * \return true when \a id was handled, false on error + * + * For provider or playback streams, this function should be called whenever + * there is a new buffer available. */ +bool pw_stream_send_buffer(struct pw_stream *stream, uint32_t id); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_STREAM_H__ */ diff --git a/src/pipewire/thread-loop.c b/src/pipewire/thread-loop.c new file mode 100644 index 00000000..d2c153a6 --- /dev/null +++ b/src/pipewire/thread-loop.c @@ -0,0 +1,297 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <pthread.h> + +#include "pipewire.h" +#include "thread-loop.h" + +/** \cond */ +struct thread_loop { + struct pw_thread_loop this; + + char *name; + + pthread_mutex_t lock; + pthread_cond_t cond; + pthread_cond_t accept_cond; + + bool running; + pthread_t thread; + + struct spa_loop_control_hooks hooks; + const struct spa_loop_control_hooks *old; + + struct spa_source *event; + + int n_waiting; + int n_waiting_for_accept; +}; +/** \endcond */ + +static void before(const struct spa_loop_control_hooks *hooks) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(hooks, struct thread_loop, hooks); + pthread_mutex_unlock(&impl->lock); +} + +static void after(const struct spa_loop_control_hooks *hooks) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(hooks, struct thread_loop, hooks); + pthread_mutex_lock(&impl->lock); +} + +static const struct spa_loop_control_hooks impl_hooks = { + SPA_VERSION_LOOP_CONTROL_HOOKS, + { NULL, }, + before, + after, +}; + +static void do_stop(struct spa_loop_utils *utils, struct spa_source *source, void *data) +{ + struct thread_loop *impl = data; + impl->running = false; +} + +/** Create a new \ref pw_thread_loop + * + * \param loop the loop to wrap + * \param name the name of the thread or NULL + * \return a newly allocated \ref pw_thread_loop + * + * Make a new \ref pw_thread_loop that will run \a loop in + * a thread with \a name. + * + * After this function you should probably call pw_thread_loop_start() to + * actually start the thread + * + * \memberof pw_thread_loop + */ +struct pw_thread_loop *pw_thread_loop_new(struct pw_loop *loop, const char *name) +{ + struct thread_loop *impl; + struct pw_thread_loop *this; + pthread_mutexattr_t attr; + + impl = calloc(1, sizeof(struct thread_loop)); + if (impl == NULL) + return NULL; + + this = &impl->this; + pw_log_debug("thread-loop %p: new", impl); + + this->loop = loop; + this->name = name ? strdup(name) : NULL; + + impl->hooks = impl_hooks; + pw_loop_add_hooks(loop, &impl->hooks); + + pw_signal_init(&this->destroy_signal); + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&impl->lock, &attr); + pthread_cond_init(&impl->cond, NULL); + pthread_cond_init(&impl->accept_cond, NULL); + + impl->event = pw_loop_add_event(this->loop, do_stop, impl); + + return this; +} + +/** Destroy a threaded loop \memberof pw_thread_loop */ +void pw_thread_loop_destroy(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + + pw_signal_emit(&loop->destroy_signal, loop); + + pw_thread_loop_stop(loop); + + if (loop->name) + free(loop->name); + pthread_mutex_destroy(&impl->lock); + pthread_cond_destroy(&impl->cond); + pthread_cond_destroy(&impl->accept_cond); + + spa_list_remove(&impl->hooks.link); + + free(impl); +} + +static void *do_loop(void *user_data) +{ + struct thread_loop *impl = user_data; + struct pw_thread_loop *this = &impl->this; + int res; + + pthread_mutex_lock(&impl->lock); + pw_log_debug("thread-loop %p: enter thread", this); + pw_loop_enter(this->loop); + + while (impl->running) { + if ((res = pw_loop_iterate(this->loop, -1)) < 0) + pw_log_warn("thread-loop %p: iterate error %d", this, res); + } + pw_log_debug("thread-loop %p: leave thread", this); + pw_loop_leave(this->loop); + pthread_mutex_unlock(&impl->lock); + + return NULL; +} + +/** Start the thread to handle \a loop + * + * \param loop a \ref pw_thread_loop + * \return \ref SPA_RESULT_OK on success + * + * \memberof pw_thread_loop + */ +int pw_thread_loop_start(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + + if (!impl->running) { + int err; + + impl->running = true; + if ((err = pthread_create(&impl->thread, NULL, do_loop, impl)) != 0) { + pw_log_warn("thread-loop %p: can't create thread: %s", impl, + strerror(err)); + impl->running = false; + return SPA_RESULT_ERROR; + } + } + return SPA_RESULT_OK; +} + +/** Quit the loop and stop its thread + * + * \param loop a \ref pw_thread_loop + * + * \memberof pw_thread_loop + */ +void pw_thread_loop_stop(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + + pw_log_debug("thread-loop: %p stopping", impl); + if (impl->running) { + pw_log_debug("thread-loop: %p signal", impl); + pw_loop_signal_event(loop->loop, impl->event); + pw_log_debug("thread-loop: %p join", impl); + pthread_join(impl->thread, NULL); + pw_log_debug("thread-loop: %p joined", impl); + impl->running = false; + } + pw_log_debug("thread-loop: %p stopped", impl); +} + +/** Lock the mutex associated with \a loop + * + * \param loop a \ref pw_thread_loop + * + * \memberof pw_thread_loop + */ +void pw_thread_loop_lock(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + pthread_mutex_lock(&impl->lock); +} + +/** Unlock the mutex associated with \a loop + * + * \param loop a \ref pw_thread_loop + * + * \memberof pw_thread_loop + */ +void pw_thread_loop_unlock(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + pthread_mutex_unlock(&impl->lock); +} + +/** Signal the thread + * + * \param loop a \ref pw_thread_loop to signal + * \param wait_for_accept if we need to wait for accept + * + * Signal the thread of \a loop. If \a wait_for_accept is true, + * this function waits until \ref pw_thread_loop_accept() is called. + * + * \memberof pw_thread_loop + */ +void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + + if (impl->n_waiting > 0) + pthread_cond_broadcast(&impl->cond); + + if (wait_for_accept) { + impl->n_waiting_for_accept++; + + while (impl->n_waiting_for_accept > 0) + pthread_cond_wait(&impl->accept_cond, &impl->lock); + } +} + +/** Wait for the loop thread to call \ref pw_thread_loop_signal() + * + * \param loop a \ref pw_thread_loop to signal + * + * \memberof pw_thread_loop + */ +void pw_thread_loop_wait(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + + impl->n_waiting++; + + pthread_cond_wait(&impl->cond, &impl->lock); + impl->n_waiting--; +} + +/** Signal the loop thread waiting for accept with \ref pw_thread_loop_signal() + * + * \param loop a \ref pw_thread_loop to signal + * + * \memberof pw_thread_loop + */ +void pw_thread_loop_accept(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + + impl->n_waiting_for_accept--; + pthread_cond_signal(&impl->accept_cond); +} + +/** Check if we are inside the thread of the loop + * + * \param loop a \ref pw_thread_loop to signal + * \return true when called inside the thread of \a loop. + * + * \memberof pw_thread_loop + */ +bool pw_thread_loop_in_thread(struct pw_thread_loop *loop) +{ + struct thread_loop *impl = SPA_CONTAINER_OF(loop, struct thread_loop, this); + return pthread_self() == impl->thread; +} diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h new file mode 100644 index 00000000..954edcec --- /dev/null +++ b/src/pipewire/thread-loop.h @@ -0,0 +1,133 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_THREAD_LOOP_H__ +#define __PIPEWIRE_THREAD_LOOP_H__ + +#include <pipewire/loop.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** \page page_thread_loop Threaded Loop + * + * \section sec_thread_loop_overview Overview + * + * The threaded loop implementation is a special wrapper around the + * regular \ref pw_loop implementation. + * + * The added feature in the threaded loop is that it spawns a new thread + * that runs the wrapped loop. This allows a synchronous application to use + * the asynchronous API without risking to stall the PipeWire library. + * + * \section sec_thread_loop_create Creation + * + * A \ref pw_thread_loop object is created using pw_thread_loop_new(). + * The \ref pw_loop to wrap must be given as an argument along with the name + * for the thread that will be spawned. + * + * After allocating the object, the thread must be started with + * pw_thread_loop_start() + * + * \section sec_thread_loop_destruction Destruction + * + * When the PipeWire connection has been terminated, the thread must be + * stopped and the resources freed. Stopping the thread is done using + * pw_thread_loop_stop(), which must be called without the lock (see + * below) held. When that function returns, the thread is stopped and the + * \ref pw_thread_loop object can be freed using pw_thread_loop_destroy(). + * + * \section sec_thread_loop_locking Locking + * + * Since the PipeWire API doesn't allow concurrent accesses to objects, + * a locking scheme must be used to guarantee safe usage. The threaded + * loop API provides such a scheme through the functions + * pw_thread_loop_lock() and pw_thread_loop_unlock(). + * + * The lock is recursive, so it's safe to use it multiple times from the same + * thread. Just make sure you call pw_thread_loop_unlock() the same + * number of times you called pw_thread_loop_lock(). + * + * The lock needs to be held whenever you call any PipeWire function that + * uses an object associated with this loop. Make sure you do not hold + * on to the lock more than necessary though, as the threaded loop stops + * while the lock is held. + * + * \section sec_thread_loop_signals Signals and Callbacks + * + * All signals and callbacks are called with the thread lock held. + * + */ +/** \class pw_thread_loop + * + * \brief PipeWire threaded loop object + * + * The threaded loop object runs a \ref pw_loop in a separate thread + * and ensures proper locking is done. + * + * All of the loop callbacks will be executed with the loop + * lock held. + * + * See also \ref page_thread_loop + */ +struct pw_thread_loop { + struct pw_loop *loop; /**< the \ref pw_loop that is wrapped */ + char *name; /**< the thread name */ + + /** Emited when the threaded loop is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, + struct pw_thread_loop *loop)); +}; + +struct pw_thread_loop * +pw_thread_loop_new(struct pw_loop *loop, const char *name); + +void +pw_thread_loop_destroy(struct pw_thread_loop *loop); + +int +pw_thread_loop_start(struct pw_thread_loop *loop); + +void +pw_thread_loop_stop(struct pw_thread_loop *loop); + +void +pw_thread_loop_lock(struct pw_thread_loop *loop); + +void +pw_thread_loop_unlock(struct pw_thread_loop *loop); + +void +pw_thread_loop_wait(struct pw_thread_loop *loop); + +void +pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept); + +void +pw_thread_loop_accept(struct pw_thread_loop *loop); + +bool +pw_thread_loop_in_thread(struct pw_thread_loop *loop); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_THREAD_LOOP_H__ */ diff --git a/src/pipewire/transport.c b/src/pipewire/transport.c new file mode 100644 index 00000000..e1054029 --- /dev/null +++ b/src/pipewire/transport.c @@ -0,0 +1,309 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <unistd.h> +#include <errno.h> +#include <sys/mman.h> + +#include <pipewire/log.h> +#include <pipewire/transport.h> + +/** \cond */ + +#define INPUT_BUFFER_SIZE (1<<12) +#define OUTPUT_BUFFER_SIZE (1<<12) + +struct transport { + struct pw_transport trans; + + struct pw_memblock mem; + size_t offset; + + struct spa_event current; + uint32_t current_index; +}; +/** \endcond */ + +static size_t transport_area_get_size(struct pw_transport_area *area) +{ + size_t size; + size = sizeof(struct pw_transport_area); + size += area->max_input_ports * sizeof(struct spa_port_io); + size += area->max_output_ports * sizeof(struct spa_port_io); + size += sizeof(struct spa_ringbuffer); + size += INPUT_BUFFER_SIZE; + size += sizeof(struct spa_ringbuffer); + size += OUTPUT_BUFFER_SIZE; + return size; +} + +static void transport_setup_area(void *p, struct pw_transport *trans) +{ + struct pw_transport_area *a; + + trans->area = a = p; + p = SPA_MEMBER(p, sizeof(struct pw_transport_area), struct spa_port_io); + + trans->inputs = p; + p = SPA_MEMBER(p, a->max_input_ports * sizeof(struct spa_port_io), void); + + trans->outputs = p; + p = SPA_MEMBER(p, a->max_output_ports * sizeof(struct spa_port_io), void); + + trans->input_buffer = p; + p = SPA_MEMBER(p, sizeof(struct spa_ringbuffer), void); + + trans->input_data = p; + p = SPA_MEMBER(p, INPUT_BUFFER_SIZE, void); + + trans->output_buffer = p; + p = SPA_MEMBER(p, sizeof(struct spa_ringbuffer), void); + + trans->output_data = p; + p = SPA_MEMBER(p, OUTPUT_BUFFER_SIZE, void); +} + +static void transport_reset_area(struct pw_transport *trans) +{ + int i; + struct pw_transport_area *a = trans->area; + + for (i = 0; i < a->max_input_ports; i++) { + trans->inputs[i].status = SPA_RESULT_OK; + trans->inputs[i].buffer_id = SPA_ID_INVALID; + } + for (i = 0; i < a->max_output_ports; i++) { + trans->outputs[i].status = SPA_RESULT_OK; + trans->outputs[i].buffer_id = SPA_ID_INVALID; + } + spa_ringbuffer_init(trans->input_buffer, INPUT_BUFFER_SIZE); + spa_ringbuffer_init(trans->output_buffer, OUTPUT_BUFFER_SIZE); +} + +/** Create a new transport + * \param max_input_ports maximum number of input_ports + * \param max_output_ports maximum number of output_ports + * \return a newly allocated \ref pw_transport + * \memberof pw_transport + */ +struct pw_transport *pw_transport_new(uint32_t max_input_ports, uint32_t max_output_ports) +{ + struct transport *impl; + struct pw_transport *trans; + struct pw_transport_area area; + + area.max_input_ports = max_input_ports; + area.n_input_ports = 0; + area.max_output_ports = max_output_ports; + area.n_output_ports = 0; + + impl = calloc(1, sizeof(struct transport)); + if (impl == NULL) + return NULL; + + impl->offset = 0; + + trans = &impl->trans; + pw_signal_init(&trans->destroy_signal); + + pw_memblock_alloc(PW_MEMBLOCK_FLAG_WITH_FD | + PW_MEMBLOCK_FLAG_MAP_READWRITE | + PW_MEMBLOCK_FLAG_SEAL, transport_area_get_size(&area), &impl->mem); + + memcpy(impl->mem.ptr, &area, sizeof(struct pw_transport_area)); + transport_setup_area(impl->mem.ptr, trans); + transport_reset_area(trans); + + return trans; +} + +struct pw_transport *pw_transport_new_from_info(struct pw_transport_info *info) +{ + struct transport *impl; + struct pw_transport *trans; + void *tmp; + + impl = calloc(1, sizeof(struct transport)); + if (impl == NULL) + return NULL; + + trans = &impl->trans; + pw_signal_init(&trans->destroy_signal); + + impl->mem.flags = PW_MEMBLOCK_FLAG_MAP_READWRITE | PW_MEMBLOCK_FLAG_WITH_FD; + impl->mem.fd = info->memfd; + impl->mem.offset = info->offset; + impl->mem.size = info->size; + if (pw_memblock_map(&impl->mem) != SPA_RESULT_OK) { + pw_log_warn("transport %p: failed to map fd %d: %s", impl, info->memfd, + strerror(errno)); + goto mmap_failed; + } + + impl->offset = info->offset; + + transport_setup_area(impl->mem.ptr, trans); + + tmp = trans->output_buffer; + trans->output_buffer = trans->input_buffer; + trans->input_buffer = tmp; + + tmp = trans->output_data; + trans->output_data = trans->input_data; + trans->input_data = tmp; + + return trans; + + mmap_failed: + free(impl); + return NULL; +} + +/** Destroy a transport + * \param trans a transport to destroy + * \memberof pw_transport + */ +void pw_transport_destroy(struct pw_transport *trans) +{ + struct transport *impl = (struct transport *) trans; + + pw_log_debug("transport %p: destroy", trans); + + pw_signal_emit(&trans->destroy_signal, trans); + + pw_memblock_free(&impl->mem); + free(impl); +} + +/** Get transport info + * \param trans the transport to get info of + * \param[out] info transport info + * \return 0 on success + * + * Fill \a info with the transport info of \a trans. This information can be + * passed to the client to set up the shared transport. + * + * \memberof pw_transport + */ +int pw_transport_get_info(struct pw_transport *trans, struct pw_transport_info *info) +{ + struct transport *impl = (struct transport *) trans; + + info->memfd = impl->mem.fd; + info->offset = impl->offset; + info->size = impl->mem.size; + + return SPA_RESULT_OK; +} + +/** Add an event to the transport + * \param trans the transport to send the event on + * \param event the event to add + * \return 0 on success, < 0 on error + * + * Write \a event to the shared ringbuffer and signal the other side that + * new data can be read. + * + * \memberof pw_transport + */ +int pw_transport_add_event(struct pw_transport *trans, struct spa_event *event) +{ + struct transport *impl = (struct transport *) trans; + int32_t filled, avail; + uint32_t size, index; + + if (impl == NULL || event == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + filled = spa_ringbuffer_get_write_index(trans->output_buffer, &index); + avail = trans->output_buffer->size - filled; + size = SPA_POD_SIZE(event); + if (avail < size) + return SPA_RESULT_ERROR; + + spa_ringbuffer_write_data(trans->output_buffer, + trans->output_data, + index & trans->output_buffer->mask, event, size); + spa_ringbuffer_write_update(trans->output_buffer, index + size); + + return SPA_RESULT_OK; +} + +/** Get next event from a transport + * \param trans the transport to get the event of + * \param[out] event the event to read + * \return 0 on success, < 0 on error, SPA_RESULT_ENUM_END when no more events + * are available. + * + * Get the skeleton next event from \a trans into \a event. This function will + * only read the head and object body of the event. + * + * After the complete size of the event has been calculated, you should call + * \ref pw_transport_parse_event() to read the complete event contents. + * + * \memberof pw_transport + */ +int pw_transport_next_event(struct pw_transport *trans, struct spa_event *event) +{ + struct transport *impl = (struct transport *) trans; + int32_t avail; + + if (impl == NULL || event == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + avail = spa_ringbuffer_get_read_index(trans->input_buffer, &impl->current_index); + if (avail < sizeof(struct spa_event)) + return SPA_RESULT_ENUM_END; + + spa_ringbuffer_read_data(trans->input_buffer, + trans->input_data, + impl->current_index & trans->input_buffer->mask, + &impl->current, sizeof(struct spa_event)); + + *event = impl->current; + + return SPA_RESULT_OK; +} + +/** Parse the complete event on transport + * \param trans the transport to read from + * \param[out] event memory that can hold the complete event + * \return 0 on success, < 0 on error + * + * Use this function after \ref pw_transport_next_event(). + * + * \memberof pw_transport + */ +int pw_transport_parse_event(struct pw_transport *trans, void *event) +{ + struct transport *impl = (struct transport *) trans; + uint32_t size; + + if (impl == NULL || event == NULL) + return SPA_RESULT_INVALID_ARGUMENTS; + + size = SPA_POD_SIZE(&impl->current); + + spa_ringbuffer_read_data(trans->input_buffer, + trans->input_data, + impl->current_index & trans->input_buffer->mask, event, size); + spa_ringbuffer_read_update(trans->input_buffer, impl->current_index + size); + + return SPA_RESULT_OK; +} diff --git a/src/pipewire/transport.h b/src/pipewire/transport.h new file mode 100644 index 00000000..402355c9 --- /dev/null +++ b/src/pipewire/transport.h @@ -0,0 +1,137 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_TRANSPORT_H__ +#define __PIPEWIRE_TRANSPORT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <string.h> + +#include <spa/defs.h> +#include <spa/node.h> + +#include <pipewire/mem.h> +#include <pipewire/sig.h> + +/** information about the transport region \memberof pw_transport */ +struct pw_transport_info { + int memfd; /**< the memfd of the transport area */ + uint32_t offset; /**< offset to map \a memfd at */ + uint32_t size; /**< size of memfd mapping */ +}; + +/** Shared structure between client and server \memberof pw_transport */ +struct pw_transport_area { + uint32_t max_input_ports; /**< max input ports of the node */ + uint32_t n_input_ports; /**< number of input ports of the node */ + uint32_t max_output_ports; /**< max output ports of the node */ + uint32_t n_output_ports; /**< number of output ports of the node */ +}; + +/** \class pw_transport + * + * \brief Transport object + * + * The transport object contains shared data and ringbuffers to exchange + * events and data between the server and the client in a low-latency and + * lockfree way. + */ +struct pw_transport { + /** Emited when the transport is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_transport *trans)); + + struct pw_transport_area *area; /**< the transport area */ + struct spa_port_io *inputs; /**< array of input port io */ + struct spa_port_io *outputs; /**< array of output port io */ + void *input_data; /**< input memory for ringbuffer */ + struct spa_ringbuffer *input_buffer; /**< ringbuffer for input memory */ + void *output_data; /**< output memory for ringbuffer */ + struct spa_ringbuffer *output_buffer; /**< ringbuffer for output memory */ +}; + +struct pw_transport * +pw_transport_new(uint32_t max_input_ports, uint32_t max_output_ports); + +struct pw_transport * +pw_transport_new_from_info(struct pw_transport_info *info); + +void +pw_transport_destroy(struct pw_transport *trans); + +int +pw_transport_get_info(struct pw_transport *trans, struct pw_transport_info *info); + +int +pw_transport_add_event(struct pw_transport *trans, struct spa_event *event); + +int +pw_transport_next_event(struct pw_transport *trans, struct spa_event *event); + +int +pw_transport_parse_event(struct pw_transport *trans, void *event); + +#define PIPEWIRE_TYPE_EVENT__Transport SPA_TYPE_EVENT_BASE "Transport" +#define PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE PIPEWIRE_TYPE_EVENT__Transport ":" + +#define PIPEWIRE_TYPE_EVENT_TRANSPORT__HaveOutput PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE "HaveOutput" +#define PIPEWIRE_TYPE_EVENT_TRANSPORT__NeedInput PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE "NeedInput" +#define PIPEWIRE_TYPE_EVENT_TRANSPORT__ReuseBuffer PIPEWIRE_TYPE_EVENT_TRANSPORT_BASE "ReuseBuffer" + +struct pw_type_event_transport { + uint32_t HaveOutput; + uint32_t NeedInput; + uint32_t ReuseBuffer; +}; + +static inline void +pw_type_event_transport_map(struct spa_type_map *map, struct pw_type_event_transport *type) +{ + if (type->HaveOutput == 0) { + type->HaveOutput = spa_type_map_get_id(map, PIPEWIRE_TYPE_EVENT_TRANSPORT__HaveOutput); + type->NeedInput = spa_type_map_get_id(map, PIPEWIRE_TYPE_EVENT_TRANSPORT__NeedInput); + type->ReuseBuffer = spa_type_map_get_id(map, PIPEWIRE_TYPE_EVENT_TRANSPORT__ReuseBuffer); + } +} + +struct pw_event_transport_reuse_buffer_body { + struct spa_pod_object_body body; + struct spa_pod_int port_id; + struct spa_pod_int buffer_id; +}; + +struct pw_event_transport_reuse_buffer { + struct spa_pod pod; + struct pw_event_transport_reuse_buffer_body body; +}; + +#define PW_EVENT_TRANSPORT_REUSE_BUFFER_INIT(type,port_id,buffer_id) \ + SPA_EVENT_INIT_COMPLEX(struct pw_event_transport_reuse_buffer, \ + sizeof(struct pw_event_transport_reuse_buffer_body), type, \ + SPA_POD_INT_INIT(port_id), \ + SPA_POD_INT_INIT(buffer_id)) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_TRANSPORT_H__ */ diff --git a/src/pipewire/type.c b/src/pipewire/type.c new file mode 100644 index 00000000..2ec057cf --- /dev/null +++ b/src/pipewire/type.c @@ -0,0 +1,125 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> + +#include "spa/defs.h" +#include "spa/clock.h" +#include "spa/type-map.h" +#include "spa/monitor.h" + +#include "pipewire/pipewire.h" +#include "pipewire/type.h" + +#include "pipewire/node-factory.h" + + +/** Initializes the type system + * \param type a type structure + * \memberof pw_type + */ +void pw_type_init(struct pw_type *type) +{ + type->map = pw_get_support_interface(SPA_TYPE__TypeMap); + + type->core = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Core); + type->registry = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Registry); + type->node = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Node); + type->node_factory = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__NodeFactory); + type->link = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Link); + type->client = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Client); + type->module = spa_type_map_get_id(type->map, PIPEWIRE_TYPE__Module); + + type->spa_log = spa_type_map_get_id(type->map, SPA_TYPE__Log); + type->spa_node = spa_type_map_get_id(type->map, SPA_TYPE__Node); + type->spa_clock = spa_type_map_get_id(type->map, SPA_TYPE__Clock); + type->spa_monitor = spa_type_map_get_id(type->map, SPA_TYPE__Monitor); + type->spa_format = spa_type_map_get_id(type->map, SPA_TYPE__Format); + type->spa_props = spa_type_map_get_id(type->map, SPA_TYPE__Props); + + spa_type_meta_map(type->map, &type->meta); + spa_type_data_map(type->map, &type->data); + spa_type_event_node_map(type->map, &type->event_node); + spa_type_command_node_map(type->map, &type->command_node); + spa_type_monitor_map(type->map, &type->monitor); + spa_type_param_alloc_buffers_map(type->map, &type->param_alloc_buffers); + spa_type_param_alloc_meta_enable_map(type->map, &type->param_alloc_meta_enable); + spa_type_param_alloc_video_padding_map(type->map, &type->param_alloc_video_padding); + + pw_type_event_transport_map(type->map, &type->event_transport); +} + +bool pw_pod_remap_data(uint32_t type, void *body, uint32_t size, struct pw_map *types) +{ + void *t; + switch (type) { + case SPA_POD_TYPE_ID: + if ((t = pw_map_lookup(types, *(int32_t *) body)) == NULL) + return false; + *(int32_t *) body = PW_MAP_PTR_TO_ID(t); + break; + + case SPA_POD_TYPE_PROP: + { + struct spa_pod_prop_body *b = body; + + if ((t = pw_map_lookup(types, b->key)) == NULL) + return false; + b->key = PW_MAP_PTR_TO_ID(t); + + if (b->value.type == SPA_POD_TYPE_ID) { + void *alt; + if (!pw_pod_remap_data + (b->value.type, SPA_POD_BODY(&b->value), b->value.size, types)) + return false; + + SPA_POD_PROP_ALTERNATIVE_FOREACH(b, size, alt) + if (!pw_pod_remap_data(b->value.type, alt, b->value.size, types)) + return false; + } + break; + } + case SPA_POD_TYPE_OBJECT: + { + struct spa_pod_object_body *b = body; + struct spa_pod *p; + + if ((t = pw_map_lookup(types, b->type)) == NULL) + return false; + b->type = PW_MAP_PTR_TO_ID(t); + + SPA_POD_OBJECT_BODY_FOREACH(b, size, p) + if (!pw_pod_remap_data(p->type, SPA_POD_BODY(p), p->size, types)) + return false; + break; + } + case SPA_POD_TYPE_STRUCT: + { + struct spa_pod *b = body, *p; + + SPA_POD_FOREACH(b, size, p) + if (!pw_pod_remap_data(p->type, SPA_POD_BODY(p), p->size, types)) + return false; + break; + } + default: + break; + } + return true; +} diff --git a/src/pipewire/type.h b/src/pipewire/type.h new file mode 100644 index 00000000..3486343a --- /dev/null +++ b/src/pipewire/type.h @@ -0,0 +1,126 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_TYPE_H__ +#define __PIPEWIRE_TYPE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/type-map.h> +#include <spa/event-node.h> +#include <spa/command-node.h> +#include <spa/monitor.h> +#include <spa/param-alloc.h> + +#include <pipewire/map.h> +#include <pipewire/transport.h> + +#define PIPEWIRE_TYPE__Core "PipeWire:Object:Core" +#define PIPEWIRE_TYPE_CORE_BASE PIPEWIRE_TYPE__Core ":" + +#define PIPEWIRE_TYPE__Registry "PipeWire:Object:Registry" +#define PIPEWIRE_TYPE_REGISYRY_BASE PIPEWIRE_TYPE__Registry ":" + +#define PIPEWIRE_TYPE__Node "PipeWire:Object:Node" +#define PIPEWIRE_TYPE_NODE_BASE PIPEWIRE_TYPE__Node ":" + +#define PIPEWIRE_TYPE__Client "PipeWire:Object:Client" +#define PIPEWIRE_TYPE_CLIENT_BASE PIPEWIRE_TYPE__Client ":" + +#define PIPEWIRE_TYPE__Link "PipeWire:Object:Link" +#define PIPEWIRE_TYPE_LINK_BASE PIPEWIRE_TYPE__Link ":" + +#define PIPEWIRE_TYPE__Module "PipeWire:Object:Module" +#define PIPEWIRE_TYPE_MODULE_BASE PIPEWIRE_TYPE__Module ":" + +#define PW_TYPE__Protocol "PipeWire:Protocol" +#define PW_TYPE_PROTOCOL_BASE PW_TYPE__Protocol ":" + +/** \class pw_interface + * \brief The interface definition + * + * The interface implements the methods and events for a \ref + * pw_proxy. It typically implements marshal functions for the + * methods and calls the user installed implementation after + * demarshalling the events. + * + * \sa pw_proxy, pw_resource + */ +struct pw_interface { + const char *type; /**< interface type */ + uint32_t version; /**< version */ + uint32_t n_methods; /**< number of methods in the interface */ + const void *methods; /**< method implementations of the interface */ + uint32_t n_events; /**< number of events in the interface */ + const void *events; /**< event implementations of the interface */ +}; + +/** \class pw_type + * \brief PipeWire type support struct + * + * This structure contains some of the most common types + * and should be initialized with \ref pw_type_init() */ +struct pw_type { + struct spa_type_map *map; /**< the type mapper */ + + uint32_t core; + uint32_t registry; + uint32_t node; + uint32_t node_factory; + uint32_t link; + uint32_t client; + uint32_t module; + + uint32_t spa_log; + uint32_t spa_node; + uint32_t spa_clock; + uint32_t spa_monitor; + uint32_t spa_format; + uint32_t spa_props; + + struct spa_type_meta meta; + struct spa_type_data data; + struct spa_type_event_node event_node; + struct spa_type_command_node command_node; + struct spa_type_monitor monitor; + struct spa_type_param_alloc_buffers param_alloc_buffers; + struct spa_type_param_alloc_meta_enable param_alloc_meta_enable; + struct spa_type_param_alloc_video_padding param_alloc_video_padding; + struct pw_type_event_transport event_transport; +}; + +void +pw_type_init(struct pw_type *type); + +bool +pw_pod_remap_data(uint32_t type, void *body, uint32_t size, struct pw_map *types); + +static inline bool +pw_pod_remap(struct spa_pod *pod, struct pw_map *types) +{ + return pw_pod_remap_data(pod->type, SPA_POD_BODY(pod), pod->size, types); +} + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_TYPE_H__ */ diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c new file mode 100644 index 00000000..744997d2 --- /dev/null +++ b/src/pipewire/utils.c @@ -0,0 +1,131 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <string.h> + +#include <pipewire/array.h> +#include <pipewire/log.h> +#include <pipewire/utils.h> + +/** Split a string based on delimiters + * \param str a string to split + * \param delimiter delimiter characters to split on + * \param[out] len the length of the current string + * \param[in,out] state a state variable + * \return a string or NULL when the end is reached + * + * Repeatedly call this function to split \a str into all substrings + * delimited by \a delimiter. \a state should be set to NULL on the first + * invocation and passed to the function until NULL is returned. + * + * \memberof pw_utils + */ +const char *pw_split_walk(const char *str, const char *delimiter, size_t * len, const char **state) +{ + const char *s = *state ? *state : str; + + if (*s == '\0') + return NULL; + + *len = strcspn(s, delimiter); + + *state = s + *len; + *state += strspn(*state, delimiter); + + return s; +} + +/** Split a string based on delimiters + * \param str a string to split + * \param delimiter delimiter characters to split on + * \param max_tokens the max number of tokens to split + * \param[out] n_tokens the number of tokens + * \return a NULL terminated array of strings that should be + * freed with \ref pw_free_strv. + * + * \memberof pw_utils + */ +char **pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens) +{ + const char *state = NULL, *s = NULL; + struct pw_array arr; + size_t len; + int n = 0; + + pw_array_init(&arr, 16); + + s = pw_split_walk(str, delimiter, &len, &state); + while (s && n + 1 < max_tokens) { + pw_array_add_ptr(&arr, strndup(s, len)); + s = pw_split_walk(str, delimiter, &len, &state); + n++; + } + if (s) { + pw_array_add_ptr(&arr, strdup(s)); + n++; + } + pw_array_add_ptr(&arr, NULL); + + *n_tokens = n; + + return arr.data; +} + +/** Free a NULL terminated array of strings + * \param str a NULL terminated array of string + * + * Free all the strings in the array and the array + * + * \memberof pw_utils + */ +void pw_free_strv(char **str) +{ + int i; + for (i = 0; str[i]; i++) + free(str[i]); + free(str); +} + +/** Strip all whitespace before and after a string + * \param str a string to strip + * \param whitespace characters to strip + * \return the stripped part of \a str + * + * Strip whitespace before and after \a str. \a str will be + * modified. + * + * \memberof pw_utils + */ +char *pw_strip(char *str, const char *whitespace) +{ + char *e, *l = NULL; + + str += strspn(str, whitespace); + + for (e = str; *e; e++) + if (!strchr(whitespace, *e)) + l = e; + + if (l) + *(l + 1) = '\0'; + else + *str = '\0'; + + return str; +} diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h new file mode 100644 index 00000000..a23b1882 --- /dev/null +++ b/src/pipewire/utils.h @@ -0,0 +1,65 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_UTILS_H__ +#define __PIPEWIRE_UTILS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <spa/defs.h> +#include <spa/pod-utils.h> + +/** \class pw_utils + * + * Various utility functions + */ + +/** a function to destroy an item \memberof pw_utils */ +typedef void (*pw_destroy_t) (void *object); + +const char * +pw_split_walk(const char *str, const char *delimiter, size_t *len, const char **state); + +char ** +pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens); + +void +pw_free_strv(char **str); + +char * +pw_strip(char *str, const char *whitespace); + +/** Copy a pod structure \memberof pw_utils */ +static inline struct spa_pod * +pw_spa_pod_copy(const struct spa_pod *pod) +{ + return pod ? memcpy(malloc(SPA_POD_SIZE(pod)), pod, SPA_POD_SIZE(pod)) : NULL; +} + +#define spa_format_copy(f) ((struct spa_format*)pw_spa_pod_copy(&(f)->pod)) +#define spa_props_copy(p) ((struct spa_prop*)pw_spa_pod_copy(&(p)->object.pod)) +#define spa_param_copy(p) ((struct spa_param*)pw_spa_pod_copy(&(p)->object.pod)) + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __PIPEWIRE_UTILS_H__ */ diff --git a/src/pipewire/work-queue.c b/src/pipewire/work-queue.c new file mode 100644 index 00000000..5c309ea8 --- /dev/null +++ b/src/pipewire/work-queue.c @@ -0,0 +1,243 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> +#include <string.h> + +#include "pipewire/log.h" +#include "pipewire/work-queue.h" + +/** \cond */ +struct work_item { + uint32_t id; + void *obj; + uint32_t seq; + int res; + pw_work_func_t func; + void *data; + struct spa_list link; +}; + +struct impl { + struct pw_work_queue this; + + struct spa_source *wakeup; + uint32_t counter; + + struct spa_list work_list; + struct spa_list free_list; + int n_queued; +}; +/** \endcond */ + +static void process_work_queue(struct spa_loop_utils *utils, struct spa_source *source, void *data) +{ + struct impl *impl = data; + struct pw_work_queue *this = &impl->this; + struct work_item *item, *tmp; + + spa_list_for_each_safe(item, tmp, &impl->work_list, link) { + if (item->seq != SPA_ID_INVALID) { + pw_log_debug("work-queue %p: %d waiting for item %p %d", this, + impl->n_queued, item->obj, item->seq); + continue; + } + + if (item->res == SPA_RESULT_WAIT_SYNC && + item != spa_list_first(&impl->work_list, struct work_item, link)) { + pw_log_debug("work-queue %p: %d sync item %p not head", this, + impl->n_queued, item->obj); + continue; + } + + spa_list_remove(&item->link); + impl->n_queued--; + + if (item->func) { + pw_log_debug("work-queue %p: %d process work item %p %d %d", this, + impl->n_queued, item->obj, item->seq, item->res); + item->func(item->obj, item->data, item->res, item->id); + } + spa_list_insert(impl->free_list.prev, &item->link); + } +} + +/** Create a new \ref pw_work_queue + * + * \param loop the loop to use + * \return a newly allocated work queue + * + * \memberof pw_work_queue + */ +struct pw_work_queue *pw_work_queue_new(struct pw_loop *loop) +{ + struct impl *impl; + struct pw_work_queue *this; + + impl = calloc(1, sizeof(struct impl)); + pw_log_debug("work-queue %p: new", impl); + + this = &impl->this; + this->loop = loop; + pw_signal_init(&this->destroy_signal); + + impl->wakeup = pw_loop_add_event(this->loop, process_work_queue, impl); + + spa_list_init(&impl->work_list); + spa_list_init(&impl->free_list); + + return this; +} + +/** Destroy a work queue + * \param queue the work queue to destroy + * + * \memberof pw_work_queue + */ +void pw_work_queue_destroy(struct pw_work_queue *queue) +{ + struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this); + struct work_item *item, *tmp; + + pw_log_debug("work-queue %p: destroy", impl); + pw_signal_emit(&queue->destroy_signal, queue); + + pw_loop_destroy_source(queue->loop, impl->wakeup); + + spa_list_for_each_safe(item, tmp, &impl->work_list, link) { + pw_log_warn("work-queue %p: cancel work item %p %d %d", queue, + item->obj, item->seq, item->res); + free(item); + } + spa_list_for_each_safe(item, tmp, &impl->free_list, link) + free(item); + + free(impl); +} + +/** Add an item to the work queue + * + * \param queue the work queue + * \param obj the object owning the work item + * \param res a result code + * \param func a work function + * \param data passed to \a func + * + * \memberof pw_work_queue + */ +uint32_t +pw_work_queue_add(struct pw_work_queue *queue, void *obj, int res, pw_work_func_t func, void *data) +{ + struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this); + struct work_item *item; + bool have_work = false; + + if (!spa_list_is_empty(&impl->free_list)) { + item = spa_list_first(&impl->free_list, struct work_item, link); + spa_list_remove(&item->link); + } else { + item = malloc(sizeof(struct work_item)); + if (item == NULL) + return SPA_ID_INVALID; + } + item->id = ++impl->counter; + item->obj = obj; + item->func = func; + item->data = data; + + if (SPA_RESULT_IS_ASYNC(res)) { + item->seq = SPA_RESULT_ASYNC_SEQ(res); + item->res = res; + pw_log_debug("work-queue %p: defer async %d for object %p", queue, item->seq, obj); + } else if (res == SPA_RESULT_WAIT_SYNC) { + pw_log_debug("work-queue %p: wait sync object %p", queue, obj); + item->seq = SPA_ID_INVALID; + item->res = res; + have_work = true; + } else { + item->seq = SPA_ID_INVALID; + item->res = res; + have_work = true; + pw_log_debug("work-queue %p: defer object %p", queue, obj); + } + spa_list_insert(impl->work_list.prev, &item->link); + impl->n_queued++; + + if (have_work) + pw_loop_signal_event(impl->this.loop, impl->wakeup); + + return item->id; +} + +/** Cancel a work item + * \param queue the work queue + * \param obj the owner object + * \param id the wotk id to cancel + * + * \memberof pw_work_queue + */ +void pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id) +{ + struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this); + bool have_work = false; + struct work_item *item; + + spa_list_for_each(item, &impl->work_list, link) { + if ((id == SPA_ID_INVALID || item->id == id) && (obj == NULL || item->obj == obj)) { + pw_log_debug("work-queue %p: cancel defer %d for object %p", queue, + item->seq, item->obj); + item->seq = SPA_ID_INVALID; + item->func = NULL; + have_work = true; + } + } + if (have_work) + pw_loop_signal_event(impl->this.loop, impl->wakeup); +} + +/** Complete a work item + * \param queue the work queue + * \param obj the owner object + * \param seq the sequence number that completed + * \param res the result of the completed work + * + * \memberof pw_work_queue + */ +bool pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res) +{ + struct work_item *item; + struct impl *impl = SPA_CONTAINER_OF(queue, struct impl, this); + bool have_work = false; + + spa_list_for_each(item, &impl->work_list, link) { + if (item->obj == obj && item->seq == seq) { + pw_log_debug("work-queue %p: found defered %d for object %p", queue, seq, + obj); + item->seq = SPA_ID_INVALID; + item->res = res; + have_work = true; + } + } + if (!have_work) { + pw_log_debug("work-queue %p: no defered %d found for object %p", queue, seq, obj); + } else { + pw_loop_signal_event(impl->this.loop, impl->wakeup); + } + return have_work; +} diff --git a/src/pipewire/work-queue.h b/src/pipewire/work-queue.h new file mode 100644 index 00000000..ccd8a5de --- /dev/null +++ b/src/pipewire/work-queue.h @@ -0,0 +1,63 @@ +/* PipeWire + * Copyright (C) 2016 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __PIPEWIRE_WORK_QUEUE_H__ +#define __PIPEWIRE_WORK_QUEUE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <pipewire/loop.h> + +typedef void (*pw_work_func_t) (void *obj, void *data, int res, uint32_t id); + +/** \class pw_work_queue + * + * PipeWire work queue object + */ +struct pw_work_queue { + struct pw_loop *loop; /**< the loop used for handling work */ + + /** Emited when the work queue is destroyed */ + PW_SIGNAL(destroy_signal, (struct pw_listener *listener, struct pw_work_queue *queue)); +}; + +struct pw_work_queue * +pw_work_queue_new(struct pw_loop *loop); + +void +pw_work_queue_destroy(struct pw_work_queue *queue); + +uint32_t +pw_work_queue_add(struct pw_work_queue *queue, + void *obj, int res, + pw_work_func_t func, void *data); + +void +pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id); + +bool +pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res); + +#ifdef __cplusplus +} +#endif + +#endif /* __PIPEWIRE_WORK_QUEUE_H__ */ diff --git a/src/server/meson.build b/src/server/meson.build new file mode 100644 index 00000000..b0418eac --- /dev/null +++ b/src/server/meson.build @@ -0,0 +1,53 @@ +pipewirecore_headers = [ + 'client.h', + 'command.h', + 'core.h', + 'data-loop.h', + 'link.h', + 'main-loop.h', + 'module.h', + 'node.h', + 'node-factory.h', + 'port.h', + 'remote.h', + 'resource.h', + 'work-queue.h', +] + +pipewirecore_sources = [ + 'client.c', + 'command.c', + 'core.c', + 'data-loop.c', + 'link.c', + 'main-loop.c', + 'module.c', + 'node.c', + 'node-factory.c', + 'port.c', + 'remote.c', + 'resource.c', + 'work-queue.c', +] + +install_headers(pipewirecore_headers, subdir : 'pipewire/server') + +libpipewirecore_c_args = [ + '-DHAVE_CONFIG_H', + '-D_GNU_SOURCE', +] + +libpipewirecore = shared_library('pipewirecore-@0@'.format(apiversion), pipewirecore_sources, + version : libversion, + soversion : soversion, + c_args : libpipewirecore_c_args, + include_directories : [configinc, spa_inc], + link_with : spalib, + install : true, + dependencies : [mathlib, dl_lib, pipewire_dep], +) + +pipewirecore_dep = declare_dependency(link_with : libpipewirecore, + include_directories : [configinc, spa_inc], + dependencies : [pipewire_dep], +) diff --git a/src/tools/meson.build b/src/tools/meson.build new file mode 100644 index 00000000..ca8e467f --- /dev/null +++ b/src/tools/meson.build @@ -0,0 +1,5 @@ +executable('pipewire-monitor', + 'pipewire-monitor.c', + install: true, + dependencies : [pipewire_dep], +) diff --git a/src/tools/pipewire-monitor.c b/src/tools/pipewire-monitor.c new file mode 100644 index 00000000..3c35be12 --- /dev/null +++ b/src/tools/pipewire-monitor.c @@ -0,0 +1,356 @@ +/* PipeWire + * Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <stdio.h> + +#include <spa/lib/debug.h> + +#include <pipewire/pipewire.h> +#include <pipewire/sig.h> + +struct data { + bool running; + struct pw_loop *loop; + struct pw_core *core; + struct pw_remote *remote; + struct pw_proxy *registry_proxy; + + struct pw_listener on_info_changed; + struct pw_listener on_state_changed; +}; + +struct proxy_data { + void *info; +}; + +static void print_properties(struct spa_dict *props, char mark) +{ + struct spa_dict_item *item; + + if (props == NULL) + return; + + printf("%c\tproperties:\n", mark); + spa_dict_for_each(item, props) { + printf("%c\t\t%s = \"%s\"\n", mark, item->key, item->value); + } +} + +#define MARK_CHANGE(f) ((print_mark && ((info)->change_mask & (1 << (f)))) ? '*' : ' ') + +static void on_info_changed(struct pw_listener *listener, struct pw_remote *remote) +{ + struct pw_core_info *info = remote->info; + bool print_all = true, print_mark = false; + + printf("\tid: %u\n", info->id); + printf("\ttype: %s\n", PIPEWIRE_TYPE__Core); + if (print_all) { + printf("%c\tuser-name: \"%s\"\n", MARK_CHANGE(0), info->user_name); + printf("%c\thost-name: \"%s\"\n", MARK_CHANGE(1), info->host_name); + printf("%c\tversion: \"%s\"\n", MARK_CHANGE(2), info->version); + printf("%c\tname: \"%s\"\n", MARK_CHANGE(3), info->name); + printf("%c\tcookie: %u\n", MARK_CHANGE(4), info->cookie); + print_properties(info->props, MARK_CHANGE(5)); + } +} + +static void module_event_info(void *object, struct pw_module_info *info) +{ + struct pw_proxy *proxy = object; + struct proxy_data *data = proxy->user_data; + bool print_all, print_mark; + + print_all = true; + if (data->info == NULL) { + printf("added:\n"); + print_mark = false; + } + else { + printf("changed:\n"); + print_mark = true; + } + + info = data->info = pw_module_info_update(data->info, info); + + printf("\tid: %u\n", info->id); + printf("\ttype: %s\n", PIPEWIRE_TYPE__Module); + if (print_all) { + printf("%c\tname: \"%s\"\n", MARK_CHANGE(0), info->name); + printf("%c\tfilename: \"%s\"\n", MARK_CHANGE(1), info->filename); + printf("%c\targs: \"%s\"\n", MARK_CHANGE(2), info->args); + print_properties(info->props, MARK_CHANGE(3)); + } +} + +static const struct pw_module_events module_events = { + &module_event_info, +}; + +static void node_event_info(void *object, struct pw_node_info *info) +{ + struct pw_proxy *proxy = object; + struct proxy_data *data = proxy->user_data; + bool print_all, print_mark; + + print_all = true; + if (data->info == NULL) { + printf("added:\n"); + print_mark = false; + } + else { + printf("changed:\n"); + print_mark = true; + } + + info = data->info = pw_node_info_update(data->info, info); + + printf("\tid: %u\n", info->id); + printf("\ttype: %s\n", PIPEWIRE_TYPE__Node); + if (print_all) { + int i; + + printf("%c\tname: \"%s\"\n", MARK_CHANGE(0), info->name); + printf("%c\tinput ports: %u/%u\n", MARK_CHANGE(1), info->n_input_ports, info->max_input_ports); + printf("%c\tinput formats:\n", MARK_CHANGE(2)); + for (i = 0; i < info->n_input_formats; i++) + spa_debug_format(info->input_formats[i]); + + printf("%c\toutput ports: %u/%u\n", MARK_CHANGE(3), info->n_output_ports, info->max_output_ports); + printf("%c\toutput formats:\n", MARK_CHANGE(4)); + for (i = 0; i < info->n_output_formats; i++) + spa_debug_format(info->output_formats[i]); + + printf("%c\tstate: \"%s\"", MARK_CHANGE(5), pw_node_state_as_string(info->state)); + if (info->state == PW_NODE_STATE_ERROR && info->error) + printf(" \"%s\"\n", info->error); + else + printf("\n"); + print_properties(info->props, MARK_CHANGE(6)); + } +} + +static const struct pw_node_events node_events = { + &node_event_info +}; + +static void client_event_info(void *object, struct pw_client_info *info) +{ + struct pw_proxy *proxy = object; + struct proxy_data *data = proxy->user_data; + bool print_all, print_mark; + + print_all = true; + if (data->info == NULL) { + printf("added:\n"); + print_mark = false; + } + else { + printf("changed:\n"); + print_mark = true; + } + + info = data->info = pw_client_info_update(data->info, info); + + printf("\tid: %u\n", info->id); + printf("\ttype: %s\n", PIPEWIRE_TYPE__Client); + if (print_all) { + print_properties(info->props, MARK_CHANGE(0)); + } +} + +static const struct pw_client_events client_events = { + &client_event_info +}; + +static void link_event_info(void *object, struct pw_link_info *info) +{ + struct pw_proxy *proxy = object; + struct proxy_data *data = proxy->user_data; + bool print_all, print_mark; + + print_all = true; + if (data->info == NULL) { + printf("added:\n"); + print_mark = false; + } + else { + printf("changed:\n"); + print_mark = true; + } + + info = data->info = pw_link_info_update(data->info, info); + + printf("\tid: %u\n", info->id); + printf("\ttype: %s\n", PIPEWIRE_TYPE__Link); + if (print_all) { + printf("%c\toutput-node-id: %u\n", MARK_CHANGE(0), info->output_node_id); + printf("%c\toutput-port-id: %u\n", MARK_CHANGE(1), info->output_port_id); + printf("%c\tinput-node-id: %u\n", MARK_CHANGE(2), info->input_node_id); + printf("%c\tinput-port-id: %u\n", MARK_CHANGE(3), info->input_port_id); + printf("%c\tformat:\n", MARK_CHANGE(4)); + if (info->format) + spa_debug_format(info->format); + else + printf("\t none\n"); + } +} + +static const struct pw_link_events link_events = { + &link_event_info +}; + +static void +destroy_proxy (void *data) +{ + struct pw_proxy *proxy = data; + struct proxy_data *user_data = proxy->user_data; + struct pw_core *core = proxy->remote->core; + + if (user_data->info == NULL) + return; + + if (proxy->type == core->type.core) { + pw_core_info_free (user_data->info); + } else if (proxy->type == core->type.node) { + pw_node_info_free (user_data->info); + } else if (proxy->type == core->type.module) { + pw_module_info_free (user_data->info); + } else if (proxy->type == core->type.client) { + pw_client_info_free (user_data->info); + } else if (proxy->type == core->type.link) { + pw_link_info_free (user_data->info); + } + user_data->info = NULL; +} + + +static void registry_event_global(void *object, uint32_t id, const char *type, uint32_t version) +{ + struct pw_proxy *registry_proxy = object; + struct data *data = registry_proxy->object; + struct pw_core *core = data->core; + struct pw_remote *remote = registry_proxy->remote; + struct pw_proxy *proxy = NULL; + uint32_t proxy_type, client_version; + const void *implementation; + + if (!strcmp(type, PIPEWIRE_TYPE__Node)) { + proxy_type = core->type.node; + implementation = &node_events; + client_version = PW_VERSION_NODE; + } else if (!strcmp(type, PIPEWIRE_TYPE__Module)) { + proxy_type = core->type.module; + implementation = &module_events; + client_version = PW_VERSION_MODULE; + } else if (!strcmp(type, PIPEWIRE_TYPE__Client)) { + proxy_type = core->type.client; + implementation = &client_events; + client_version = PW_VERSION_CLIENT; + } else if (!strcmp(type, PIPEWIRE_TYPE__Link)) { + proxy_type = core->type.link; + implementation = &link_events; + client_version = PW_VERSION_LINK; + } else + return; + + proxy = pw_proxy_new(remote, SPA_ID_INVALID, proxy_type, sizeof(struct proxy_data)); + if (proxy == NULL) + goto no_mem; + + pw_proxy_set_implementation(proxy, data, client_version, implementation, destroy_proxy); + + pw_registry_do_bind(registry_proxy, id, version, proxy->id); + + return; + + no_mem: + printf("failed to create proxy"); + return; +} + +static void registry_event_global_remove(void *object, uint32_t id) +{ + printf("removed:\n"); + printf("\tid: %u\n", id); +} + +static const struct pw_registry_events registry_events = { + registry_event_global, + registry_event_global_remove, +}; + +static void on_state_changed(struct pw_listener *listener, struct pw_remote *remote) +{ + struct data *data = SPA_CONTAINER_OF(listener, struct data, on_state_changed); + + switch (remote->state) { + case PW_REMOTE_STATE_ERROR: + printf("remote error: %s\n", remote->error); + data->running = false; + break; + + case PW_REMOTE_STATE_CONNECTED: + printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state)); + + data->registry_proxy = pw_proxy_new(data->remote, + SPA_ID_INVALID, + data->core->type.registry, + 0); + pw_proxy_set_implementation(data->registry_proxy, data, PW_VERSION_REGISTRY, + ®istry_events, NULL); + + pw_core_do_get_registry(data->remote->core_proxy, + data->registry_proxy->id); + + break; + + default: + printf("remote state: \"%s\"\n", pw_remote_state_as_string(remote->state)); + break; + } +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0 }; + + pw_init(&argc, &argv); + + data.loop = pw_loop_new(); + data.running = true; + data.core = pw_core_new(data.loop, NULL); + data.remote = pw_remote_new(data.core, NULL); + + pw_signal_add(&data.remote->info_changed, &data.on_info_changed, on_info_changed); + pw_signal_add(&data.remote->state_changed, &data.on_state_changed, on_state_changed); + + pw_remote_connect(data.remote); + + pw_loop_enter(data.loop); + while (data.running) { + pw_loop_iterate(data.loop, -1); + } + pw_loop_leave(data.loop); + + pw_remote_destroy(data.remote); + pw_loop_destroy(data.loop); + + return 0; +} |