From 224712ad5f07dccb6acb19ddf8333d822c7928ce Mon Sep 17 00:00:00 2001 From: George Kiagiadakis Date: Fri, 7 Jun 2019 17:44:35 +0300 Subject: Initial binding version Signed-off-by: George Kiagiadakis Change-Id: I89e493d88c7fa1309f1b2991d346fc496caa6898 --- binding/CMakeLists.txt | 20 ++ binding/audiomixer-binding.c | 342 +++++++++++++++++++++++++ binding/audiomixer.c | 597 +++++++++++++++++++++++++++++++++++++++++++ binding/audiomixer.h | 60 +++++ 4 files changed, 1019 insertions(+) create mode 100644 binding/CMakeLists.txt create mode 100644 binding/audiomixer-binding.c create mode 100644 binding/audiomixer.c create mode 100644 binding/audiomixer.h (limited to 'binding') diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt new file mode 100644 index 0000000..587d137 --- /dev/null +++ b/binding/CMakeLists.txt @@ -0,0 +1,20 @@ +PROJECT_TARGET_ADD(audiomixer-binding) + + add_definitions(-DAFB_BINDING_VERSION=3) + add_definitions(-DBUILDING_APPFW_BINDING) + + set(audiomixer_SOURCES + audiomixer-binding.c + audiomixer.c + ) + + add_library(${TARGET_NAME} MODULE ${audiomixer_SOURCES}) + + SET_TARGET_PROPERTIES(${TARGET_NAME} PROPERTIES + PREFIX "libafm-" + LABELS "BINDING" + LINK_FLAGS ${BINDINGS_LINK_FLAG} + OUTPUT_NAME ${TARGET_NAME} + ) + + TARGET_LINK_LIBRARIES(${TARGET_NAME} ${link_libraries}) diff --git a/binding/audiomixer-binding.c b/binding/audiomixer-binding.c new file mode 100644 index 0000000..005f5c7 --- /dev/null +++ b/binding/audiomixer-binding.c @@ -0,0 +1,342 @@ +/* + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include "audiomixer.h" + +static struct audiomixer *audiomixer; +static afb_event_t controls_changed; +static afb_event_t volume_changed; +static afb_event_t mute_changed; +static sd_event_source *controls_changed_source; + +static int +audiomixer_controls_changed_deferred(sd_event_source *s, void *data) +{ + afb_event_push(controls_changed, NULL); + + sd_event_source_unref(controls_changed_source); + controls_changed_source = NULL; + return 0; +} + +struct value_changed_data +{ + unsigned int change_mask; + struct mixer_control control; + sd_event_source *source; +}; + +static int +audiomixer_value_changed_deferred(sd_event_source *s, void *data) +{ + struct value_changed_data *d = data; + json_object *json; + + json = json_object_new_object(); + json_object_object_add(json, "control", + json_object_new_string(d->control.name)); + + if (d->change_mask & MIXER_CONTROL_CHANGE_FLAG_VOLUME) { + json_object_object_add(json, "value", + json_object_new_double(d->control.volume)); + afb_event_push(volume_changed, json); + } else if (d->change_mask & MIXER_CONTROL_CHANGE_FLAG_MUTE) { + json_object_object_add(json, "value", + json_object_new_int(d->control.mute)); + afb_event_push(mute_changed, json); + } + + sd_event_source_unref(d->source); + free(d); + return 0; +} + +/* called in audiomixer's thread */ +static void +audiomixer_controls_changed(void *data) +{ + sd_event *e = afb_daemon_get_event_loop(); + sd_event_add_defer(e, &controls_changed_source, + audiomixer_controls_changed_deferred, NULL); +} + + +/* called in audiomixer's thread */ +static void +audiomixer_value_changed(void *data, + unsigned int change_mask, + const struct mixer_control *control) +{ + sd_event *e = afb_daemon_get_event_loop(); + struct value_changed_data *d = calloc(1, sizeof(*d)); + + d->change_mask = change_mask; + d->control = *control; + + if (sd_event_add_defer(e, &d->source, + audiomixer_value_changed_deferred, d) < 0) + free(d); +} + +static const struct audiomixer_events audiomixer_events = { + .controls_changed = audiomixer_controls_changed, + .value_changed = audiomixer_value_changed, +}; + +static int +cleanup(sd_event_source *s, void *data) +{ + audiomixer_free(audiomixer); + audiomixer = NULL; + return 0; +} + +static int +init(afb_api_t api) +{ + sd_event *e = afb_daemon_get_event_loop(); + + controls_changed = afb_api_make_event(api, "controls_changed"); + volume_changed = afb_api_make_event(api, "volume_changed"); + mute_changed = afb_api_make_event(api, "mute_changed"); + + audiomixer = audiomixer_new(); + sd_event_add_exit(e, NULL, cleanup, NULL); + + audiomixer_add_event_listener(audiomixer, &audiomixer_events, NULL); + + return 0; +} + +static void +list_controls_cb(afb_req_t request) +{ + json_object *ret_json, *nest_json; + const struct mixer_control **ctls; + unsigned int n_controls, i; + + audiomixer_lock(audiomixer); + + if (audiomixer_ensure_connected(audiomixer, 3) < 0) { + afb_req_fail(request, "failed", + "Could not connect to the PipeWire daemon"); + goto unlock; + } + + if (audiomixer_ensure_controls(audiomixer, 3) < 0) { + AFB_REQ_NOTICE(request, "No mixer controls were exposed " + "in PipeWire after 3 seconds"); + } + + ctls = audiomixer_get_active_controls(audiomixer, &n_controls); + + ret_json = json_object_new_array(); + for (i = 0; i < n_controls; i++) { + nest_json = json_object_new_object(); + json_object_object_add(nest_json, "control", + json_object_new_string(ctls[i]->name)); + json_object_object_add(nest_json, "volume", + json_object_new_double(ctls[i]->volume)); + json_object_object_add(nest_json, "mute", + json_object_new_int(ctls[i]->mute)); + json_object_array_add(ret_json, nest_json); + } + afb_req_success(request, ret_json, NULL); + +unlock: + audiomixer_unlock(audiomixer); +} + +static void +volume_cb(afb_req_t request) +{ + json_object *ret_json; + const char *control = afb_req_value(request, "control"); + const char *value = afb_req_value(request, "value"); + const struct mixer_control *ctl; + double volume; + + audiomixer_lock(audiomixer); + + if (!control) { + afb_req_fail(request, "failed", + "Invalid arguments: missing 'control'"); + goto unlock; + } + + if (audiomixer_ensure_connected(audiomixer, 3) < 0) { + afb_req_fail(request, "failed", + "Could not connect to the PipeWire daemon"); + goto unlock; + } + + if (audiomixer_ensure_controls(audiomixer, 3) < 0) { + AFB_REQ_NOTICE(request, "No mixer controls were exposed " + "in PipeWire after 3 seconds"); + } + + ctl = audiomixer_find_control(audiomixer, control); + if (!ctl) { + afb_req_fail(request, "failed", "Could not find control"); + goto unlock; + } + + if(value) { + char *endptr; + volume = strtod(value, &endptr); + if (endptr == value || volume < -0.00001 || volume > 1.00001) { + afb_req_fail(request, "failed", + "Invalid volume value (must be between 0.0 and 1.0)"); + goto unlock; + } + + audiomixer_change_volume(audiomixer, ctl, volume); + } else { + volume = ctl->volume; + } + + ret_json = json_object_new_object(); + json_object_object_add(ret_json, "volume", json_object_new_double(volume)); + afb_req_success(request, ret_json, NULL); + +unlock: + audiomixer_unlock(audiomixer); +} + +static void +mute_cb(afb_req_t request) +{ + json_object *ret_json; + const char *control = afb_req_value(request, "control"); + const char *value = afb_req_value(request, "value"); + const struct mixer_control *ctl; + int mute; + + audiomixer_lock(audiomixer); + + if (!control) { + afb_req_fail(request, "failed", + "Invalid arguments: missing 'control'"); + goto unlock; + } + + if (audiomixer_ensure_connected(audiomixer, 3) < 0) { + afb_req_fail(request, "failed", + "Could not connect to the PipeWire daemon"); + goto unlock; + } + + if (audiomixer_ensure_controls(audiomixer, 3) < 0) { + AFB_REQ_NOTICE(request, "No mixer controls were exposed " + "in PipeWire after 3 seconds"); + } + + ctl = audiomixer_find_control(audiomixer, control); + if (!ctl) { + afb_req_fail(request, "failed", "Could not find control"); + goto unlock; + } + + if(value) { + char *endptr; + mute = (int) strtol(value, &endptr, 10); + if (endptr == value || mute < 0 || mute > 1) { + afb_req_fail(request, "failed", + "Invalid mute value (must be integer 0 or 1)"); + goto unlock; + } + + audiomixer_change_mute(audiomixer, ctl, mute); + } else { + mute = ctl->mute; + } + + ret_json = json_object_new_object(); + json_object_object_add(ret_json, "mute", json_object_new_int(mute)); + afb_req_success(request, ret_json, NULL); + +unlock: + audiomixer_unlock(audiomixer); +} + +static void +subscribe_cb(afb_req_t request) +{ + const char *eventstr = afb_req_value(request, "event"); + afb_event_t event; + + if (!eventstr) { + afb_req_fail(request, "failed", + "Invalid arguments: missing 'event'"); + return; + } + + if (!strcmp(eventstr, "controls_changed")) + event = controls_changed; + else if (!strcmp(eventstr, "volume_changed")) + event = volume_changed; + else if (!strcmp(eventstr, "mute_changed")) + event = mute_changed; + else { + afb_req_fail(request, "failed", "Invalid event name"); + return; + } + + if (afb_req_subscribe(request, event) != 0) + afb_req_fail(request, "failed", "Failed to subscribe to event"); + else + afb_req_success(request, NULL, "Subscribed"); +} + +static void +unsubscribe_cb(afb_req_t request) +{ + const char *eventstr = afb_req_value(request, "event"); + afb_event_t event; + + if (!eventstr) { + afb_req_fail(request, "failed", + "Invalid arguments: missing 'event'"); + return; + } + + if (!strcmp(eventstr, "controls_changed")) + event = controls_changed; + else if (!strcmp(eventstr, "volume_changed")) + event = volume_changed; + else if (!strcmp(eventstr, "mute_changed")) + event = mute_changed; + else { + afb_req_fail(request, "failed", "Invalid event name"); + return; + } + + if (afb_req_unsubscribe(request, event) != 0) + afb_req_fail(request, "failed", "Failed to unsubscribe from event"); + else + afb_req_success(request, NULL, "Unsubscribed"); +} + +static const afb_verb_t verbs[]= { + { .verb = "list_controls", .callback = list_controls_cb, .info = "List the available controls" }, + { .verb = "volume", .callback = volume_cb, .info = "Get/Set volume" }, + { .verb = "mute", .callback = mute_cb, .info = "Get/Set mute" }, + { .verb = "subscribe", .callback = subscribe_cb, .info = "Subscribe to mixer events" }, + { .verb = "unsubscribe", .callback = unsubscribe_cb, .info = "Unsubscribe from mixer events" }, + { } +}; + +const afb_binding_t afbBindingV3 = { + .api = "audiomixer", + .specification = "AudioMixer API", + .verbs = verbs, + .init = init, +}; diff --git a/binding/audiomixer.c b/binding/audiomixer.c new file mode 100644 index 0000000..c4ccc12 --- /dev/null +++ b/binding/audiomixer.c @@ -0,0 +1,597 @@ +/* + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include "audiomixer.h" +#include +#include +#include + +#if !defined(BUILDING_APPFW_BINDING) +#define debug(...) fprintf(stdout, __VA_ARGS__) +#else +#include +#define debug(...) AFB_DEBUG(__VA_ARGS__) +#endif + +struct audiomixer +{ + struct pw_thread_loop *main_loop; + + struct pw_core *core; + struct pw_remote *remote; + struct spa_hook remote_listener; + + struct pw_core_proxy *core_proxy; + struct spa_hook remote_core_listener; + struct pw_registry_proxy *registry_proxy; + struct spa_hook registry_listener; + + struct pw_array endpoints; + struct pw_array all_mixer_controls; + + const struct audiomixer_events *events; + void *events_data; +}; + +enum endpoint_state { + EP_STATE_INITIAL, + EP_STATE_COLLECT_ENUM_STREAM, + EP_STATE_COLLECT_ENUM_CONTROL, + EP_STATE_COLLECT_CONTROL, + EP_STATE_ACTIVE, +}; + +struct endpoint +{ + struct audiomixer *audiomixer; + struct pw_endpoint_proxy *proxy; + + struct pw_properties *properties; + enum endpoint_state state; + + struct spa_hook proxy_listener; + struct spa_hook endpoint_listener; + + struct pw_array mixer_controls; +}; + +struct mixer_control_impl +{ + struct mixer_control pub; + struct endpoint *endpoint; + uint32_t stream_id; + uint32_t volume_control_id; + uint32_t mute_control_id; +}; + +static void +emit_controls_changed(struct audiomixer *self) +{ + pw_thread_loop_signal(self->main_loop, false); + + if (!self->events || !self->events->controls_changed) + return; + + self->events->controls_changed(self->events_data); +} + +static void +emit_value_changed(struct audiomixer *self, + unsigned int change_mask, + struct mixer_control *ctl) +{ + if (!self->events || !self->events->value_changed) + return; + + self->events->value_changed(self->events_data, change_mask, ctl); +} + +static void +advance_endpoint_state(struct endpoint *endpoint) +{ + debug("%p advance endpoint state, was:%d", endpoint, endpoint->state); + + switch (endpoint->state) { + case EP_STATE_INITIAL: + endpoint->state = EP_STATE_COLLECT_ENUM_STREAM; + pw_endpoint_proxy_enum_params(endpoint->proxy, 0, + PW_ENDPOINT_PARAM_EnumStream, 0, -1, NULL); + pw_proxy_sync((struct pw_proxy *) endpoint->proxy, 0); + break; + case EP_STATE_COLLECT_ENUM_STREAM: + endpoint->state = EP_STATE_COLLECT_ENUM_CONTROL; + pw_endpoint_proxy_enum_params(endpoint->proxy, 0, + PW_ENDPOINT_PARAM_EnumControl, 0, -1, NULL); + pw_proxy_sync((struct pw_proxy *) endpoint->proxy, 0); + break; + case EP_STATE_COLLECT_ENUM_CONTROL: { + uint32_t ids[1] = { PW_ENDPOINT_PARAM_Control }; + + endpoint->state = EP_STATE_COLLECT_CONTROL; + pw_endpoint_proxy_subscribe_params(endpoint->proxy, ids, 1); + pw_proxy_sync((struct pw_proxy *) endpoint->proxy, 0); + break; + } + case EP_STATE_COLLECT_CONTROL: { + struct mixer_control_impl *ctl; + struct audiomixer *self = endpoint->audiomixer; + + endpoint->state = EP_STATE_ACTIVE; + pw_array_for_each(ctl, &endpoint->mixer_controls) { + pw_array_add_ptr(&self->all_mixer_controls, ctl); + } + emit_controls_changed(self); + break; + } + default: + break; + } +} + +static void +endpoint_param (void *object, int seq, uint32_t id, + uint32_t index, uint32_t next, + const struct spa_pod *param) +{ + struct endpoint *endpoint = object; + struct mixer_control_impl *ctl; + const struct spa_pod_prop *prop; + struct spa_pod_object *obj = (struct spa_pod_object *) param; + + if (!spa_pod_is_object(param)) { + debug("endpoint_param: bad param - not an object"); + return; + } + + switch (id) { + case PW_ENDPOINT_PARAM_EnumStream: + /* verify conditions */ + if (endpoint->state != EP_STATE_COLLECT_ENUM_STREAM) { + debug("endpoint_param EnumStream: wrong state"); + return; + } + if (SPA_POD_OBJECT_TYPE(obj) != PW_ENDPOINT_OBJECT_ParamStream) { + debug("endpoint_param EnumStream: invalid param"); + return; + } + + /* create new mixer control */ + ctl = pw_array_add(&endpoint->mixer_controls, sizeof(*ctl)); + ctl->endpoint = endpoint; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case PW_ENDPOINT_PARAM_STREAM_id: + spa_pod_get_int(&prop->value, &ctl->stream_id); + break; + case PW_ENDPOINT_PARAM_STREAM_name: + spa_pod_copy_string(&prop->value, + SPA_N_ELEMENTS(ctl->pub.name), + ctl->pub.name); + break; + default: + break; + } + } + break; + + case PW_ENDPOINT_PARAM_EnumControl: { + uint32_t tmp_id = -1; + const char *name = NULL; + + /* verify conditions */ + if (endpoint->state != EP_STATE_COLLECT_ENUM_CONTROL) { + debug("endpoint_param EnumControl: wrong state"); + return; + } + if (SPA_POD_OBJECT_TYPE(obj) != PW_ENDPOINT_OBJECT_ParamControl) { + debug("endpoint_param EnumControl: invalid param"); + return; + } + + /* find the mixer control */ + prop = spa_pod_object_find_prop(obj, NULL, + PW_ENDPOINT_PARAM_CONTROL_stream_id); + if (prop) + spa_pod_get_int(&prop->value, &tmp_id); + else { + debug("endpoint_param EnumControl: invalid control without stream"); + return; + } + + pw_array_for_each(ctl, &endpoint->mixer_controls) { + if (ctl->stream_id == tmp_id) + break; + } + + /* check if we reached the end of the array + * without finding the stream */ + if (!pw_array_check(&endpoint->mixer_controls, ctl)) { + debug("endpoint_param EnumControl: could not find " + "stream id %u", tmp_id); + return; + } + + /* store the control id based on the control's name */ + prop = spa_pod_object_find_prop(obj, NULL, + PW_ENDPOINT_PARAM_CONTROL_name); + if (prop) + spa_pod_get_string(&prop->value, &name); + else { + debug("endpoint_param EnumControl: invalid control without name"); + return; + } + + prop = spa_pod_object_find_prop(obj, NULL, + PW_ENDPOINT_PARAM_CONTROL_id); + if (!prop) { + debug("endpoint_param EnumControl: invalid control without id"); + return; + } + + if (strcmp (name, "volume")) { + spa_pod_get_int(&prop->value, &ctl->volume_control_id); + prop = spa_pod_object_find_prop(obj, NULL, + PW_ENDPOINT_PARAM_CONTROL_type); + } else if (strcmp (name, "mute")) { + spa_pod_get_int(&prop->value, &ctl->mute_control_id); + } + + break; + } + case PW_ENDPOINT_PARAM_Control: { + uint32_t tmp_id = -1; + + /* verify conditions */ + if (endpoint->state != EP_STATE_COLLECT_CONTROL || + endpoint->state != EP_STATE_ACTIVE) { + debug("endpoint_param Control: wrong state"); + return; + } + if (SPA_POD_OBJECT_TYPE(obj) != PW_ENDPOINT_OBJECT_ParamControl) { + debug("endpoint_param Control: invalid param"); + return; + } + + /* match the control id and set the value */ + prop = spa_pod_object_find_prop(obj, NULL, + PW_ENDPOINT_PARAM_CONTROL_id); + if (prop) + spa_pod_get_int(&prop->value, &tmp_id); + else { + debug("endpoint_param Control: invalid control without id"); + return; + } + + prop = spa_pod_object_find_prop(obj, NULL, + PW_ENDPOINT_PARAM_CONTROL_value); + if (!prop) { + debug("endpoint_param Control: invalid control without value"); + return; + } + + pw_array_for_each(ctl, &endpoint->mixer_controls) { + if (ctl->volume_control_id == tmp_id) { + spa_pod_get_double(&prop->value, &ctl->pub.volume); + + if (endpoint->state == EP_STATE_ACTIVE) { + emit_value_changed(endpoint->audiomixer, + MIXER_CONTROL_CHANGE_FLAG_VOLUME, + &ctl->pub); + } + break; + } else if (ctl->mute_control_id == tmp_id) { + spa_pod_get_bool(&prop->value, &ctl->pub.mute); + + if (endpoint->state == EP_STATE_ACTIVE) { + emit_value_changed(endpoint->audiomixer, + MIXER_CONTROL_CHANGE_FLAG_MUTE, + &ctl->pub); + } + break; + } + } + + break; + } + default: + break; + } +} + +struct pw_endpoint_proxy_events endpoint_events = { + PW_VERSION_ENDPOINT_PROXY_EVENTS, + .param = endpoint_param, +}; + +static void +endpoint_proxy_destroyed(void *object) +{ + struct endpoint *endpoint = object; + struct audiomixer *self = endpoint->audiomixer; + struct mixer_control_impl *ctl; + struct mixer_control **ctlptr; + struct endpoint **epptr; + + debug("%p endpoint destroyed", endpoint); + + if (endpoint->properties) + pw_properties_free(endpoint->properties); + + pw_array_for_each(ctl, &endpoint->mixer_controls) { + pw_array_for_each(ctlptr, &self->all_mixer_controls) { + if (*ctlptr == &ctl->pub) { + pw_array_remove(&self->all_mixer_controls, ctlptr); + break; + } + } + } + emit_controls_changed(self); + pw_array_clear(&endpoint->mixer_controls); + + pw_array_for_each(epptr, &self->endpoints) { + if (*epptr == endpoint) { + pw_array_remove(&self->endpoints, epptr); + break; + } + } +} + +static void +endpoint_proxy_done(void *object, int seq) +{ + struct endpoint *endpoint = object; + advance_endpoint_state(endpoint); +} + +struct pw_proxy_events proxy_events = { + PW_VERSION_PROXY_EVENTS, + .destroy = endpoint_proxy_destroyed, + .done = endpoint_proxy_done, +}; + +static void +registry_event_global(void *data, uint32_t id, uint32_t parent_id, + uint32_t permissions, uint32_t type, uint32_t version, + const struct spa_dict *props) +{ + struct audiomixer *self = data; + const char *media_class; + struct pw_proxy *proxy; + struct endpoint *endpoint; + + if (type != PW_TYPE_INTERFACE_Endpoint) + return; + + /* we are only interested in mixer endpoints */ + media_class = props ? spa_dict_lookup(props, "media.class") : NULL; + if (!media_class || strcmp(media_class, "Mixer/Audio") != 0) + return; + + proxy = pw_registry_proxy_bind(self->registry_proxy, + id, type, PW_VERSION_ENDPOINT, sizeof(struct endpoint)); + + endpoint = pw_proxy_get_user_data(proxy); + endpoint->audiomixer = self; + endpoint->proxy = (struct pw_endpoint_proxy *) proxy; + endpoint->properties = props ? pw_properties_new_dict(props) : NULL; + endpoint->state = EP_STATE_INITIAL; + pw_array_init(&endpoint->mixer_controls, 4 * sizeof(struct mixer_control)); + + pw_proxy_add_listener(proxy, &endpoint->proxy_listener, + &proxy_events, endpoint); + pw_endpoint_proxy_add_listener(endpoint->proxy, + &endpoint->endpoint_listener, + &endpoint_events, endpoint); + + debug("%p added endpoint: %u", endpoint, id); + + pw_array_add_ptr(&self->endpoints, endpoint); + advance_endpoint_state(endpoint); +} + + +static const struct pw_registry_proxy_events registry_events = { + PW_VERSION_REGISTRY_PROXY_EVENTS, + .global = registry_event_global, +}; + +static void +on_remote_state_changed(void *data, enum pw_remote_state old, + enum pw_remote_state state, const char *error) +{ + struct audiomixer *self = data; + + if (state == PW_REMOTE_STATE_CONNECTED) { + self->core_proxy = pw_remote_get_core_proxy(self->remote); + self->registry_proxy = pw_core_proxy_get_registry( + self->core_proxy, + PW_TYPE_INTERFACE_Registry, + PW_VERSION_REGISTRY, 0); + pw_registry_proxy_add_listener(self->registry_proxy, + &self->registry_listener, + ®istry_events, self); + } + + pw_thread_loop_signal(self->main_loop, false); +} + +static const struct pw_remote_events remote_events = { + PW_VERSION_REMOTE_EVENTS, + .state_changed = on_remote_state_changed, +}; + +struct audiomixer * +audiomixer_new(void) +{ + struct audiomixer *self; + struct pw_loop *loop; + + pw_init(NULL, NULL); + + self = calloc(1, sizeof(struct audiomixer)); + loop = pw_loop_new(NULL); + self->main_loop = pw_thread_loop_new(loop, "audiomixer-loop"); + self->core = pw_core_new(loop, NULL, 0); + self->remote = pw_remote_new(self->core, NULL, 0); + pw_array_init(&self->endpoints, 1 * sizeof(void*)); + pw_array_init(&self->all_mixer_controls, 8 * sizeof(void*)); + + pw_module_load(self->core, "libpipewire-module-endpoint", NULL, NULL, + NULL, NULL); + pw_thread_loop_start(self->main_loop); + + return self; +} + +void +audiomixer_free(struct audiomixer *self) +{ + struct pw_loop *loop; + + pw_thread_loop_lock(self->main_loop); + self->events = NULL; + self->events_data = NULL; + pw_remote_disconnect(self->remote); + pw_thread_loop_unlock(self->main_loop); + pw_thread_loop_stop(self->main_loop); + + pw_array_clear(&self->endpoints); + pw_array_clear(&self->all_mixer_controls); + pw_remote_destroy(self->remote); + pw_core_destroy(self->core); + + loop = pw_thread_loop_get_loop(self->main_loop); + pw_thread_loop_destroy(self->main_loop); + pw_loop_destroy(loop); + + free(self); +} + +void +audiomixer_lock(struct audiomixer *self) +{ + pw_thread_loop_lock(self->main_loop); +} + +void +audiomixer_unlock(struct audiomixer *self) +{ + pw_thread_loop_unlock(self->main_loop); +} + +int +audiomixer_ensure_connected(struct audiomixer *self, int timeout_sec) +{ + enum pw_remote_state state; + int res; + + state = pw_remote_get_state(self->remote, NULL); + if (state == PW_REMOTE_STATE_CONNECTED) + return 0; + + if ((res = pw_remote_connect(self->remote)) < 0) + return res; + + while (true) { + state = pw_remote_get_state(self->remote, NULL); + if (state == PW_REMOTE_STATE_CONNECTED) + return 0; + else if (state == PW_REMOTE_STATE_ERROR) + return -EIO; + + if (pw_thread_loop_timed_wait(self->main_loop, timeout_sec) != 0) + return -ETIMEDOUT; + } +} + +int +audiomixer_ensure_controls(struct audiomixer *self, int timeout_sec) +{ + while (pw_array_get_len(&self->all_mixer_controls, void*) == 0) { + if (pw_thread_loop_timed_wait(self->main_loop, timeout_sec) != 0) + return -ETIMEDOUT; + } + return 0; +} + +const struct mixer_control ** +audiomixer_get_active_controls(struct audiomixer *self, + unsigned int *n_controls) +{ + const struct mixer_control **ret; + + *n_controls = pw_array_get_len(&self->all_mixer_controls, void*); + ret = (const struct mixer_control **) + pw_array_first(&self->all_mixer_controls); + + return ret; +} + +const struct mixer_control * +audiomixer_find_control(struct audiomixer *self, const char *name) +{ + struct mixer_control **ctlptr; + + pw_array_for_each(ctlptr, &self->all_mixer_controls) { + if (!strcmp((*ctlptr)->name, name)) { + return *ctlptr; + } + } + return NULL; +} + +void +audiomixer_add_event_listener(struct audiomixer *self, + const struct audiomixer_events *events, + void *data) +{ + self->events = events; + self->events_data = data; +} + +void +audiomixer_change_volume(struct audiomixer *self, + const struct mixer_control *control, + double volume) +{ + const struct mixer_control_impl *impl = + (const struct mixer_control_impl *) control; + struct endpoint *endpoint = impl->endpoint; + char buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 1024); + struct spa_pod *param; + + param = spa_pod_builder_add_object(&b, + PW_ENDPOINT_OBJECT_ParamControl, PW_ENDPOINT_PARAM_Control, + PW_ENDPOINT_PARAM_CONTROL_id, SPA_POD_Int(impl->volume_control_id), + PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Double(volume), + NULL); + pw_endpoint_proxy_set_param(endpoint->proxy, + PW_ENDPOINT_PARAM_Control, 0, param); +} + +void +audiomixer_change_mute(struct audiomixer *self, + const struct mixer_control *control, + bool mute) +{ + const struct mixer_control_impl *impl = + (const struct mixer_control_impl *) control; + struct endpoint *endpoint = impl->endpoint; + char buffer[1024]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 1024); + struct spa_pod *param; + + param = spa_pod_builder_add_object(&b, + PW_ENDPOINT_OBJECT_ParamControl, PW_ENDPOINT_PARAM_Control, + PW_ENDPOINT_PARAM_CONTROL_id, SPA_POD_Int(impl->mute_control_id), + PW_ENDPOINT_PARAM_CONTROL_value, SPA_POD_Bool(mute), + NULL); + pw_endpoint_proxy_set_param(endpoint->proxy, + PW_ENDPOINT_PARAM_Control, 0, param); +} diff --git a/binding/audiomixer.h b/binding/audiomixer.h new file mode 100644 index 0000000..f4e83c7 --- /dev/null +++ b/binding/audiomixer.h @@ -0,0 +1,60 @@ +/* + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include + +struct audiomixer; + +struct mixer_control +{ + char name[32]; + double volume; + bool mute; +}; + +struct audiomixer_events +{ + void (*controls_changed) (void *data); + + void (*value_changed) (void *data, +#define MIXER_CONTROL_CHANGE_FLAG_VOLUME (1<<0) +#define MIXER_CONTROL_CHANGE_FLAG_MUTE (1<<1) + unsigned int change_mask, + const struct mixer_control *control); +}; + +struct audiomixer * audiomixer_new(void); +void audiomixer_free(struct audiomixer *self); + +/* locking is required to call any of the methods below + * and to access any structure maintained by audiomixer */ +void audiomixer_lock(struct audiomixer *self); +void audiomixer_unlock(struct audiomixer *self); + +int audiomixer_ensure_connected(struct audiomixer *self, int timeout_sec); +int audiomixer_ensure_controls(struct audiomixer *self, int timeout_sec); + +const struct mixer_control ** audiomixer_get_active_controls( + struct audiomixer *self, + unsigned int *n_controls); + +const struct mixer_control * audiomixer_find_control( + struct audiomixer *self, + const char *name); + +void audiomixer_add_event_listener(struct audiomixer *self, + const struct audiomixer_events *events, + void *data); + +void audiomixer_change_volume(struct audiomixer *self, + const struct mixer_control *control, + double volume); + +void audiomixer_change_mute(struct audiomixer *self, + const struct mixer_control *control, + bool mute); + -- cgit 1.2.3-korg