mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-02 09:01:50 -05:00
Make sure we always first update the registry. Call callbacks with the right return value and set the context error on errors correctly. Remove some old code.
1866 lines
47 KiB
C
1866 lines
47 KiB
C
/* PipeWire
|
|
* Copyright (C) 2018 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/utils/result.h>
|
|
#include <spa/param/props.h>
|
|
|
|
#include <pipewire/pipewire.h>
|
|
#include <extensions/metadata.h>
|
|
|
|
#include <pulse/context.h>
|
|
#include <pulse/timeval.h>
|
|
#include <pulse/error.h>
|
|
#include <pulse/xmalloc.h>
|
|
|
|
#include "internal.h"
|
|
|
|
int pa_context_set_error(PA_CONST pa_context *c, int error) {
|
|
pa_assert(error >= 0);
|
|
pa_assert(error < PA_ERR_MAX);
|
|
if (c && c->error != error) {
|
|
pw_log_debug("context %p: error %d %s", c, error, pa_strerror(error));
|
|
((pa_context*)c)->error = error;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static void global_free(pa_context *c, struct global *g)
|
|
{
|
|
pw_log_debug("context %p: %d", c, g->id);
|
|
|
|
spa_list_remove(&g->link);
|
|
|
|
if (g->ginfo && g->ginfo->destroy)
|
|
g->ginfo->destroy(g);
|
|
if (g->proxy) {
|
|
pw_proxy_destroy(g->proxy);
|
|
}
|
|
if (g->props)
|
|
pw_properties_free(g->props);
|
|
free(g->type);
|
|
free(g);
|
|
}
|
|
|
|
static void context_unlink(pa_context *c)
|
|
{
|
|
pa_stream *s, *t;
|
|
struct global *g;
|
|
pa_operation *o;
|
|
|
|
pw_log_debug("context %p: unlink %d", c, c->state);
|
|
|
|
c->disconnect = true;
|
|
c->state_callback = NULL;
|
|
c->state_userdata = NULL;
|
|
|
|
spa_list_for_each_safe(s, t, &c->streams, link) {
|
|
pa_stream_set_state(s, c->state == PA_CONTEXT_FAILED ?
|
|
PA_STREAM_FAILED : PA_STREAM_TERMINATED);
|
|
}
|
|
if (c->registry) {
|
|
pw_proxy_destroy((struct pw_proxy*)c->registry);
|
|
c->registry = NULL;
|
|
}
|
|
if (c->core) {
|
|
pw_core_disconnect(c->core);
|
|
c->core = NULL;
|
|
}
|
|
spa_list_consume(g, &c->globals, link)
|
|
global_free(c, g);
|
|
|
|
spa_list_consume(o, &c->operations, link)
|
|
pa_operation_cancel(o);
|
|
}
|
|
|
|
void pa_context_set_state(pa_context *c, pa_context_state_t st) {
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
if (c->state == st)
|
|
return;
|
|
|
|
pw_log_debug("context %p: state %d", c, st);
|
|
|
|
pa_context_ref(c);
|
|
|
|
c->state = st;
|
|
|
|
if (c->state_callback)
|
|
c->state_callback(c, c->state_userdata);
|
|
|
|
if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED)
|
|
context_unlink(c);
|
|
|
|
pa_context_unref(c);
|
|
}
|
|
|
|
void pa_context_fail(PA_CONST pa_context *c, int error) {
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
pw_log_debug("context %p: error %d", c, error);
|
|
|
|
pa_context_set_error(c, error);
|
|
pa_context_set_state((pa_context*)c, PA_CONTEXT_FAILED);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name)
|
|
{
|
|
return pa_context_new_with_proplist(mainloop, name, NULL);
|
|
}
|
|
|
|
struct global *pa_context_find_global(pa_context *c, uint32_t id)
|
|
{
|
|
struct global *g;
|
|
spa_list_for_each(g, &c->globals, link) {
|
|
if (g->id == id)
|
|
return g;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const char *pa_context_find_global_name(pa_context *c, uint32_t id)
|
|
{
|
|
struct global *g;
|
|
const char *name = NULL;
|
|
|
|
g = pa_context_find_global(c, id & PA_IDX_MASK_DSP);
|
|
if (g == NULL)
|
|
return "unknown object";
|
|
|
|
if (g->mask & (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE)) {
|
|
name = pw_properties_get(g->props, PW_KEY_NODE_NAME);
|
|
}
|
|
if (name == NULL)
|
|
name = "unknown";
|
|
return name;
|
|
}
|
|
|
|
struct global *pa_context_find_global_by_name(pa_context *c, uint32_t mask, const char *name)
|
|
{
|
|
struct global *g;
|
|
const char *str;
|
|
uint32_t id = atoi(name);
|
|
|
|
spa_list_for_each(g, &c->globals, link) {
|
|
if ((g->mask & mask) == 0)
|
|
continue;
|
|
if (g->props != NULL &&
|
|
(str = pw_properties_get(g->props, PW_KEY_NODE_NAME)) != NULL &&
|
|
strcmp(str, name) == 0)
|
|
return g;
|
|
if (g->id == id || (g->id == (id & PA_IDX_MASK_DSP)))
|
|
return g;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct global *pa_context_find_linked(pa_context *c, uint32_t idx)
|
|
{
|
|
struct global *g, *f;
|
|
|
|
spa_list_for_each(g, &c->globals, link) {
|
|
uint32_t src_node_id, dst_node_id;
|
|
|
|
if (strcmp(g->type, PW_TYPE_INTERFACE_Link) != 0)
|
|
continue;
|
|
|
|
src_node_id = g->link_info.src->port_info.node_id;
|
|
dst_node_id = g->link_info.dst->port_info.node_id;
|
|
|
|
pw_log_debug("context %p: %p %d %d %d", c, g, idx,
|
|
src_node_id, dst_node_id);
|
|
|
|
if (src_node_id == idx)
|
|
f = pa_context_find_global(c, dst_node_id);
|
|
else if (dst_node_id == idx)
|
|
f = pa_context_find_global(c, src_node_id);
|
|
else
|
|
continue;
|
|
|
|
if (f == NULL ||
|
|
!(f->mask & (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE)))
|
|
continue;
|
|
return f;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *str_etype(pa_subscription_event_type_t event)
|
|
{
|
|
switch (event & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
|
|
case PA_SUBSCRIPTION_EVENT_NEW:
|
|
return "new";
|
|
case PA_SUBSCRIPTION_EVENT_CHANGE:
|
|
return "change";
|
|
case PA_SUBSCRIPTION_EVENT_REMOVE:
|
|
return "remove";
|
|
}
|
|
return "invalid";
|
|
}
|
|
|
|
static const char *str_efac(pa_subscription_event_type_t event)
|
|
{
|
|
switch (event & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
|
|
case PA_SUBSCRIPTION_EVENT_SINK:
|
|
return "sink";
|
|
case PA_SUBSCRIPTION_EVENT_SOURCE:
|
|
return "source";
|
|
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
|
|
return "sink-input";
|
|
case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
|
|
return "source-output";
|
|
case PA_SUBSCRIPTION_EVENT_MODULE:
|
|
return "module";
|
|
case PA_SUBSCRIPTION_EVENT_CLIENT:
|
|
return "client";
|
|
case PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE:
|
|
return "sample-cache";
|
|
case PA_SUBSCRIPTION_EVENT_SERVER:
|
|
return "server";
|
|
case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
|
|
return "autoload";
|
|
case PA_SUBSCRIPTION_EVENT_CARD:
|
|
return "card";
|
|
}
|
|
return "invalid";
|
|
}
|
|
|
|
static void global_sync(struct global *g)
|
|
{
|
|
pa_operation *o;
|
|
pa_context *c = g->context;
|
|
|
|
g->pending_seq = pw_proxy_sync(g->proxy, 0);
|
|
spa_list_for_each(o, &c->operations, link)
|
|
o->seq = g->pending_seq;
|
|
}
|
|
|
|
static void emit_event(pa_context *c, struct global *g, pa_subscription_event_type_t event)
|
|
{
|
|
if (c->subscribe_callback && (c->subscribe_mask & g->mask)) {
|
|
pw_log_debug("context %p: obj %d: emit %s:%s", c, g->id,
|
|
str_etype(event), str_efac(g->event));
|
|
c->subscribe_callback(c,
|
|
event | g->event,
|
|
g->id,
|
|
c->subscribe_userdata);
|
|
|
|
if (g->mask == (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE)) {
|
|
pw_log_debug("context %p: obj %d: emit %s:source", c, g->node_info.monitor,
|
|
str_etype(event));
|
|
c->subscribe_callback(c,
|
|
event | PA_SUBSCRIPTION_EVENT_SOURCE,
|
|
g->node_info.monitor,
|
|
c->subscribe_userdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void remove_params(struct spa_list *params, uint32_t id)
|
|
{
|
|
struct param *p, *t;
|
|
spa_list_for_each_safe(p, t, params, link) {
|
|
if (id == SPA_ID_INVALID || p->id == id) {
|
|
spa_list_remove(&p->link);
|
|
free(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void update_device_props(struct global *g)
|
|
{
|
|
pa_card_info *i = &g->card_info.info;
|
|
const char *s;
|
|
|
|
if ((s = pa_proplist_gets(i->proplist, PW_KEY_DEVICE_ICON_NAME)))
|
|
pa_proplist_sets(i->proplist, PA_PROP_DEVICE_ICON_NAME, s);
|
|
}
|
|
|
|
|
|
static void device_event_info(void *object, const struct pw_device_info *info)
|
|
{
|
|
struct global *g = object;
|
|
pa_card_info *i = &g->card_info.info;
|
|
const char *str;
|
|
uint32_t n;
|
|
|
|
pw_log_debug("global %p: id:%d change-mask:%"PRIu64, g, g->id, info->change_mask);
|
|
info = g->info = pw_device_info_update(g->info, info);
|
|
|
|
i->index = g->id;
|
|
i->name = info->props ?
|
|
spa_dict_lookup(info->props, PW_KEY_DEVICE_NAME) : "unknown";
|
|
str = info->props ? spa_dict_lookup(info->props, PW_KEY_MODULE_ID) : NULL;
|
|
i->owner_module = str ? (unsigned)atoi(str) : SPA_ID_INVALID;
|
|
if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) {
|
|
i->driver = info->props ?
|
|
spa_dict_lookup(info->props, PW_KEY_DEVICE_API) : NULL;
|
|
|
|
if (i->proplist)
|
|
pa_proplist_update_dict(i->proplist, info->props);
|
|
else {
|
|
i->proplist = pa_proplist_new_dict(info->props);
|
|
}
|
|
update_device_props(g);
|
|
}
|
|
if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) {
|
|
for (n = 0; n < info->n_params; n++) {
|
|
if (!(info->params[n].flags & SPA_PARAM_INFO_READ))
|
|
continue;
|
|
|
|
switch (info->params[n].id) {
|
|
case SPA_PARAM_EnumProfile:
|
|
if (g->card_info.pending_profiles)
|
|
continue;
|
|
remove_params(&g->card_info.profiles, SPA_PARAM_EnumProfile);
|
|
g->card_info.n_profiles = 0;
|
|
g->card_info.pending_profiles = true;
|
|
pw_device_enum_params((struct pw_device*)g->proxy,
|
|
0, SPA_PARAM_EnumProfile, 0, -1, NULL);
|
|
break;
|
|
case SPA_PARAM_Profile:
|
|
pw_device_enum_params((struct pw_device*)g->proxy,
|
|
0, SPA_PARAM_Profile, 0, -1, NULL);
|
|
break;
|
|
case SPA_PARAM_EnumRoute:
|
|
if (g->card_info.pending_ports)
|
|
continue;
|
|
remove_params(&g->card_info.ports, SPA_PARAM_EnumRoute);
|
|
g->card_info.n_ports = 0;
|
|
g->card_info.pending_ports = true;
|
|
pw_device_enum_params((struct pw_device*)g->proxy,
|
|
0, SPA_PARAM_EnumRoute, 0, -1, NULL);
|
|
break;
|
|
case SPA_PARAM_Route:
|
|
pw_device_enum_params((struct pw_device*)g->proxy,
|
|
0, SPA_PARAM_Route, 0, -1, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
global_sync(g);
|
|
}
|
|
|
|
static void parse_props(struct global *g, const struct spa_pod *param, bool device)
|
|
{
|
|
struct spa_pod_prop *prop;
|
|
struct spa_pod_object *obj = (struct spa_pod_object *) param;
|
|
|
|
SPA_POD_OBJECT_FOREACH(obj, prop) {
|
|
switch (prop->key) {
|
|
case SPA_PROP_volume:
|
|
spa_pod_get_float(&prop->value, &g->node_info.volume);
|
|
SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_DEVICE_VOLUME, device);
|
|
SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_HW_VOLUME,
|
|
prop->flags & SPA_POD_PROP_FLAG_HARDWARE);
|
|
break;
|
|
case SPA_PROP_mute:
|
|
spa_pod_get_bool(&prop->value, &g->node_info.mute);
|
|
SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_DEVICE_MUTE, device);
|
|
SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_HW_MUTE,
|
|
prop->flags & SPA_POD_PROP_FLAG_HARDWARE);
|
|
break;
|
|
case SPA_PROP_channelVolumes:
|
|
{
|
|
uint32_t n_vals;
|
|
|
|
n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
|
|
g->node_info.channel_volumes, SPA_AUDIO_MAX_CHANNELS);
|
|
|
|
if (n_vals != g->node_info.n_channel_volumes) {
|
|
pw_log_debug("channel change %d->%d, trigger remove",
|
|
g->node_info.n_channel_volumes, n_vals);
|
|
emit_event(g->context, g, PA_SUBSCRIPTION_EVENT_REMOVE);
|
|
g->node_info.n_channel_volumes = n_vals;
|
|
/* mark as init, this will emit the NEW event when the
|
|
* params are updated */
|
|
g->init = true;
|
|
}
|
|
SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_DEVICE_VOLUME, device);
|
|
SPA_FLAG_UPDATE(g->node_info.flags, NODE_FLAG_HW_VOLUME,
|
|
prop->flags & SPA_POD_PROP_FLAG_HARDWARE);
|
|
break;
|
|
}
|
|
case SPA_PROP_volumeBase:
|
|
spa_pod_get_float(&prop->value, &g->node_info.base_volume);
|
|
break;
|
|
case SPA_PROP_volumeStep:
|
|
spa_pod_get_float(&prop->value, &g->node_info.volume_step);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct global *find_node_for_route(pa_context *c, struct global *card, uint32_t device)
|
|
{
|
|
struct global *n;
|
|
spa_list_for_each(n, &c->globals, link) {
|
|
if (strcmp(n->type, PW_TYPE_INTERFACE_Node) != 0)
|
|
continue;
|
|
pw_log_debug("%d/%d %d/%d",
|
|
n->node_info.device_id, card->id,
|
|
n->node_info.profile_device_id, device);
|
|
if (n->node_info.device_id != card->id)
|
|
continue;
|
|
if (n->node_info.profile_device_id != device)
|
|
continue;
|
|
return n;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void device_event_param(void *object, int seq,
|
|
uint32_t id, uint32_t index, uint32_t next,
|
|
const struct spa_pod *param)
|
|
{
|
|
struct global *g = object;
|
|
pa_context *c = g->context;
|
|
|
|
switch (id) {
|
|
case SPA_PARAM_EnumProfile:
|
|
{
|
|
uint32_t index;
|
|
const char *name;
|
|
struct param *p;
|
|
|
|
if (spa_pod_parse_object(param,
|
|
SPA_TYPE_OBJECT_ParamProfile, NULL,
|
|
SPA_PARAM_PROFILE_index, SPA_POD_Int(&index),
|
|
SPA_PARAM_PROFILE_name, SPA_POD_String(&name)) < 0) {
|
|
pw_log_warn("device %d: can't parse profile", g->id);
|
|
return;
|
|
}
|
|
p = malloc(sizeof(struct param) + SPA_POD_SIZE(param));
|
|
if (p) {
|
|
p->id = id;
|
|
p->seq = seq;
|
|
p->param = SPA_MEMBER(p, sizeof(struct param), struct spa_pod);
|
|
memcpy(p->param, param, SPA_POD_SIZE(param));
|
|
spa_list_append(&g->card_info.profiles, &p->link);
|
|
g->card_info.n_profiles++;
|
|
}
|
|
pw_log_debug("device %d: enum profile %d: \"%s\" n_profiles:%d", g->id,
|
|
index, name, g->card_info.n_profiles);
|
|
break;
|
|
}
|
|
case SPA_PARAM_Profile:
|
|
{
|
|
uint32_t index;
|
|
if (spa_pod_parse_object(param,
|
|
SPA_TYPE_OBJECT_ParamProfile, NULL,
|
|
SPA_PARAM_PROFILE_index, SPA_POD_Int(&index)) < 0) {
|
|
pw_log_warn("device %d: can't parse profile", g->id);
|
|
return;
|
|
}
|
|
g->card_info.active_profile = index;
|
|
pw_log_debug("device %d: current profile %d", g->id, index);
|
|
break;
|
|
}
|
|
case SPA_PARAM_EnumRoute:
|
|
{
|
|
uint32_t index;
|
|
const char *name;
|
|
struct param *p;
|
|
|
|
if (spa_pod_parse_object(param,
|
|
SPA_TYPE_OBJECT_ParamRoute, NULL,
|
|
SPA_PARAM_ROUTE_index, SPA_POD_Int(&index),
|
|
SPA_PARAM_ROUTE_name, SPA_POD_String(&name)) < 0) {
|
|
pw_log_warn("device %d: can't parse route", g->id);
|
|
return;
|
|
}
|
|
p = malloc(sizeof(struct param) + SPA_POD_SIZE(param));
|
|
if (p) {
|
|
p->id = id;
|
|
p->seq = seq;
|
|
p->param = SPA_MEMBER(p, sizeof(struct param), struct spa_pod);
|
|
memcpy(p->param, param, SPA_POD_SIZE(param));
|
|
spa_list_append(&g->card_info.ports, &p->link);
|
|
g->card_info.n_ports++;
|
|
}
|
|
pw_log_debug("device %d: enum route %d: \"%s\"", g->id, index, name);
|
|
break;
|
|
}
|
|
case SPA_PARAM_Route:
|
|
{
|
|
uint32_t index, device;
|
|
enum spa_direction direction;
|
|
struct spa_pod *props = NULL;
|
|
struct global *ng;
|
|
|
|
if (spa_pod_parse_object(param,
|
|
SPA_TYPE_OBJECT_ParamRoute, NULL,
|
|
SPA_PARAM_ROUTE_index, SPA_POD_Int(&index),
|
|
SPA_PARAM_ROUTE_direction, SPA_POD_Id(&direction),
|
|
SPA_PARAM_ROUTE_device, SPA_POD_Int(&device),
|
|
SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props)) < 0) {
|
|
pw_log_warn("device %d: can't parse route", g->id);
|
|
return;
|
|
}
|
|
|
|
if (direction == SPA_DIRECTION_OUTPUT)
|
|
g->card_info.active_port_output = index;
|
|
else
|
|
g->card_info.active_port_input = index;
|
|
|
|
pw_log_debug("device %d: active %s route %d", g->id,
|
|
direction == SPA_DIRECTION_OUTPUT ? "output" : "input",
|
|
index);
|
|
|
|
ng = find_node_for_route(c, g, device);
|
|
if (props && ng) {
|
|
parse_props(ng, props, true);
|
|
emit_event(c, ng, PA_SUBSCRIPTION_EVENT_CHANGE);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void device_clear_profiles(struct global *g)
|
|
{
|
|
pa_card_info *i = &g->card_info.info;
|
|
i->n_profiles = 0;
|
|
free(i->profiles);
|
|
i->profiles = NULL;
|
|
free(g->card_info.card_profiles);
|
|
g->card_info.card_profiles = NULL;
|
|
free(i->profiles2);
|
|
i->profiles2 = NULL;
|
|
}
|
|
|
|
static void device_sync_profiles(struct global *g)
|
|
{
|
|
pa_card_info *i = &g->card_info.info;
|
|
uint32_t n_profiles, j;
|
|
struct param *p;
|
|
|
|
device_clear_profiles(g);
|
|
|
|
n_profiles = g->card_info.n_profiles;
|
|
|
|
i->profiles = calloc(n_profiles, sizeof(pa_card_profile_info));
|
|
g->card_info.card_profiles = calloc(n_profiles, sizeof(pa_card_profile_info2));
|
|
i->profiles2 = calloc(n_profiles + 1, sizeof(pa_card_profile_info2 *));
|
|
i->n_profiles = 0;
|
|
|
|
pw_log_debug("context %p: info for %d n_profiles:%d", g->context, g->id, n_profiles);
|
|
|
|
j = 0;
|
|
spa_list_for_each(p, &g->card_info.profiles, link) {
|
|
uint32_t id, priority = 0, available = 0, n_cap = 0, n_play = 0;
|
|
const char *name = NULL;
|
|
const char *description = NULL;
|
|
struct spa_pod *classes = NULL, *info = NULL;
|
|
|
|
if (spa_pod_parse_object(p->param,
|
|
SPA_TYPE_OBJECT_ParamProfile, NULL,
|
|
SPA_PARAM_PROFILE_index, SPA_POD_Int(&id),
|
|
SPA_PARAM_PROFILE_name, SPA_POD_String(&name),
|
|
SPA_PARAM_PROFILE_description, SPA_POD_OPT_String(&description),
|
|
SPA_PARAM_PROFILE_priority, SPA_POD_OPT_Int(&priority),
|
|
SPA_PARAM_PROFILE_available, SPA_POD_OPT_Id(&available),
|
|
SPA_PARAM_PROFILE_info, SPA_POD_OPT_Pod(&info),
|
|
SPA_PARAM_PROFILE_classes, SPA_POD_OPT_Pod(&classes)) < 0) {
|
|
pw_log_warn("device %d: can't parse profile", g->id);
|
|
continue;
|
|
}
|
|
if (classes != NULL) {
|
|
struct spa_pod *iter;
|
|
|
|
SPA_POD_STRUCT_FOREACH(classes, iter) {
|
|
struct spa_pod_parser prs;
|
|
struct spa_pod_frame f[1];
|
|
char *class;
|
|
uint32_t count;
|
|
|
|
spa_pod_parser_pod(&prs, iter);
|
|
if (spa_pod_parser_get_struct(&prs,
|
|
SPA_POD_String(&class),
|
|
SPA_POD_Int(&count)) < 0)
|
|
continue;
|
|
|
|
if (strcmp(class, "Audio/Sink") == 0)
|
|
n_play += count;
|
|
else if (strcmp(class, "Audio/Source") == 0)
|
|
n_cap += count;
|
|
|
|
spa_pod_parser_pop(&prs, &f[0]);
|
|
}
|
|
}
|
|
pw_log_debug("profile %d: name:%s", j, name);
|
|
|
|
i->profiles[j].name = name;
|
|
i->profiles[j].description = description ? description : name;
|
|
i->profiles[j].n_sinks = n_play;
|
|
i->profiles[j].n_sources = n_cap;
|
|
i->profiles[j].priority = priority;
|
|
|
|
i->profiles2[j] = &g->card_info.card_profiles[j];
|
|
i->profiles2[j]->name = i->profiles[j].name;
|
|
i->profiles2[j]->description = i->profiles[j].description;
|
|
i->profiles2[j]->n_sinks = i->profiles[j].n_sinks;
|
|
i->profiles2[j]->n_sources = i->profiles[j].n_sources;
|
|
i->profiles2[j]->priority = i->profiles[j].priority;
|
|
i->profiles2[j]->available = available != SPA_PARAM_AVAILABILITY_no;
|
|
|
|
if (g->card_info.active_profile == id) {
|
|
i->active_profile = &i->profiles[j];
|
|
i->active_profile2 = i->profiles2[j];
|
|
}
|
|
j++;
|
|
}
|
|
i->profiles2[j] = NULL;
|
|
i->n_profiles = j;
|
|
}
|
|
|
|
static void device_clear_ports(struct global *g)
|
|
{
|
|
pa_card_info *i = &g->card_info.info;
|
|
uint32_t n;
|
|
|
|
for (n = 0; n < i->n_ports; i++)
|
|
free(i->ports[n]->profiles2);
|
|
|
|
i->n_ports = 0;
|
|
free(i->ports);
|
|
i->ports = NULL;
|
|
free(g->card_info.card_ports);
|
|
g->card_info.card_ports = NULL;
|
|
}
|
|
|
|
static void device_sync_ports(struct global *g)
|
|
{
|
|
pa_card_info *i = &g->card_info.info;
|
|
uint32_t n_ports, j;
|
|
struct param *p;
|
|
|
|
device_clear_ports(g);
|
|
|
|
n_ports = g->card_info.n_ports;
|
|
i->ports = calloc(n_ports+1, sizeof(pa_card_port_info *));
|
|
g->card_info.card_ports = calloc(n_ports, sizeof(pa_card_port_info));
|
|
i->n_ports = 0;
|
|
|
|
pw_log_debug("context %p: info for %d", g->context, g->id);
|
|
|
|
j = 0;
|
|
|
|
spa_list_for_each(p, &g->card_info.ports, link) {
|
|
uint32_t id, priority;
|
|
enum spa_direction direction;
|
|
const char *name = NULL, *description = NULL;
|
|
enum spa_param_availability available = SPA_PARAM_AVAILABILITY_unknown;
|
|
struct spa_pod *profiles = NULL, *info = NULL;
|
|
pa_card_port_info *pi;
|
|
|
|
if (spa_pod_parse_object(p->param,
|
|
SPA_TYPE_OBJECT_ParamRoute, NULL,
|
|
SPA_PARAM_ROUTE_index, SPA_POD_Int(&id),
|
|
SPA_PARAM_ROUTE_direction, SPA_POD_Id(&direction),
|
|
SPA_PARAM_ROUTE_name, SPA_POD_String(&name),
|
|
SPA_PARAM_ROUTE_description, SPA_POD_OPT_String(&description),
|
|
SPA_PARAM_ROUTE_priority, SPA_POD_OPT_Int(&priority),
|
|
SPA_PARAM_ROUTE_available, SPA_POD_OPT_Id(&available),
|
|
SPA_PARAM_ROUTE_info, SPA_POD_OPT_Pod(&info),
|
|
SPA_PARAM_ROUTE_profiles, SPA_POD_OPT_Pod(&profiles)) < 0) {
|
|
pw_log_warn("device %d: can't parse route", g->id);
|
|
continue;
|
|
}
|
|
|
|
pi = i->ports[j] = &g->card_info.card_ports[j];
|
|
spa_zero(*pi);
|
|
pi->name = name;
|
|
pi->description = description;
|
|
pi->priority = priority;
|
|
pi->available = available;
|
|
pi->direction = direction == SPA_DIRECTION_INPUT ? PA_DIRECTION_INPUT : PA_DIRECTION_OUTPUT;
|
|
pi->proplist = pa_proplist_new();
|
|
while (info) {
|
|
struct spa_pod_parser prs;
|
|
struct spa_pod_frame f[1];
|
|
uint32_t n, n_items;
|
|
const char *key, *value;
|
|
|
|
spa_pod_parser_pod(&prs, info);
|
|
if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
|
|
spa_pod_parser_get_int(&prs, &n_items) < 0)
|
|
break;
|
|
|
|
for (n = 0; n < n_items; n++) {
|
|
if (spa_pod_parser_get(&prs,
|
|
SPA_POD_String(&key),
|
|
SPA_POD_String(&value),
|
|
NULL) < 0)
|
|
break;
|
|
pa_proplist_sets(pi->proplist, key, value);
|
|
}
|
|
spa_pod_parser_pop(&prs, &f[0]);
|
|
break;
|
|
}
|
|
pi->n_profiles = 0;
|
|
pi->profiles = NULL;
|
|
pi->profiles2 = NULL;
|
|
while (profiles) {
|
|
uint32_t *pr, n, n_pr;
|
|
|
|
pr = spa_pod_get_array(profiles, &n_pr);
|
|
if (pr == NULL)
|
|
break;
|
|
|
|
pi->n_profiles = n_pr;
|
|
pi->profiles2 = calloc(n_pr + 1, sizeof(pa_card_profile_info2 *));
|
|
for (n = 0; n < n_pr; n++)
|
|
pi->profiles2[n] = i->profiles2[pr[n]];
|
|
pi->profiles2[n_pr] = NULL;
|
|
pi->profiles = (pa_card_profile_info **)pi->profiles2;
|
|
break;
|
|
}
|
|
j++;
|
|
}
|
|
i->ports[j] = NULL;
|
|
i->n_ports = j;
|
|
if (i->n_ports == 0)
|
|
i->ports = NULL;
|
|
}
|
|
|
|
static void device_sync(struct global *g)
|
|
{
|
|
if (g->card_info.pending_profiles) {
|
|
device_sync_profiles(g);
|
|
g->card_info.pending_profiles = false;
|
|
}
|
|
if (g->card_info.pending_ports) {
|
|
device_sync_ports(g);
|
|
g->card_info.pending_ports = false;
|
|
}
|
|
}
|
|
|
|
static const struct pw_device_events device_events = {
|
|
PW_VERSION_DEVICE_EVENTS,
|
|
.info = device_event_info,
|
|
.param = device_event_param,
|
|
};
|
|
|
|
static void device_destroy(void *data)
|
|
{
|
|
struct global *global = data;
|
|
|
|
if (global->card_info.info.proplist)
|
|
pa_proplist_free(global->card_info.info.proplist);
|
|
|
|
device_clear_ports(global);
|
|
device_clear_profiles(global);
|
|
|
|
remove_params(&global->card_info.ports, SPA_ID_INVALID);
|
|
remove_params(&global->card_info.profiles, SPA_ID_INVALID);
|
|
|
|
if (global->info)
|
|
pw_device_info_free(global->info);
|
|
}
|
|
|
|
struct global_info device_info = {
|
|
.version = PW_VERSION_DEVICE,
|
|
.events = &device_events,
|
|
.destroy = device_destroy,
|
|
.sync = device_sync,
|
|
};
|
|
|
|
static void node_event_info(void *object, const struct pw_node_info *info)
|
|
{
|
|
struct global *g = object;
|
|
const char *str;
|
|
uint32_t i;
|
|
|
|
pw_log_debug("update %d %"PRIu64, g->id, info->change_mask);
|
|
info = g->info = pw_node_info_update(g->info, info);
|
|
|
|
if (info->props && (str = spa_dict_lookup(info->props, "card.profile.device")))
|
|
g->node_info.profile_device_id = atoi(str);
|
|
else
|
|
g->node_info.profile_device_id = SPA_ID_INVALID;
|
|
|
|
if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS && !g->subscribed) {
|
|
uint32_t subscribed[32], n_subscribed = 0;
|
|
|
|
for (i = 0; i < info->n_params; i++) {
|
|
switch (info->params[i].id) {
|
|
case SPA_PARAM_EnumFormat:
|
|
case SPA_PARAM_Props:
|
|
subscribed[n_subscribed++] = info->params[i].id;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (n_subscribed > 0) {
|
|
pw_node_subscribe_params((struct pw_node*)g->proxy,
|
|
subscribed, n_subscribed);
|
|
g->subscribed = true;
|
|
}
|
|
}
|
|
global_sync(g);
|
|
}
|
|
|
|
static void node_event_param(void *object, int seq,
|
|
uint32_t id, uint32_t index, uint32_t next,
|
|
const struct spa_pod *param)
|
|
{
|
|
struct global *g = object;
|
|
pw_log_debug("update param %d %d", g->id, id);
|
|
|
|
switch (id) {
|
|
case SPA_PARAM_Props:
|
|
if (!SPA_FLAG_IS_SET(g->node_info.flags, NODE_FLAG_DEVICE_VOLUME | NODE_FLAG_DEVICE_MUTE))
|
|
parse_props(g, param, false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const struct pw_node_events node_events = {
|
|
PW_VERSION_NODE_EVENTS,
|
|
.info = node_event_info,
|
|
.param = node_event_param,
|
|
};
|
|
|
|
static void node_destroy(void *data)
|
|
{
|
|
struct global *global = data;
|
|
if (global->info)
|
|
pw_node_info_free(global->info);
|
|
}
|
|
|
|
struct global_info node_info = {
|
|
.version = PW_VERSION_NODE,
|
|
.events = &node_events,
|
|
.destroy = node_destroy,
|
|
};
|
|
|
|
|
|
static void module_event_info(void *object, const struct pw_module_info *info)
|
|
{
|
|
struct global *g = object;
|
|
pa_module_info *i = &g->module_info.info;
|
|
|
|
pw_log_debug("update %d", g->id);
|
|
|
|
info = g->info = pw_module_info_update(g->info, info);
|
|
|
|
i->index = g->id;
|
|
if (info->change_mask & PW_MODULE_CHANGE_MASK_PROPS) {
|
|
if (i->proplist)
|
|
pa_proplist_update_dict(i->proplist, info->props);
|
|
else
|
|
i->proplist = pa_proplist_new_dict(info->props);
|
|
}
|
|
|
|
i->name = info->name;
|
|
i->argument = info->args;
|
|
i->n_used = -1;
|
|
i->auto_unload = false;
|
|
global_sync(g);
|
|
}
|
|
|
|
static const struct pw_module_events module_events = {
|
|
PW_VERSION_MODULE_EVENTS,
|
|
.info = module_event_info,
|
|
};
|
|
|
|
static void module_destroy(void *data)
|
|
{
|
|
struct global *global = data;
|
|
if (global->module_info.info.proplist)
|
|
pa_proplist_free(global->module_info.info.proplist);
|
|
if (global->info)
|
|
pw_module_info_free(global->info);
|
|
}
|
|
|
|
struct global_info module_info = {
|
|
.version = PW_VERSION_MODULE,
|
|
.events = &module_events,
|
|
.destroy = module_destroy,
|
|
};
|
|
|
|
static void client_event_info(void *object, const struct pw_client_info *info)
|
|
{
|
|
struct global *g = object;
|
|
const char *str;
|
|
pa_client_info *i = &g->client_info.info;
|
|
|
|
pw_log_debug("update %d", g->id);
|
|
info = g->info = pw_client_info_update(g->info, info);
|
|
|
|
i->index = g->id;
|
|
str = info->props ? spa_dict_lookup(info->props, PW_KEY_MODULE_ID) : NULL;
|
|
i->owner_module = str ? (unsigned)atoi(str) : SPA_ID_INVALID;
|
|
|
|
if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS) {
|
|
if (i->proplist)
|
|
pa_proplist_update_dict(i->proplist, info->props);
|
|
else
|
|
i->proplist = pa_proplist_new_dict(info->props);
|
|
i->name = info->props ?
|
|
spa_dict_lookup(info->props, PW_KEY_APP_NAME) : NULL;
|
|
i->driver = info->props ?
|
|
spa_dict_lookup(info->props, PW_KEY_PROTOCOL) : NULL;
|
|
}
|
|
global_sync(g);
|
|
}
|
|
|
|
static const struct pw_client_events client_events = {
|
|
PW_VERSION_CLIENT_EVENTS,
|
|
.info = client_event_info,
|
|
};
|
|
|
|
static void client_destroy(void *data)
|
|
{
|
|
struct global *global = data;
|
|
if (global->client_info.info.proplist)
|
|
pa_proplist_free(global->client_info.info.proplist);
|
|
if (global->info)
|
|
pw_client_info_free(global->info);
|
|
}
|
|
|
|
struct global_info client_info = {
|
|
.version = PW_VERSION_CLIENT,
|
|
.events = &client_events,
|
|
.destroy = client_destroy,
|
|
};
|
|
|
|
static int metadata_property(void *object,
|
|
uint32_t subject,
|
|
const char *key,
|
|
const char *type,
|
|
const char *value)
|
|
{
|
|
struct global *global = object;
|
|
int res;
|
|
|
|
if ((res = pa_metadata_update(global, subject, key, type, value)) < 0)
|
|
return res;
|
|
|
|
if (key) {
|
|
if (strcmp(key, METADATA_DEFAULT_SINK) == 0 ||
|
|
strcmp(key, METADATA_DEFAULT_SOURCE) == 0) {
|
|
emit_event(global->context, global, PA_SUBSCRIPTION_EVENT_CHANGE);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static const struct pw_metadata_events metadata_events = {
|
|
PW_VERSION_METADATA_EVENTS,
|
|
.property = metadata_property,
|
|
};
|
|
|
|
static void metadata_destroy(void *data)
|
|
{
|
|
struct global *global = data;
|
|
pa_context *c = global->context;
|
|
if (c->metadata == global)
|
|
c->metadata = NULL;
|
|
pw_array_clear(&global->metadata_info.metadata);
|
|
}
|
|
|
|
struct global_info metadata_info = {
|
|
.version = PW_VERSION_METADATA,
|
|
.events = &metadata_events,
|
|
.destroy = metadata_destroy,
|
|
};
|
|
|
|
static void proxy_removed(void *data)
|
|
{
|
|
struct global *g = data;
|
|
pw_proxy_destroy(g->proxy);
|
|
}
|
|
|
|
static void proxy_destroy(void *data)
|
|
{
|
|
struct global *g = data;
|
|
spa_hook_remove(&g->proxy_listener);
|
|
spa_hook_remove(&g->object_listener);
|
|
g->proxy = NULL;
|
|
}
|
|
|
|
static void proxy_done(void *data, int seq)
|
|
{
|
|
struct global *g = data;
|
|
pa_subscription_event_type_t event;
|
|
|
|
if (g->pending_seq == seq) {
|
|
if (g->ginfo && g->ginfo->sync)
|
|
g->ginfo->sync(g);
|
|
if (g->init) {
|
|
g->init = false;
|
|
event = PA_SUBSCRIPTION_EVENT_NEW;
|
|
} else {
|
|
event = PA_SUBSCRIPTION_EVENT_CHANGE;
|
|
}
|
|
pw_log_debug("emit because of pending");
|
|
emit_event(g->context, g, event);
|
|
}
|
|
}
|
|
|
|
static const struct pw_proxy_events proxy_events = {
|
|
PW_VERSION_PROXY_EVENTS,
|
|
.removed = proxy_removed,
|
|
.destroy = proxy_destroy,
|
|
.done = proxy_done,
|
|
};
|
|
|
|
static int set_mask(pa_context *c, struct global *g)
|
|
{
|
|
const char *str;
|
|
struct global *f;
|
|
struct global_info *ginfo = NULL;
|
|
|
|
if (strcmp(g->type, PW_TYPE_INTERFACE_Device) == 0) {
|
|
if (g->props == NULL)
|
|
return 0;
|
|
if ((str = pw_properties_get(g->props, PW_KEY_MEDIA_CLASS)) == NULL)
|
|
return 0;
|
|
if (strcmp(str, "Audio/Device") != 0)
|
|
return 0;
|
|
|
|
pw_log_debug("found card %d", g->id);
|
|
g->mask = PA_SUBSCRIPTION_MASK_CARD;
|
|
g->event = PA_SUBSCRIPTION_EVENT_CARD;
|
|
ginfo = &device_info;
|
|
spa_list_init(&g->card_info.profiles);
|
|
spa_list_init(&g->card_info.ports);
|
|
g->card_info.active_port_output = SPA_ID_INVALID;
|
|
g->card_info.active_port_input = SPA_ID_INVALID;
|
|
} else if (strcmp(g->type, PW_TYPE_INTERFACE_Node) == 0) {
|
|
if (g->props == NULL)
|
|
return 0;
|
|
|
|
if ((str = pw_properties_get(g->props, PW_KEY_PRIORITY_MASTER)) != NULL)
|
|
g->priority_master = pw_properties_parse_int(str);
|
|
|
|
if ((str = pw_properties_get(g->props, PW_KEY_MEDIA_CLASS)) == NULL) {
|
|
pw_log_debug("node %d without "PW_KEY_MEDIA_CLASS, g->id);
|
|
return 0;
|
|
}
|
|
|
|
if (strcmp(str, "Audio/Sink") == 0) {
|
|
pw_log_debug("found sink %d", g->id);
|
|
g->mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE;
|
|
g->event = PA_SUBSCRIPTION_EVENT_SINK;
|
|
g->node_info.monitor = g->id | PA_IDX_FLAG_DSP;
|
|
}
|
|
else if (strcmp(str, "Audio/Source") == 0) {
|
|
pw_log_debug("found source %d", g->id);
|
|
g->mask = PA_SUBSCRIPTION_MASK_SOURCE;
|
|
g->event = PA_SUBSCRIPTION_EVENT_SOURCE;
|
|
}
|
|
else if (strcmp(str, "Stream/Output/Audio") == 0) {
|
|
pw_log_debug("found sink input %d", g->id);
|
|
g->mask = PA_SUBSCRIPTION_MASK_SINK_INPUT;
|
|
g->event = PA_SUBSCRIPTION_EVENT_SINK_INPUT;
|
|
}
|
|
else if (strcmp(str, "Stream/Input/Audio") == 0) {
|
|
pw_log_debug("found source output %d", g->id);
|
|
g->mask = PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT;
|
|
g->event = PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT;
|
|
}
|
|
|
|
if ((str = pw_properties_get(g->props, PW_KEY_CLIENT_ID)) != NULL)
|
|
g->node_info.client_id = atoi(str);
|
|
else
|
|
g->node_info.client_id = SPA_ID_INVALID;
|
|
|
|
if ((str = pw_properties_get(g->props, PW_KEY_DEVICE_ID)) != NULL)
|
|
g->node_info.device_id = atoi(str);
|
|
else
|
|
g->node_info.device_id = SPA_ID_INVALID;
|
|
|
|
ginfo = &node_info;
|
|
g->node_info.volume = 1.0f;
|
|
g->node_info.mute = false;
|
|
g->node_info.base_volume = 1.0f;
|
|
g->node_info.volume_step = 1.0f / (PA_VOLUME_NORM+1);
|
|
} else if (strcmp(g->type, PW_TYPE_INTERFACE_Port) == 0) {
|
|
if (g->props == NULL)
|
|
return 0;
|
|
|
|
if ((str = pw_properties_get(g->props, PW_KEY_NODE_ID)) == NULL) {
|
|
pw_log_warn("port %d without "PW_KEY_NODE_ID, g->id);
|
|
return 0;
|
|
}
|
|
g->port_info.node_id = atoi(str);
|
|
pw_log_debug("found port %d node %d", g->id, g->port_info.node_id);
|
|
} else if (strcmp(g->type, PW_TYPE_INTERFACE_Module) == 0) {
|
|
pw_log_debug("found module %d", g->id);
|
|
g->mask = PA_SUBSCRIPTION_MASK_MODULE;
|
|
g->event = PA_SUBSCRIPTION_EVENT_MODULE;
|
|
ginfo = &module_info;
|
|
} else if (strcmp(g->type, PW_TYPE_INTERFACE_Client) == 0) {
|
|
pw_log_debug("found client %d", g->id);
|
|
g->mask = PA_SUBSCRIPTION_MASK_CLIENT;
|
|
g->event = PA_SUBSCRIPTION_EVENT_CLIENT;
|
|
ginfo = &client_info;
|
|
} else if (strcmp(g->type, PW_TYPE_INTERFACE_Link) == 0) {
|
|
if ((str = pw_properties_get(g->props, PW_KEY_LINK_OUTPUT_PORT)) == NULL)
|
|
return 0;
|
|
g->link_info.src = pa_context_find_global(c, pw_properties_parse_int(str));
|
|
if ((str = pw_properties_get(g->props, PW_KEY_LINK_INPUT_PORT)) == NULL)
|
|
return 0;
|
|
g->link_info.dst = pa_context_find_global(c, pw_properties_parse_int(str));
|
|
|
|
if (g->link_info.src == NULL || g->link_info.dst == NULL)
|
|
return 0;
|
|
|
|
pw_log_debug("link %d:%d->%d:%d",
|
|
g->link_info.src->port_info.node_id,
|
|
g->link_info.src->id,
|
|
g->link_info.dst->port_info.node_id,
|
|
g->link_info.dst->id);
|
|
|
|
if ((f = pa_context_find_global(c, g->link_info.src->port_info.node_id)) != NULL &&
|
|
!f->init)
|
|
emit_event(c, f, PA_SUBSCRIPTION_EVENT_CHANGE);
|
|
if ((f = pa_context_find_global(c, g->link_info.dst->port_info.node_id)) != NULL &&
|
|
!f->init)
|
|
emit_event(c, f, PA_SUBSCRIPTION_EVENT_CHANGE);
|
|
|
|
} else if (strcmp(g->type, PW_TYPE_INTERFACE_Metadata) == 0) {
|
|
if (c->metadata == NULL) {
|
|
ginfo = &metadata_info;
|
|
c->metadata = g;
|
|
g->mask = PA_SUBSCRIPTION_MASK_SERVER;
|
|
g->event = PA_SUBSCRIPTION_EVENT_SERVER;
|
|
}
|
|
pw_array_init(&g->metadata_info.metadata, 64);
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
pw_log_debug("global %p: id:%u mask %d/%d", g, g->id, g->mask, g->event);
|
|
|
|
if (ginfo) {
|
|
pw_log_debug("bind %d", g->id);
|
|
|
|
g->proxy = pw_registry_bind(c->registry, g->id, g->type,
|
|
ginfo->version, 0);
|
|
if (g->proxy == NULL)
|
|
return -ENOMEM;
|
|
|
|
pw_proxy_add_object_listener(g->proxy, &g->object_listener, ginfo->events, g);
|
|
pw_proxy_add_listener(g->proxy, &g->proxy_listener, &proxy_events, g);
|
|
g->ginfo = ginfo;
|
|
global_sync(g);
|
|
} else {
|
|
emit_event(c, g, PA_SUBSCRIPTION_EVENT_NEW);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static inline void insert_global(pa_context *c, struct global *global)
|
|
{
|
|
struct global *g;
|
|
bool found = false;
|
|
|
|
spa_list_for_each(g, &c->globals, link) {
|
|
if (g->priority_master < global->priority_master) {
|
|
g = spa_list_prev(g, link);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
spa_list_append(&g->link, &global->link);
|
|
else
|
|
spa_list_prepend(&g->link, &global->link);
|
|
}
|
|
|
|
static void registry_event_global(void *data, uint32_t id,
|
|
uint32_t permissions, const char *type, uint32_t version,
|
|
const struct spa_dict *props)
|
|
{
|
|
pa_context *c = data;
|
|
struct global *g;
|
|
int res;
|
|
|
|
g = calloc(1, sizeof(struct global));
|
|
pw_log_debug("context %p: global %d %s %p", c, id, type, g);
|
|
g->context = c;
|
|
g->id = id;
|
|
g->permissions = permissions;
|
|
g->type = strdup(type);
|
|
g->init = true;
|
|
g->props = props ? pw_properties_new_dict(props) : NULL;
|
|
|
|
res = set_mask(c, g);
|
|
insert_global(c, g);
|
|
|
|
if (res != 1)
|
|
global_free(c, g);
|
|
}
|
|
|
|
static void registry_event_global_remove(void *object, uint32_t id)
|
|
{
|
|
pa_context *c = object;
|
|
struct global *g;
|
|
|
|
pw_log_debug("context %p: remove %d", c, id);
|
|
if ((g = pa_context_find_global(c, id)) == NULL)
|
|
return;
|
|
|
|
emit_event(c, g, PA_SUBSCRIPTION_EVENT_REMOVE);
|
|
|
|
pw_log_debug("context %p: free %d %p", c, id, g);
|
|
global_free(c, g);
|
|
}
|
|
|
|
static const struct pw_registry_events registry_events =
|
|
{
|
|
PW_VERSION_REGISTRY_EVENTS,
|
|
.global = registry_event_global,
|
|
.global_remove = registry_event_global_remove,
|
|
};
|
|
|
|
static void complete_operations(pa_context *c, int seq)
|
|
{
|
|
pa_operation *o, *t;
|
|
spa_list_for_each_safe(o, t, &c->operations, link) {
|
|
if (o->seq != seq)
|
|
continue;
|
|
pa_operation_ref(o);
|
|
if (o->callback)
|
|
o->callback(o, o->userdata);
|
|
pa_operation_unref(o);
|
|
}
|
|
}
|
|
|
|
static void core_info(void *data, const struct pw_core_info *info)
|
|
{
|
|
pa_context *c = data;
|
|
bool first = c->core_info == NULL;
|
|
|
|
pw_log_debug("context %p: info", c);
|
|
|
|
if (first) {
|
|
pa_context_set_state(c, PA_CONTEXT_AUTHORIZING);
|
|
pa_context_set_state(c, PA_CONTEXT_SETTING_NAME);
|
|
}
|
|
|
|
c->core_info = pw_core_info_update(c->core_info, info);
|
|
|
|
if (first)
|
|
pa_context_set_state(c, PA_CONTEXT_READY);
|
|
}
|
|
|
|
static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
|
|
{
|
|
pa_context *c = data;
|
|
|
|
pw_log_error("context %p: error id:%u seq:%d res:%d (%s): %s", c,
|
|
id, seq, res, spa_strerror(res), message);
|
|
|
|
if (id == PW_ID_CORE) {
|
|
if (res == -EPIPE && !c->disconnect)
|
|
pa_context_fail(c, PA_ERR_CONNECTIONTERMINATED);
|
|
}
|
|
}
|
|
|
|
static void core_done(void *data, uint32_t id, int seq)
|
|
{
|
|
pa_context *c = data;
|
|
pw_log_debug("done id:%u seq:%d", id, seq);
|
|
complete_operations(c, seq);
|
|
}
|
|
|
|
static const struct pw_core_events core_events = {
|
|
PW_VERSION_CORE_EVENTS,
|
|
.info = core_info,
|
|
.done = core_done,
|
|
.error = core_error
|
|
};
|
|
|
|
struct success_data {
|
|
pa_context_success_cb_t cb;
|
|
void *userdata;
|
|
int error;
|
|
};
|
|
|
|
static void on_success(pa_operation *o, void *userdata)
|
|
{
|
|
struct success_data *d = userdata;
|
|
pa_context *c = o->context;
|
|
pw_log_debug("context %p: error %d", c, d->error);
|
|
if (d->error != 0)
|
|
pa_context_set_error(c, d->error);
|
|
if (d->cb)
|
|
d->cb(c, d->error ? 0 : 1, d->userdata);
|
|
pa_operation_done(o);
|
|
}
|
|
|
|
void pa_context_ensure_registry(pa_context *c)
|
|
{
|
|
if (c->registry != NULL)
|
|
return;
|
|
|
|
c->registry = pw_core_get_registry(c->core,
|
|
PW_VERSION_REGISTRY, 0);
|
|
pw_registry_add_listener(c->registry,
|
|
&c->registry_listener,
|
|
®istry_events, c);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
struct success_data *d;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
|
|
|
|
c->subscribe_mask = m;
|
|
|
|
pa_context_ensure_registry(c);
|
|
|
|
o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data));
|
|
d = o->userdata;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
|
|
return o;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, PA_CONST pa_proplist *p)
|
|
{
|
|
struct pw_context *context;
|
|
struct pw_loop *loop;
|
|
struct pw_properties *props;
|
|
pa_context *c;
|
|
|
|
pa_assert(mainloop);
|
|
|
|
props = pw_properties_new(NULL, NULL);
|
|
if (name)
|
|
pw_properties_set(props, PA_PROP_APPLICATION_NAME, name);
|
|
pw_properties_set(props, PW_KEY_CLIENT_API, "pulseaudio");
|
|
if (p)
|
|
pw_properties_update_proplist(props, p);
|
|
|
|
loop = mainloop->userdata;
|
|
context = pw_context_new(loop, NULL, sizeof(struct pa_context));
|
|
if (context == NULL)
|
|
return NULL;
|
|
|
|
c = pw_context_get_user_data(context);
|
|
c->props = props;
|
|
c->loop = loop;
|
|
c->context = context;
|
|
c->proplist = p ? pa_proplist_copy(p) : pa_proplist_new();
|
|
c->refcount = 1;
|
|
c->client_index = PA_INVALID_INDEX;
|
|
|
|
if (name)
|
|
pa_proplist_sets(c->proplist, PA_PROP_APPLICATION_NAME, name);
|
|
|
|
c->mainloop = mainloop;
|
|
c->error = 0;
|
|
c->state = PA_CONTEXT_UNCONNECTED;
|
|
|
|
spa_list_init(&c->globals);
|
|
|
|
spa_list_init(&c->streams);
|
|
spa_list_init(&c->operations);
|
|
|
|
return c;
|
|
}
|
|
|
|
static void do_context_destroy(pa_mainloop_api*m, void *userdata)
|
|
{
|
|
pa_context *c = userdata;
|
|
pw_context_destroy(c->context);
|
|
}
|
|
|
|
static void context_free(pa_context *c)
|
|
{
|
|
pw_log_debug("context %p: free", c);
|
|
|
|
context_unlink(c);
|
|
|
|
pw_properties_free(c->props);
|
|
if (c->proplist)
|
|
pa_proplist_free(c->proplist);
|
|
if (c->core_info)
|
|
pw_core_info_free(c->core_info);
|
|
|
|
pa_mainloop_api_once(c->mainloop, do_context_destroy, c);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
void pa_context_unref(pa_context *c)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
if (--c->refcount == 0)
|
|
context_free(c);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_context* pa_context_ref(pa_context *c)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
c->refcount++;
|
|
return c;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED)
|
|
return;
|
|
|
|
c->state_callback = cb;
|
|
c->state_userdata = userdata;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
void pa_context_set_event_callback(pa_context *c, pa_context_event_cb_t cb, void *userdata)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
if (c->state == PA_CONTEXT_TERMINATED || c->state == PA_CONTEXT_FAILED)
|
|
return;
|
|
|
|
c->event_callback = cb;
|
|
c->event_userdata = userdata;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
int pa_context_errno(PA_CONST pa_context *c)
|
|
{
|
|
if (!c)
|
|
return PA_ERR_INVALID;
|
|
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
return c->error;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
int pa_context_is_pending(PA_CONST pa_context *c)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE);
|
|
|
|
return !spa_list_is_empty(&c->operations);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_context_state_t pa_context_get_state(PA_CONST pa_context *c)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
return c->state;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api)
|
|
{
|
|
int res = 0;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_UNCONNECTED, PA_ERR_BADSTATE);
|
|
PA_CHECK_VALIDITY(c, !(flags & ~(PA_CONTEXT_NOAUTOSPAWN|PA_CONTEXT_NOFAIL)), PA_ERR_INVALID);
|
|
PA_CHECK_VALIDITY(c, !server || *server, PA_ERR_INVALID);
|
|
|
|
pa_context_ref(c);
|
|
|
|
c->no_fail = !!(flags & PA_CONTEXT_NOFAIL);
|
|
|
|
pa_context_set_state(c, PA_CONTEXT_CONNECTING);
|
|
|
|
c->core = pw_context_connect(c->context, pw_properties_copy(c->props), 0);
|
|
if (c->core == NULL) {
|
|
pa_context_fail(c, PA_ERR_CONNECTIONREFUSED);
|
|
res = -1;
|
|
goto exit;
|
|
}
|
|
pw_core_add_listener(c->core, &c->core_listener, &core_events, c);
|
|
|
|
exit:
|
|
pa_context_unref(c);
|
|
|
|
return res;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
void pa_context_disconnect(pa_context *c)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
c->disconnect = true;
|
|
if (c->registry) {
|
|
pw_proxy_destroy((struct pw_proxy*)c->registry);
|
|
c->registry = NULL;
|
|
}
|
|
if (c->core) {
|
|
pw_core_disconnect(c->core);
|
|
c->core = NULL;
|
|
}
|
|
if (PA_CONTEXT_IS_GOOD(c->state))
|
|
pa_context_set_state(c, PA_CONTEXT_TERMINATED);
|
|
}
|
|
|
|
struct notify_data {
|
|
pa_context_notify_cb_t cb;
|
|
void *userdata;
|
|
};
|
|
|
|
static void on_notify(pa_operation *o, void *userdata)
|
|
{
|
|
struct notify_data *d = userdata;
|
|
pa_context *c = o->context;
|
|
pa_operation_done(o);
|
|
if (d->cb)
|
|
d->cb(c, d->userdata);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
struct notify_data *d;
|
|
|
|
o = pa_operation_new(c, NULL, on_notify, sizeof(struct notify_data));
|
|
d = o->userdata;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
|
|
return o;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
struct success_data *d;
|
|
|
|
o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data));
|
|
d = o->userdata;
|
|
d->error = PA_ERR_NOTIMPLEMENTED;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
pw_log_warn("Not Implemented");
|
|
|
|
return o;
|
|
}
|
|
|
|
struct default_node {
|
|
uint32_t mask;
|
|
pa_context_success_cb_t cb;
|
|
void *userdata;
|
|
char *name;
|
|
const char *key;
|
|
};
|
|
|
|
static void do_default_node(pa_operation *o, void *userdata)
|
|
{
|
|
struct default_node *d = userdata;
|
|
pa_context *c = o->context;
|
|
struct global *g;
|
|
int error = 0;
|
|
|
|
pw_log_debug("%p", c);
|
|
|
|
g = pa_context_find_global_by_name(c, d->mask, d->name);
|
|
if (g == NULL) {
|
|
error = PA_ERR_NOENTITY;
|
|
} else if (c->metadata) {
|
|
pw_metadata_set_property(c->metadata->proxy,
|
|
PW_ID_CORE, d->key, "text/plain", d->name);
|
|
} else {
|
|
error = PA_ERR_NOTIMPLEMENTED;
|
|
}
|
|
if (error != 0)
|
|
pa_context_set_error(c, error);
|
|
if (d->cb)
|
|
d->cb(c, error != 0 ? 0 : 1, d->userdata);
|
|
pa_operation_done(o);
|
|
pa_xfree(d->name);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
struct default_node *d;
|
|
|
|
pa_context_ensure_registry(c);
|
|
|
|
o = pa_operation_new(c, NULL, do_default_node, sizeof(*d));
|
|
d = o->userdata;
|
|
d->mask = PA_SUBSCRIPTION_MASK_SINK;
|
|
d->name = pa_xstrdup(name);
|
|
d->key = METADATA_DEFAULT_SINK;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
|
|
return o;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
struct default_node *d;
|
|
|
|
pa_context_ensure_registry(c);
|
|
|
|
o = pa_operation_new(c, NULL, do_default_node, sizeof(*d));
|
|
d = o->userdata;
|
|
d->mask = PA_SUBSCRIPTION_MASK_SOURCE;
|
|
d->name = pa_xstrdup(name);
|
|
d->key = METADATA_DEFAULT_SOURCE;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
|
|
return o;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
int pa_context_is_local(PA_CONST pa_context *c)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, -1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata)
|
|
{
|
|
struct spa_dict dict;
|
|
struct spa_dict_item items[1];
|
|
pa_operation *o;
|
|
struct success_data *d;
|
|
int changed;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
pa_assert(name);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
|
|
|
|
items[0] = SPA_DICT_ITEM_INIT(PA_PROP_APPLICATION_NAME, name);
|
|
dict = SPA_DICT_INIT(items, 1);
|
|
changed = pw_properties_update(c->props, &dict);
|
|
|
|
if (changed) {
|
|
struct pw_client *client;
|
|
|
|
client = pw_core_get_client(c->core);
|
|
pw_client_update_properties(client, &c->props->dict);
|
|
}
|
|
|
|
o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data));
|
|
d = o->userdata;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
|
|
return o;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
const char* pa_context_get_server(PA_CONST pa_context *c)
|
|
{
|
|
const struct pw_core_info *info;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
info = c->core_info;
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, info && info->name, PA_ERR_NOENTITY);
|
|
|
|
return info->name;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
uint32_t pa_context_get_protocol_version(PA_CONST pa_context *c)
|
|
{
|
|
return PA_PROTOCOL_VERSION;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
uint32_t pa_context_get_server_protocol_version(PA_CONST pa_context *c)
|
|
{
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(c, PA_CONTEXT_IS_GOOD(c->state), PA_ERR_BADSTATE, PA_INVALID_INDEX);
|
|
|
|
return PA_PROTOCOL_VERSION;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation *pa_context_proplist_update(pa_context *c, pa_update_mode_t mode, PA_CONST pa_proplist *p, pa_context_success_cb_t cb, void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
struct success_data *d;
|
|
|
|
spa_assert(c);
|
|
spa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, mode == PA_UPDATE_SET ||
|
|
mode == PA_UPDATE_MERGE || mode == PA_UPDATE_REPLACE, PA_ERR_INVALID);
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
|
|
|
|
pa_proplist_update(c->proplist, mode, p);
|
|
|
|
o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data));
|
|
d = o->userdata;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
return o;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_operation *pa_context_proplist_remove(pa_context *c, const char *const keys[], pa_context_success_cb_t cb, void *userdata)
|
|
{
|
|
pa_operation *o;
|
|
struct success_data *d;
|
|
|
|
spa_assert(c);
|
|
spa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, keys && keys[0], PA_ERR_INVALID);
|
|
PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE);
|
|
|
|
pw_log_warn("Not Implemented");
|
|
|
|
o = pa_operation_new(c, NULL, on_success, sizeof(struct success_data));
|
|
d = o->userdata;
|
|
d->cb = cb;
|
|
d->userdata = userdata;
|
|
pa_operation_sync(o);
|
|
return o;
|
|
}
|
|
|
|
SPA_EXPORT
|
|
uint32_t pa_context_get_index(PA_CONST pa_context *c)
|
|
{
|
|
struct pw_client *client;
|
|
|
|
pa_assert(c);
|
|
spa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX);
|
|
client = pw_core_get_client(c->core);
|
|
if (client == NULL)
|
|
return PA_INVALID_INDEX;
|
|
|
|
return pw_proxy_get_bound_id((struct pw_proxy*)client);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
pa_time_event* pa_context_rttime_new(PA_CONST pa_context *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata)
|
|
{
|
|
struct timeval tv;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
pa_assert(c->mainloop);
|
|
|
|
if (usec == PA_USEC_INVALID)
|
|
return c->mainloop->time_new(c->mainloop, NULL, cb, userdata);
|
|
|
|
pa_timeval_store(&tv, usec);
|
|
|
|
return c->mainloop->time_new(c->mainloop, &tv, cb, userdata);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
void pa_context_rttime_restart(PA_CONST pa_context *c, pa_time_event *e, pa_usec_t usec)
|
|
{
|
|
struct timeval tv;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
pa_assert(c->mainloop);
|
|
|
|
if (usec == PA_USEC_INVALID)
|
|
c->mainloop->time_restart(e, NULL);
|
|
else {
|
|
pa_timeval_store(&tv, usec);
|
|
c->mainloop->time_restart(e, &tv);
|
|
}
|
|
}
|
|
|
|
SPA_EXPORT
|
|
size_t pa_context_get_tile_size(PA_CONST pa_context *c, const pa_sample_spec *ss)
|
|
{
|
|
size_t fs, mbs;
|
|
|
|
pa_assert(c);
|
|
pa_assert(c->refcount >= 1);
|
|
|
|
PA_CHECK_VALIDITY_RETURN_ANY(c, !ss || pa_sample_spec_valid(ss), PA_ERR_INVALID, (size_t) -1);
|
|
|
|
fs = ss ? pa_frame_size(ss) : 1;
|
|
mbs = PA_ROUND_DOWN(4096, fs);
|
|
return PA_MAX(mbs, fs);
|
|
}
|
|
|
|
SPA_EXPORT
|
|
int pa_context_load_cookie_from_file(pa_context *c, const char *cookie_file_path)
|
|
{
|
|
return 0;
|
|
}
|