pipewire/src/pipewire/context.c
Jonas Holmberg 44e0251afd impl-node: Make rounding of quantum configurable
Add new context config clock.power-of-two-quantum to make it possible to
not round quantum to closest lower power of two. This makes it possible
to match the quantum of a source node with the quantum of a client
node.
2021-03-16 19:09:29 +00:00

1203 lines
35 KiB
C

/* PipeWire
*
* Copyright © 2018 Wim Taymans
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <regex.h>
#include <limits.h>
#include <sys/mman.h>
#include <pipewire/log.h>
#include <spa/support/cpu.h>
#include <spa/support/dbus.h>
#include <spa/node/utils.h>
#include <spa/utils/names.h>
#include <spa/debug/format.h>
#include <spa/debug/types.h>
#include <pipewire/impl.h>
#include <pipewire/private.h>
#include <pipewire/conf.h>
#include <extensions/protocol-native.h>
#define NAME "context"
#define CLOCK_MIN_QUANTUM 4u
#define CLOCK_MAX_QUANTUM 8192u
#define DEFAULT_CLOCK_RATE 48000u
#define DEFAULT_CLOCK_QUANTUM 1024u
#define DEFAULT_CLOCK_MIN_QUANTUM 32u
#define DEFAULT_CLOCK_MAX_QUANTUM 8192u
#define DEFAULT_CLOCK_POWER_OF_TWO_QUANTUM true
#define DEFAULT_VIDEO_WIDTH 640
#define DEFAULT_VIDEO_HEIGHT 480
#define DEFAULT_VIDEO_RATE_NUM 25u
#define DEFAULT_VIDEO_RATE_DENOM 1u
#define DEFAULT_LINK_MAX_BUFFERS 64u
#define DEFAULT_MEM_WARN_MLOCK false
#define DEFAULT_MEM_ALLOW_MLOCK true
/** \cond */
struct impl {
struct pw_context this;
struct spa_handle *dbus_handle;
unsigned int recalc:1;
unsigned int recalc_pending:1;
};
struct factory_entry {
regex_t regex;
char *lib;
};
static void fill_properties(struct pw_context *context)
{
struct pw_properties *properties = context->properties;
if (!pw_properties_get(properties, PW_KEY_APP_NAME))
pw_properties_set(properties, PW_KEY_APP_NAME, pw_get_client_name());
if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_BINARY))
pw_properties_set(properties, PW_KEY_APP_PROCESS_BINARY, pw_get_prgname());
if (!pw_properties_get(properties, PW_KEY_APP_LANGUAGE)) {
pw_properties_set(properties, PW_KEY_APP_LANGUAGE, getenv("LANG"));
}
if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_ID)) {
pw_properties_setf(properties, PW_KEY_APP_PROCESS_ID, "%zd", (size_t) getpid());
}
if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_USER))
pw_properties_set(properties, PW_KEY_APP_PROCESS_USER, pw_get_user_name());
if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_HOST))
pw_properties_set(properties, PW_KEY_APP_PROCESS_HOST, pw_get_host_name());
if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_SESSION_ID)) {
pw_properties_set(properties, PW_KEY_APP_PROCESS_SESSION_ID,
getenv("XDG_SESSION_ID"));
}
if (!pw_properties_get(properties, PW_KEY_WINDOW_X11_DISPLAY)) {
pw_properties_set(properties, PW_KEY_WINDOW_X11_DISPLAY,
getenv("DISPLAY"));
}
pw_properties_set(properties, PW_KEY_CORE_VERSION, context->core->info.version);
pw_properties_set(properties, PW_KEY_CORE_NAME, context->core->info.name);
}
static uint32_t get_default_int(struct pw_properties *properties, const char *name, uint32_t def)
{
uint32_t val;
const char *str;
if ((str = pw_properties_get(properties, name)) != NULL)
val = atoi(str);
else {
val = def;
pw_properties_setf(properties, name, "%d", val);
}
return val;
}
static bool get_default_bool(struct pw_properties *properties, const char *name, bool def)
{
bool val;
const char *str;
if ((str = pw_properties_get(properties, name)) != NULL)
val = pw_properties_parse_bool(str);
else {
val = def;
pw_properties_set(properties, name, val ? "true" : "false");
}
return val;
}
static void fill_defaults(struct pw_context *this)
{
struct pw_properties *p = this->properties;
this->defaults.clock_power_of_two_quantum = get_default_bool(p, "clock.power-of-two-quantum",
DEFAULT_CLOCK_POWER_OF_TWO_QUANTUM);
this->defaults.clock_rate = get_default_int(p, "default.clock.rate", DEFAULT_CLOCK_RATE);
this->defaults.clock_quantum = get_default_int(p, "default.clock.quantum", DEFAULT_CLOCK_QUANTUM);
this->defaults.clock_min_quantum = get_default_int(p, "default.clock.min-quantum", DEFAULT_CLOCK_MIN_QUANTUM);
this->defaults.clock_max_quantum = get_default_int(p, "default.clock.max-quantum", DEFAULT_CLOCK_MAX_QUANTUM);
this->defaults.video_size.width = get_default_int(p, "default.video.width", DEFAULT_VIDEO_WIDTH);
this->defaults.video_size.height = get_default_int(p, "default.video.height", DEFAULT_VIDEO_HEIGHT);
this->defaults.video_rate.num = get_default_int(p, "default.video.rate.num", DEFAULT_VIDEO_RATE_NUM);
this->defaults.video_rate.denom = get_default_int(p, "default.video.rate.denom", DEFAULT_VIDEO_RATE_DENOM);
this->defaults.link_max_buffers = get_default_int(p, "link.max-buffers", DEFAULT_LINK_MAX_BUFFERS);
this->defaults.mem_warn_mlock = get_default_bool(p, "mem.warn-mlock", DEFAULT_MEM_WARN_MLOCK);
this->defaults.mem_allow_mlock = get_default_bool(p, "mem.allow-mlock", DEFAULT_MEM_ALLOW_MLOCK);
this->defaults.clock_max_quantum = SPA_CLAMP(this->defaults.clock_max_quantum,
CLOCK_MIN_QUANTUM, CLOCK_MAX_QUANTUM);
this->defaults.clock_min_quantum = SPA_CLAMP(this->defaults.clock_min_quantum,
CLOCK_MIN_QUANTUM, this->defaults.clock_max_quantum);
this->defaults.clock_quantum = SPA_CLAMP(this->defaults.clock_quantum,
this->defaults.clock_min_quantum, this->defaults.clock_max_quantum);
}
/** Create a new context object
*
* \param main_loop the main loop to use
* \param properties extra properties for the context, ownership it taken
* \return a newly allocated context object
*
* \memberof pw_context
*/
SPA_EXPORT
struct pw_context *pw_context_new(struct pw_loop *main_loop,
struct pw_properties *properties,
size_t user_data_size)
{
struct impl *impl;
struct pw_context *this;
const char *lib, *str, *conf_prefix, *conf_name;
void *dbus_iface = NULL;
uint32_t n_support;
struct pw_properties *pr, *conf = NULL;
struct spa_cpu *cpu;
int res = 0;
impl = calloc(1, sizeof(struct impl) + user_data_size);
if (impl == NULL) {
res = -errno;
goto error_cleanup;
}
this = &impl->this;
pw_log_debug(NAME" %p: new", this);
if (user_data_size > 0)
this->user_data = SPA_MEMBER(impl, sizeof(struct impl), void);
if (properties == NULL)
properties = pw_properties_new(NULL, NULL);
if (properties == NULL) {
res = -errno;
goto error_free;
}
conf_prefix = getenv("PIPEWIRE_CONFIG_PREFIX");
if (conf_prefix == NULL)
conf_prefix = pw_properties_get(properties, PW_KEY_CONFIG_PREFIX);
conf_name = getenv("PIPEWIRE_CONFIG_NAME");
if (conf_name == NULL)
conf_name = pw_properties_get(properties, PW_KEY_CONFIG_NAME);
if (conf_name == NULL)
conf_name = "client.conf";
conf = pw_properties_new(NULL, NULL);
if (conf == NULL) {
res = -errno;
goto error_free;
}
if (strcmp(conf_name, "null") != 0 &&
(res = pw_conf_load_conf(conf_prefix, conf_name, conf)) < 0) {
if (conf_prefix == NULL && strcmp(conf_name, "client.conf") == 0) {
pw_log_error(NAME" %p: can't load config %s: %s",
this, conf_name, spa_strerror(res));
goto error_free;
} else {
pw_log_warn(NAME" %p: can't load config %s%s%s: %s. Using client.conf fallback",
this,
conf_prefix ? conf_prefix : "",
conf_prefix ? "/" : "",
conf_name, spa_strerror(res));
}
conf_prefix = NULL;
if ((res = pw_conf_load_conf(NULL, "client.conf", conf)) < 0) {
pw_log_error(NAME" %p: can't load client.conf config: %s",
this, spa_strerror(res));
goto error_free;
}
}
this->conf = conf;
if ((str = pw_properties_get(conf, "context.properties")) != NULL) {
pw_properties_update_string(properties, str, strlen(str));
pw_log_info(NAME" %p: parsed context.properties section", this);
}
if (getenv("PIPEWIRE_DEBUG") == NULL &&
(str = pw_properties_get(properties, "log.level")) != NULL)
pw_log_set_level(atoi(str));
if ((str = pw_properties_get(properties, "mem.mlock-all")) != NULL &&
pw_properties_parse_bool(str)) {
if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0)
pw_log_warn(NAME" %p: could not mlockall; %m", impl);
else
pw_log_info(NAME" %p: mlockall succeeded", impl);
}
this->properties = properties;
fill_defaults(this);
pr = pw_properties_copy(properties);
if ((str = pw_properties_get(pr, "context.data-loop." PW_KEY_LIBRARY_NAME_SYSTEM)))
pw_properties_set(pr, PW_KEY_LIBRARY_NAME_SYSTEM, str);
this->data_loop_impl = pw_data_loop_new(&pr->dict);
pw_properties_free(pr);
if (this->data_loop_impl == NULL) {
res = -errno;
goto error_free;
}
this->pool = pw_mempool_new(NULL);
if (this->pool == NULL) {
res = -errno;
goto error_free_loop;
}
this->data_loop = pw_data_loop_get_loop(this->data_loop_impl);
this->data_system = this->data_loop->system;
this->main_loop = main_loop;
n_support = pw_get_support(this->support, SPA_N_ELEMENTS(this->support));
this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, this->main_loop->system);
this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, this->main_loop->loop);
this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_LoopUtils, this->main_loop->utils);
this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, this->data_system);
this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, this->data_loop->loop);
if ((cpu = spa_support_find(this->support, n_support, SPA_TYPE_INTERFACE_CPU)) != NULL)
pw_properties_setf(properties, PW_KEY_CPU_MAX_ALIGN, "%u", spa_cpu_get_max_align(cpu));
if ((str = pw_properties_get(properties, "support.dbus")) == NULL ||
pw_properties_parse_bool(str)) {
lib = pw_properties_get(properties, PW_KEY_LIBRARY_NAME_DBUS);
if (lib == NULL)
lib = "support/libspa-dbus";
impl->dbus_handle = pw_load_spa_handle(lib,
SPA_NAME_SUPPORT_DBUS, NULL,
n_support, this->support);
if (impl->dbus_handle == NULL ||
(res = spa_handle_get_interface(impl->dbus_handle,
SPA_TYPE_INTERFACE_DBus, &dbus_iface)) < 0) {
pw_log_warn(NAME" %p: can't load dbus interface: %s", this, spa_strerror(res));
} else {
this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DBus, dbus_iface);
}
}
this->n_support = n_support;
pw_array_init(&this->factory_lib, 32);
pw_array_init(&this->objects, 32);
pw_map_init(&this->globals, 128, 32);
spa_list_init(&this->core_impl_list);
spa_list_init(&this->protocol_list);
spa_list_init(&this->core_list);
spa_list_init(&this->registry_resource_list);
spa_list_init(&this->global_list);
spa_list_init(&this->module_list);
spa_list_init(&this->device_list);
spa_list_init(&this->client_list);
spa_list_init(&this->node_list);
spa_list_init(&this->factory_list);
spa_list_init(&this->link_list);
spa_list_init(&this->control_list[0]);
spa_list_init(&this->control_list[1]);
spa_list_init(&this->export_list);
spa_list_init(&this->driver_list);
spa_hook_list_init(&this->listener_list);
spa_hook_list_init(&this->driver_listener_list);
this->core = pw_context_create_core(this, pw_properties_copy(properties), 0);
if (this->core == NULL) {
res = -errno;
goto error_free_loop;
}
pw_impl_core_register(this->core, NULL);
fill_properties(this);
if ((res = pw_data_loop_start(this->data_loop_impl)) < 0)
goto error_free_loop;
this->sc_pagesize = sysconf(_SC_PAGESIZE);
if ((res = pw_context_parse_conf_section(this, conf, "context.spa-libs")) >= 0)
pw_log_info(NAME" %p: parsed context.spa-libs section", this);
if ((res = pw_context_parse_conf_section(this, conf, "context.modules")) >= 0)
pw_log_info(NAME" %p: parsed context.modules section", this);
if ((res = pw_context_parse_conf_section(this, conf, "context.objects")) >= 0)
pw_log_info(NAME" %p: parsed context.objects section", this);
if ((res = pw_context_parse_conf_section(this, conf, "context.exec")) >= 0)
pw_log_info(NAME" %p: parsed context.exec section", this);
pw_log_debug(NAME" %p: created", this);
return this;
error_free_loop:
pw_data_loop_destroy(this->data_loop_impl);
error_free:
free(this);
error_cleanup:
if (conf)
pw_properties_free(conf);
if (properties)
pw_properties_free(properties);
errno = -res;
return NULL;
}
/** Destroy a context object
*
* \param context a context to destroy
*
* \memberof pw_context
*/
SPA_EXPORT
void pw_context_destroy(struct pw_context *context)
{
struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this);
struct pw_global *global;
struct pw_impl_client *client;
struct pw_impl_module *module;
struct pw_impl_device *device;
struct pw_core *core;
struct pw_resource *resource;
struct pw_impl_node *node;
struct factory_entry *entry;
struct pw_impl_core *core_impl;
pw_log_debug(NAME" %p: destroy", context);
pw_context_emit_destroy(context);
spa_list_consume(core, &context->core_list, link)
pw_core_disconnect(core);
spa_list_consume(client, &context->client_list, link)
pw_impl_client_destroy(client);
spa_list_consume(node, &context->node_list, link)
pw_impl_node_destroy(node);
spa_list_consume(device, &context->device_list, link)
pw_impl_device_destroy(device);
spa_list_consume(resource, &context->registry_resource_list, link)
pw_resource_destroy(resource);
spa_list_consume(module, &context->module_list, link)
pw_impl_module_destroy(module);
spa_list_consume(global, &context->global_list, link)
pw_global_destroy(global);
spa_list_consume(core_impl, &context->core_impl_list, link)
pw_impl_core_destroy(core_impl);
pw_log_debug(NAME" %p: free", context);
pw_context_emit_free(context);
pw_mempool_destroy(context->pool);
pw_data_loop_destroy(context->data_loop_impl);
pw_properties_free(context->properties);
pw_properties_free(context->conf);
if (impl->dbus_handle)
pw_unload_spa_handle(impl->dbus_handle);
pw_array_for_each(entry, &context->factory_lib) {
regfree(&entry->regex);
free(entry->lib);
}
pw_array_clear(&context->factory_lib);
pw_array_clear(&context->objects);
pw_map_clear(&context->globals);
spa_hook_list_clean(&context->listener_list);
spa_hook_list_clean(&context->driver_listener_list);
free(context);
}
SPA_EXPORT
void *pw_context_get_user_data(struct pw_context *context)
{
return context->user_data;
}
SPA_EXPORT
void pw_context_add_listener(struct pw_context *context,
struct spa_hook *listener,
const struct pw_context_events *events,
void *data)
{
spa_hook_list_append(&context->listener_list, listener, events, data);
}
SPA_EXPORT
const struct spa_support *pw_context_get_support(struct pw_context *context, uint32_t *n_support)
{
*n_support = context->n_support;
return context->support;
}
SPA_EXPORT
struct pw_loop *pw_context_get_main_loop(struct pw_context *context)
{
return context->main_loop;
}
SPA_EXPORT
const struct pw_properties *pw_context_get_properties(struct pw_context *context)
{
return context->properties;
}
SPA_EXPORT
const char *pw_context_get_conf_section(struct pw_context *context, const char *section)
{
return pw_properties_get(context->conf, section);
}
/** Update context properties
*
* \param context a context
* \param dict properties to update
*
* Update the context object with the given properties
*
* \memberof pw_context
*/
SPA_EXPORT
int pw_context_update_properties(struct pw_context *context, const struct spa_dict *dict)
{
int changed;
changed = pw_properties_update(context->properties, dict);
pw_log_debug(NAME" %p: updated %d properties", context, changed);
return changed;
}
static bool global_can_read(struct pw_context *context, struct pw_global *global)
{
if (context->current_client &&
!PW_PERM_IS_R(pw_global_get_permissions(global, context->current_client)))
return false;
return true;
}
SPA_EXPORT
int pw_context_for_each_global(struct pw_context *context,
int (*callback) (void *data, struct pw_global *global),
void *data)
{
struct pw_global *g, *t;
int res;
spa_list_for_each_safe(g, t, &context->global_list, link) {
if (!global_can_read(context, g))
continue;
if ((res = callback(data, g)) != 0)
return res;
}
return 0;
}
SPA_EXPORT
struct pw_global *pw_context_find_global(struct pw_context *context, uint32_t id)
{
struct pw_global *global;
global = pw_map_lookup(&context->globals, id);
if (global == NULL || global->destroyed) {
errno = ENOENT;
return NULL;
}
if (!global_can_read(context, global)) {
errno = EACCES;
return NULL;
}
return global;
}
/** Find a port to link with
*
* \param context a context
* \param other_port a port to find a link with
* \param id the id of a port or PW_ID_ANY
* \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_context
*/
struct pw_impl_port *pw_context_find_port(struct pw_context *context,
struct pw_impl_port *other_port,
uint32_t id,
struct pw_properties *props,
uint32_t n_format_filters,
struct spa_pod **format_filters,
char **error)
{
struct pw_impl_port *best = NULL;
bool have_id;
struct pw_impl_node *n;
have_id = id != PW_ID_ANY;
pw_log_debug(NAME" %p: id:%u", context, id);
spa_list_for_each(n, &context->node_list, link) {
if (n->global == NULL)
continue;
if (other_port->node == n)
continue;
if (!global_can_read(context, n->global))
continue;
pw_log_debug(NAME" %p: node id:%d", context, n->global->id);
if (have_id) {
if (n->global->id == id) {
pw_log_debug(NAME" %p: id:%u matches node %p", context, id, n);
best =
pw_impl_node_find_port(n,
pw_direction_reverse(other_port->direction),
PW_ID_ANY);
if (best)
break;
}
} else {
struct pw_impl_port *p, *pin, *pout;
uint8_t buf[4096];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
struct spa_pod *dummy;
p = pw_impl_node_find_port(n,
pw_direction_reverse(other_port->direction),
PW_ID_ANY);
if (p == NULL)
continue;
if (p->direction == PW_DIRECTION_OUTPUT) {
pin = other_port;
pout = p;
} else {
pin = p;
pout = other_port;
}
if (pw_context_find_format(context,
pout,
pin,
props,
n_format_filters,
format_filters,
&dummy,
&b,
error) < 0) {
free(*error);
continue;
}
best = p;
break;
}
}
if (best == NULL) {
*error = spa_aprintf("No matching Node found");
}
return best;
}
SPA_PRINTF_FUNC(7, 8) int pw_context_debug_port_params(struct pw_context *this,
struct spa_node *node, enum spa_direction direction,
uint32_t port_id, uint32_t id, int err, const char *debug, ...)
{
struct spa_pod_builder b = { 0 };
uint8_t buffer[4096];
uint32_t state;
struct spa_pod *param;
int res;
va_list args;
va_start(args, debug);
vsnprintf((char*)buffer, sizeof(buffer), debug, args);
va_end(args);
pw_log_error("params %s: %d:%d %s (%s)",
spa_debug_type_find_name(spa_type_param, id),
direction, port_id, spa_strerror(err), buffer);
if (err == -EBUSY)
return 0;
state = 0;
while (true) {
spa_pod_builder_init(&b, buffer, sizeof(buffer));
res = spa_node_port_enum_params_sync(node,
direction, port_id,
id, &state,
NULL, &param, &b);
if (res != 1) {
if (res < 0)
pw_log_error(" error: %s", spa_strerror(res));
break;
}
pw_log_pod(SPA_LOG_LEVEL_ERROR, param);
}
return 0;
}
/** Find a common format between two ports
*
* \param context a context 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_context
*/
int pw_context_find_format(struct pw_context *context,
struct pw_impl_port *output,
struct pw_impl_port *input,
struct pw_properties *props,
uint32_t n_format_filters,
struct spa_pod **format_filters,
struct spa_pod **format,
struct spa_pod_builder *builder,
char **error)
{
uint32_t out_state, in_state;
int res;
uint32_t iidx = 0, oidx = 0;
struct spa_pod_builder fb = { 0 };
uint8_t fbuf[4096];
struct spa_pod *filter;
out_state = output->state;
in_state = input->state;
pw_log_debug(NAME" %p: finding best format %d %d", context, 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_IMPL_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE)
out_state = PW_IMPL_PORT_STATE_CONFIGURE;
if (in_state > PW_IMPL_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE)
in_state = PW_IMPL_PORT_STATE_CONFIGURE;
pw_log_debug(NAME" %p: states %d %d", context, out_state, in_state);
if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state > PW_IMPL_PORT_STATE_CONFIGURE) {
/* only input needs format */
spa_pod_builder_init(&fb, fbuf, sizeof(fbuf));
if ((res = spa_node_port_enum_params_sync(output->node->node,
output->direction, output->port_id,
SPA_PARAM_Format, &oidx,
NULL, &filter, &fb)) != 1) {
if (res < 0)
*error = spa_aprintf("error get output format: %s", spa_strerror(res));
else
*error = spa_aprintf("no output formats");
goto error;
}
pw_log_debug(NAME" %p: Got output format:", context);
pw_log_format(SPA_LOG_LEVEL_DEBUG, filter);
if ((res = spa_node_port_enum_params_sync(input->node->node,
input->direction, input->port_id,
SPA_PARAM_EnumFormat, &iidx,
filter, format, builder)) <= 0) {
if (res == -ENOENT || res == 0) {
pw_log_debug(NAME" %p: no input format filter, using output format: %s",
context, spa_strerror(res));
*format = filter;
} else {
*error = spa_aprintf("error input enum formats: %s", spa_strerror(res));
goto error;
}
}
} else if (out_state >= PW_IMPL_PORT_STATE_CONFIGURE && in_state > PW_IMPL_PORT_STATE_CONFIGURE) {
/* only output needs format */
spa_pod_builder_init(&fb, fbuf, sizeof(fbuf));
if ((res = spa_node_port_enum_params_sync(input->node->node,
input->direction, input->port_id,
SPA_PARAM_Format, &iidx,
NULL, &filter, &fb)) != 1) {
if (res < 0)
*error = spa_aprintf("error get input format: %s", spa_strerror(res));
else
*error = spa_aprintf("no input format");
goto error;
}
pw_log_debug(NAME" %p: Got input format:", context);
pw_log_format(SPA_LOG_LEVEL_DEBUG, filter);
if ((res = spa_node_port_enum_params_sync(output->node->node,
output->direction, output->port_id,
SPA_PARAM_EnumFormat, &oidx,
filter, format, builder)) <= 0) {
if (res == -ENOENT || res == 0) {
pw_log_debug(NAME" %p: no output format filter, using input format: %s",
context, spa_strerror(res));
*format = filter;
} else {
*error = spa_aprintf("error output enum formats: %s", spa_strerror(res));
goto error;
}
}
} else if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state == PW_IMPL_PORT_STATE_CONFIGURE) {
again:
/* both ports need a format */
pw_log_debug(NAME" %p: do enum input %d", context, iidx);
spa_pod_builder_init(&fb, fbuf, sizeof(fbuf));
if ((res = spa_node_port_enum_params_sync(input->node->node,
input->direction, input->port_id,
SPA_PARAM_EnumFormat, &iidx,
NULL, &filter, &fb)) != 1) {
if (res == -ENOENT) {
pw_log_debug(NAME" %p: no input filter", context);
filter = NULL;
} else {
if (res < 0)
*error = spa_aprintf("error input enum formats: %s", spa_strerror(res));
else
*error = spa_aprintf("no more input formats");
goto error;
}
}
pw_log_debug(NAME" %p: enum output %d with filter: %p", context, oidx, filter);
pw_log_format(SPA_LOG_LEVEL_DEBUG, filter);
if ((res = spa_node_port_enum_params_sync(output->node->node,
output->direction, output->port_id,
SPA_PARAM_EnumFormat, &oidx,
filter, format, builder)) != 1) {
if (res == 0 && filter != NULL) {
oidx = 0;
goto again;
}
*error = spa_aprintf("error output enum formats: %s", spa_strerror(res));
goto error;
}
pw_log_debug(NAME" %p: Got filtered:", context);
pw_log_format(SPA_LOG_LEVEL_DEBUG, *format);
} else {
res = -EBADF;
*error = spa_aprintf("error bad node state");
goto error;
}
return res;
error:
if (res == 0)
res = -EINVAL;
return res;
}
static int ensure_state(struct pw_impl_node *node, bool running)
{
enum pw_node_state state = node->info.state;
if (node->active && !SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE) && running)
state = PW_NODE_STATE_RUNNING;
else if (state > PW_NODE_STATE_IDLE)
state = PW_NODE_STATE_IDLE;
return pw_impl_node_set_state(node, state);
}
static int collect_nodes(struct pw_context *context, struct pw_impl_node *driver)
{
struct spa_list queue;
struct pw_impl_node *n, *t;
struct pw_impl_port *p;
struct pw_impl_link *l;
spa_list_consume(t, &driver->follower_list, follower_link) {
spa_list_remove(&t->follower_link);
spa_list_init(&t->follower_link);
}
pw_log_debug("driver %p: '%s'", driver, driver->name);
/* start with driver in the queue */
spa_list_init(&queue);
spa_list_append(&queue, &driver->sort_link);
driver->visited = true;
/* now follow all the links from the nodes in the queue
* and add the peers to the queue. */
spa_list_consume(n, &queue, sort_link) {
spa_list_remove(&n->sort_link);
pw_impl_node_set_driver(n, driver);
n->passive = true;
spa_list_for_each(p, &n->input_ports, link) {
spa_list_for_each(l, &p->links, input_link) {
t = l->output->node;
if (l->passive)
pw_impl_link_prepare(l);
else if (t->active)
driver->passive = n->passive = false;
if (t->visited || !t->active)
continue;
if (l->prepared) {
t->visited = true;
spa_list_append(&queue, &t->sort_link);
}
}
}
spa_list_for_each(p, &n->output_ports, link) {
spa_list_for_each(l, &p->links, output_link) {
t = l->input->node;
if (l->passive)
pw_impl_link_prepare(l);
else if (t->active)
driver->passive = n->passive = false;
if (t->visited || !t->active)
continue;
if (l->prepared) {
t->visited = true;
spa_list_append(&queue, &t->sort_link);
}
}
}
/* now go through all the followers of this driver and add the
* nodes that have the same group and that are not yet visited */
if (n->group_id == SPA_ID_INVALID)
continue;
spa_list_for_each(t, &context->node_list, link) {
if (t->exported || t == n || !t->active || t->visited)
continue;
if (t->group_id != n->group_id)
continue;
t->visited = true;
spa_list_append(&queue, &t->sort_link);
}
}
return 0;
}
int pw_context_recalc_graph(struct pw_context *context, const char *reason)
{
struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this);
struct pw_impl_node *n, *s, *target, *fallback;
pw_log_info(NAME" %p: busy:%d reason:%s", context, impl->recalc, reason);
if (impl->recalc) {
impl->recalc_pending = true;
return -EBUSY;
}
again:
impl->recalc = true;
/* start from all drivers and group all nodes that are linked
* to it. Some nodes are not (yet) linked to anything and they
* will end up 'unassigned' to a driver. Other nodes are drivers
* and if they have active followers, we can use them to schedule
* the unassigned nodes. */
target = fallback = NULL;
spa_list_for_each(n, &context->driver_list, driver_link) {
if (n->exported)
continue;
if (!n->visited)
collect_nodes(context, n);
/* from now on we are only interested in active driving nodes.
* We're going to see if there are active followers. */
if (!n->driving || !n->active)
continue;
/* first active driving node is fallback */
if (fallback == NULL)
fallback = n;
if (n->passive)
continue;
spa_list_for_each(s, &n->follower_list, follower_link) {
pw_log_debug(NAME" %p: driver %p: follower %p %s: active:%d",
context, n, s, s->name, s->active);
if (s != n && s->active) {
/* if the driving node has active followers, it
* is a target for our unassigned nodes */
if (target == NULL)
target = n;
break;
}
}
}
/* no active node, use fallback driving node */
if (target == NULL)
target = fallback;
/* now go through all available nodes. The ones we didn't visit
* in collect_nodes() are not linked to any driver. We assign them
* to either an active driver of the first driver */
spa_list_for_each(n, &context->node_list, link) {
if (n->exported)
continue;
if (!n->visited) {
struct pw_impl_node *t;
pw_log_debug(NAME" %p: unassigned node %p: '%s' active:%d want_driver:%d target:%p",
context, n, n->name, n->active, n->want_driver, target);
t = (n->active && n->want_driver) ? target : NULL;
pw_impl_node_set_driver(n, t);
if (t == NULL)
ensure_state(n, false);
else
t->passive = false;
}
n->visited = false;
}
/* assign final quantum and set state for followers and drivers */
spa_list_for_each(n, &context->driver_list, driver_link) {
bool running = false;
uint32_t max_quantum = context->defaults.clock_max_quantum;
uint32_t quantum = 0;
if (!n->driving || n->exported)
continue;
/* collect quantum and count active nodes */
spa_list_for_each(s, &n->follower_list, follower_link) {
if (s->quantum_size > 0) {
if (quantum == 0 || s->quantum_size < quantum)
quantum = s->quantum_size;
}
if (s->max_quantum_size > 0) {
if (s->max_quantum_size < max_quantum)
max_quantum = s->max_quantum_size;
}
if (s->active)
running = !n->passive;
}
if (quantum == 0)
quantum = context->defaults.clock_quantum;
quantum = SPA_CLAMP(quantum,
context->defaults.clock_min_quantum,
max_quantum);
if (n->rt.position && quantum != n->rt.position->clock.duration) {
pw_log_info("(%s-%u) new quantum:%"PRIu64"->%u",
n->name, n->info.id,
n->rt.position->clock.duration,
quantum);
n->rt.position->clock.duration = quantum;
}
pw_log_debug(NAME" %p: driving %p running:%d passive:%d quantum:%u '%s'",
context, n, running, n->passive, quantum, n->name);
spa_list_for_each(s, &n->follower_list, follower_link) {
if (s == n)
continue;
pw_log_debug(NAME" %p: follower %p: active:%d '%s'",
context, s, s->active, s->name);
ensure_state(s, running);
}
ensure_state(n, running);
}
impl->recalc = false;
if (impl->recalc_pending) {
impl->recalc_pending = false;
goto again;
}
return 0;
}
SPA_EXPORT
int pw_context_add_spa_lib(struct pw_context *context,
const char *factory_regexp, const char *lib)
{
struct factory_entry *entry;
int err;
entry = pw_array_add(&context->factory_lib, sizeof(*entry));
if (entry == NULL)
return -errno;
if ((err = regcomp(&entry->regex, factory_regexp, REG_EXTENDED | REG_NOSUB)) != 0) {
char errbuf[1024];
regerror(err, &entry->regex, errbuf, sizeof(errbuf));
pw_log_error(NAME" %p: can compile regex: %s", context, errbuf);
pw_array_remove(&context->factory_lib, entry);
return -EINVAL;
}
entry->lib = strdup(lib);
pw_log_debug(NAME" %p: map factory regex '%s' to '%s", context,
factory_regexp, lib);
return 0;
}
SPA_EXPORT
const char *pw_context_find_spa_lib(struct pw_context *context, const char *factory_name)
{
struct factory_entry *entry;
pw_array_for_each(entry, &context->factory_lib) {
if (regexec(&entry->regex, factory_name, 0, NULL, 0) == 0)
return entry->lib;
}
return NULL;
}
SPA_EXPORT
struct spa_handle *pw_context_load_spa_handle(struct pw_context *context,
const char *factory_name,
const struct spa_dict *info)
{
const char *lib;
const struct spa_support *support;
uint32_t n_support;
struct spa_handle *handle;
pw_log_debug(NAME" %p: load factory %s", context, factory_name);
lib = pw_context_find_spa_lib(context, factory_name);
if (lib == NULL && info != NULL)
lib = spa_dict_lookup(info, SPA_KEY_LIBRARY_NAME);
if (lib == NULL) {
pw_log_warn(NAME" %p: no library for %s: %m",
context, factory_name);
errno = ENOENT;
return NULL;
}
support = pw_context_get_support(context, &n_support);
handle = pw_load_spa_handle(lib, factory_name,
info, n_support, support);
return handle;
}
SPA_EXPORT
int pw_context_register_export_type(struct pw_context *context, struct pw_export_type *type)
{
if (pw_context_find_export_type(context, type->type)) {
pw_log_warn("context %p: duplicate export type %s", context, type->type);
return -EEXIST;
}
pw_log_debug("context %p: Add export type %s to context", context, type->type);
spa_list_append(&context->export_list, &type->link);
return 0;
}
SPA_EXPORT
const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type)
{
const struct pw_export_type *t;
spa_list_for_each(t, &context->export_list, link) {
if (strcmp(t->type, type) == 0)
return t;
}
return NULL;
}
struct object_entry {
const char *type;
void *value;
};
static struct object_entry *find_object(struct pw_context *context, const char *type)
{
struct object_entry *entry;
pw_array_for_each(entry, &context->objects) {
if (strcmp(entry->type, type) == 0)
return entry;
}
return NULL;
}
SPA_EXPORT
int pw_context_set_object(struct pw_context *context, const char *type, void *value)
{
struct object_entry *entry;
entry = find_object(context, type);
if (value == NULL) {
if (entry)
pw_array_remove(&context->objects, entry);
} else {
if (entry == NULL) {
entry = pw_array_add(&context->objects, sizeof(*entry));
if (entry == NULL)
return -errno;
entry->type = type;
}
entry->value = value;
}
return 0;
}
SPA_EXPORT
void *pw_context_get_object(struct pw_context *context, const char *type)
{
struct object_entry *entry;
if ((entry = find_object(context, type)) != NULL)
return entry->value;
return NULL;
}