Commit 7b22461b authored by Julian Bouzas's avatar Julian Bouzas
Browse files

m-endpoint-creation: add ACP endpoint creation

parent 307a0ca4
Pipeline #228739 passed with stages
in 53 seconds
......@@ -119,6 +119,7 @@ shared_library(
[
'module-endpoint-creation/generic-creation.c',
'module-endpoint-creation/limited-creation.c',
'module-endpoint-creation/limited-creation-acp.c',
'module-endpoint-creation/limited-creation-bluez5.c',
'module-endpoint-creation/parser-endpoint.c',
'module-endpoint-creation/parser-streams.c',
......
......@@ -12,6 +12,7 @@
#include "module-endpoint-creation/generic-creation.h"
#include "module-endpoint-creation/limited-creation.h"
#include "module-endpoint-creation/limited-creation-acp.h"
#include "module-endpoint-creation/limited-creation-bluez5.h"
struct _WpEndpointCreation
......@@ -47,6 +48,11 @@ static WpLimitedCreation *
create_device_limited_creation (WpEndpointCreation *self, WpProxy *device)
{
const gchar *device_api = wp_proxy_get_property (device, PW_KEY_DEVICE_API);
const gchar *acp = wp_proxy_get_property (device, "device.api.alsa.acp");
/* ACP */
if (g_strcmp0 (device_api, "alsa") == 0 && acp && atoi (acp))
return wp_limited_creation_acp_new (WP_DEVICE (device));
/* Bluez5 */
if (g_strcmp0 (device_api, "bluez5") == 0)
......
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <spa/utils/keys.h>
#include "limited-creation-acp.h"
G_DEFINE_QUARK (wp-limited-creation-acp-monitor, acpmon);
struct _WpLimitedCreationAcp
{
WpLimitedCreation parent;
GSource *timeout_source;
GHashTable *endpoints;
GPtrArray *profiles;
};
G_DEFINE_TYPE (WpLimitedCreationAcp, wp_limited_creation_acp,
WP_TYPE_LIMITED_CREATION)
static void endpoint_activate_finish_cb (WpSessionItem * ep, GAsyncResult * res,
WpLimitedCreationAcp * self);
static void
endpoint_export_finish_cb (WpSessionItem * ep, GAsyncResult * res,
WpLimitedCreationAcp * self)
{
g_autoptr (GError) error = NULL;
g_autoptr (GObject) node = NULL;
WpSessionItem * monitor = NULL;
/* Finish */
if (!wp_session_item_export_finish (ep, res, &error)) {
wp_warning_object (self, "failed to export endpoint: %s", error->message);
return;
}
/* Activate the monitor if any */
node = wp_session_item_get_associated_proxy (ep, WP_TYPE_NODE);
if (node)
monitor = g_object_get_qdata (node, acpmon_quark ());
if (monitor && !(wp_session_item_get_flags (monitor) & WP_SI_FLAG_ACTIVE))
wp_session_item_activate (monitor,
(GAsyncReadyCallback) endpoint_activate_finish_cb, self);
/* Notify endpoint created */
wp_endpoint_creation_notify_endpoint_created (WP_LIMITED_CREATION (self), ep);
}
static void
endpoint_activate_finish_cb (WpSessionItem * ep, GAsyncResult * res,
WpLimitedCreationAcp * self)
{
g_autoptr (GError) error = NULL;
/* Finish */
if (!wp_session_item_activate_finish (ep, res, &error)) {
wp_warning_object (self, "failed to activate endpoint: %s", error->message);
return;
}
/* Only export if not already exported */
if (!(wp_session_item_get_flags (ep) & WP_SI_FLAG_EXPORTED)) {
g_autoptr (WpSession) session = wp_limited_creation_lookup_session (
WP_LIMITED_CREATION (self), WP_CONSTRAINT_TYPE_PW_PROPERTY,
"session.name", "=s", "audio", NULL);
if (!session) {
wp_warning_object (self, "could not find audio session for endpoint");
return;
}
wp_session_item_export (ep, session,
(GAsyncReadyCallback) endpoint_export_finish_cb, self);
}
}
static void
enable_endpoint (WpLimitedCreationAcp *self, guint id, WpNode *node,
guint32 priority)
{
g_autoptr (WpDevice) device =
wp_limited_creation_get_device (WP_LIMITED_CREATION (self));
g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (device));
WpSessionItem *ep = NULL;
g_autoptr (WpSessionItem) adapter = NULL;
const gchar *media_class = NULL;
wp_info_object (self, "activating endpoint for card profile device %d", id);
/* Create audio softdsp endpoint if not found */
g_return_if_fail (self->endpoints);
ep = g_hash_table_lookup (self->endpoints, GUINT_TO_POINTER (id));
if (!ep) {
ep = wp_session_item_make (core, "si-audio-softdsp-endpoint");
g_hash_table_insert (self->endpoints, GUINT_TO_POINTER (id),
g_object_ref (ep));
}
g_return_if_fail (ep);
/* Create adapter endpoint */
adapter = wp_session_item_make (core, "si-adapter");
g_return_if_fail (adapter);
/* Configure adapter endpoint */
{
const gchar *str = NULL;
g_auto (GVariantBuilder) b =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "node",
g_variant_new_uint64 ((guint64) node));
str = wp_proxy_get_property (WP_PROXY (node), "device.profile.name");
g_variant_builder_add (&b, "{sv}", "name",
g_variant_new_string (str));
str = wp_proxy_get_property (WP_PROXY (node), "device.profile.description");
g_variant_builder_add (&b, "{sv}", "role",
g_variant_new_string (str));
g_variant_builder_add (&b, "{sv}", "priority",
g_variant_new_uint32 (priority));
g_variant_builder_add (&b, "{sv}", "enable-control-port",
g_variant_new_boolean (FALSE));
g_variant_builder_add (&b, "{sv}", "enable-monitor",
g_variant_new_boolean (TRUE));
str = wp_proxy_get_property (WP_PROXY (node), PW_KEY_AUDIO_CHANNELS);
g_variant_builder_add (&b, "{sv}", "preferred-n-channels",
g_variant_new_uint32 (str ? atoi (str) : 0));
g_return_if_fail (wp_session_item_configure (adapter,
g_variant_builder_end (&b)));
}
/* Configure audio softdsp endpoint */
{
g_auto (GVariantBuilder) b =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "adapter",
g_variant_new_uint64 ((guint64) adapter));
wp_session_item_configure (ep, g_variant_builder_end (&b));
}
/* Add the streams if any */
for (guint i = 0; self->profiles && i < self->profiles->len; i++) {
const gchar *profile_name = g_ptr_array_index (self->profiles, i);
g_autoptr (WpSessionItem) stream =
wp_session_item_make (core, "si-convert");
{
g_auto (GVariantBuilder) b =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
g_variant_builder_add (&b, "{sv}", "target",
g_variant_new_uint64 ((guint64) adapter));
g_variant_builder_add (&b, "{sv}", "name",
g_variant_new_string (profile_name));
g_variant_builder_add (&b, "{sv}", "enable-control-port",
g_variant_new_boolean (FALSE));
wp_session_item_configure (stream, g_variant_builder_end (&b));
}
wp_session_bin_add (WP_SESSION_BIN (ep), g_steal_pointer (&stream));
}
/* Create monitor endpoint if input direction */
media_class = wp_proxy_get_property (WP_PROXY (node), PW_KEY_MEDIA_CLASS);
if (g_strcmp0 (media_class, "Audio/Sink") == 0) {
g_autoptr (WpSessionItem) monitor_ep =
wp_session_item_make (core, "si-monitor-endpoint");
g_auto (GVariantBuilder) b =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
g_return_if_fail (monitor_ep);
g_variant_builder_add (&b, "{sv}", "adapter",
g_variant_new_uint64 ((guint64) adapter));
wp_session_item_configure (monitor_ep, g_variant_builder_end (&b));
/* Keep a reference in the endpoint */
g_object_set_qdata_full (G_OBJECT (node),
acpmon_quark (), g_steal_pointer (&monitor_ep), g_object_unref);
}
/* Activate endpoint */
wp_session_item_activate (ep,
(GAsyncReadyCallback) endpoint_activate_finish_cb, self);
}
static void
disable_endpoints_foreach (gpointer key, gpointer value, gpointer user_data)
{
WpSessionItem *ep = value;
if (ep) {
wp_session_item_deactivate (ep);
wp_session_item_reset (ep);
}
}
static gboolean
timeout_reconfigure_callback (WpLimitedCreationAcp *self)
{
g_autoptr (WpIterator) nodes = NULL;
g_auto (GValue) item = G_VALUE_INIT;
/* Disable all endpoints */
g_hash_table_foreach (self->endpoints, disable_endpoints_foreach, self);
/* Only enable endpoints for the current nodes */
nodes = wp_limited_creation_iterate_nodes (WP_LIMITED_CREATION (self));
for (; wp_iterator_next (nodes, &item); g_value_unset (&item)) {
WpNode *n = g_value_get_object (&item);
const gchar *id = NULL;
/* Get the card profile device id */
id = wp_proxy_get_property (WP_PROXY (n), "card.profile.device");
if (!id) {
wp_warning_object (self, "node %p does not have card profile device", n);
continue;
}
/* Enable endpoint */
enable_endpoint (self, atoi (id), n, 20);
}
return G_SOURCE_REMOVE;
}
static void
wp_limited_creation_acp_nodes_changed (WpLimitedCreation * lc)
{
WpLimitedCreationAcp *self = WP_LIMITED_CREATION_ACP (lc);
g_autoptr (WpDevice) device =
wp_limited_creation_get_device (WP_LIMITED_CREATION (self));
g_autoptr (WpCore) core = wp_proxy_get_core (WP_PROXY (device));
/* The changed callback can be triggered multiple times if the nodes are
* destroyed and created again. This happens a lot when the profiles change
* quickly (eg when the ACP device is created and restored with a different
* profile). Therefore, a timeout callback is added to avoid reconfiguring
* endpoints with the old profile if the profile changes again in less than 1
* second */
/* Clear the current timeout callback */
if (self->timeout_source)
g_source_destroy (self->timeout_source);
g_clear_pointer (&self->timeout_source, g_source_unref);
/* Add a new timeout callback */
wp_core_timeout_add_closure (core, &self->timeout_source, 1000,
g_cclosure_new_object (G_CALLBACK (timeout_reconfigure_callback),
G_OBJECT (self)));
}
static void
on_device_enum_profile_done (WpProxy *proxy, GAsyncResult *res,
WpLimitedCreationAcp *self)
{
g_autoptr (WpIterator) profiles = NULL;
g_auto (GValue) item = G_VALUE_INIT;
g_autoptr (GError) error = NULL;
/* Finish */
profiles = wp_proxy_enum_params_finish (proxy, res, &error);
if (error) {
wp_warning_object (self, "failed to get profile in ACP device");
return;
}
/* Reset profiles */
g_ptr_array_set_size (self->profiles, 0);
/* Iterate all profiles */
for (; wp_iterator_next (profiles, &item); g_value_unset (&item)) {
WpSpaPod *pod = g_value_get_boxed (&item);
gint index = 0;
const gchar *name = NULL;
const gchar *desc = NULL;
gint priority = 0;
enum spa_param_availability available = SPA_PARAM_AVAILABILITY_unknown;
g_return_if_fail (pod);
g_return_if_fail (wp_spa_pod_is_object (pod));
/* Parse profile */
{
g_autoptr (WpSpaPodParser) pp = wp_spa_pod_parser_new_object (pod,
"Profile", NULL);
g_return_if_fail (pp);
g_return_if_fail (wp_spa_pod_parser_get (pp, "index", "i", &index, NULL));
if (index == 0) {
/* Skip profile 0 (Off) */
wp_spa_pod_parser_end (pp);
continue;
}
g_return_if_fail (wp_spa_pod_parser_get (pp, "name", "s", &name, NULL));
g_return_if_fail (wp_spa_pod_parser_get (pp, "description", "s", &desc, NULL));
g_return_if_fail (wp_spa_pod_parser_get (pp, "priority", "i", &priority, NULL));
g_return_if_fail (wp_spa_pod_parser_get (pp, "available", "I", &available, NULL));
wp_spa_pod_parser_end (pp);
}
/* Add the profile only if it is available */
if (available == SPA_PARAM_AVAILABILITY_yes)
g_ptr_array_add (self->profiles, g_strdup (name));
}
}
static void
wp_limited_creation_acp_constructed (GObject *object)
{
WpLimitedCreationAcp *self = WP_LIMITED_CREATION_ACP (object);
g_autoptr (WpDevice) device = wp_limited_creation_get_device (
WP_LIMITED_CREATION (self));
g_return_if_fail (device);
/* Init endpoints table and profiles array */
self->endpoints = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, g_object_unref);
self->profiles = g_ptr_array_new_with_free_func (g_free);
wp_proxy_enum_params (WP_PROXY (device), "EnumProfile", NULL, NULL,
(GAsyncReadyCallback) on_device_enum_profile_done, self);
G_OBJECT_CLASS (wp_limited_creation_acp_parent_class)->constructed (object);
}
static void
wp_limited_creation_acp_finalize (GObject * object)
{
WpLimitedCreationAcp *self = WP_LIMITED_CREATION_ACP (object);
/* Clear the current timeout callback */
if (self->timeout_source)
g_source_destroy (self->timeout_source);
g_clear_pointer (&self->timeout_source, g_source_unref);
g_clear_pointer (&self->endpoints, g_hash_table_unref);
g_clear_pointer (&self->profiles, g_ptr_array_unref);
G_OBJECT_CLASS (wp_limited_creation_acp_parent_class)->finalize (object);
}
static void
wp_limited_creation_acp_init (WpLimitedCreationAcp *self)
{
}
static void
wp_limited_creation_acp_class_init (WpLimitedCreationAcpClass *klass)
{
GObjectClass *object_class = (GObjectClass *) klass;
WpLimitedCreationClass *parent_class = (WpLimitedCreationClass *) klass;
object_class->constructed = wp_limited_creation_acp_constructed;
object_class->finalize = wp_limited_creation_acp_finalize;
parent_class->nodes_changed = wp_limited_creation_acp_nodes_changed;
}
WpLimitedCreation *
wp_limited_creation_acp_new (WpDevice *device)
{
return g_object_new (wp_limited_creation_acp_get_type (),
"device", device,
NULL);
}
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author Julian Bouzas <julian.bouzas@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WIREPLUMBER_LIMITED_CREATION_ACP_H__
#define __WIREPLUMBER_LIMITED_CREATION_ACP_H__
#include <wp/wp.h>
#include "limited-creation.h"
G_BEGIN_DECLS
#define WP_TYPE_LIMITED_CREATION_ACP (wp_limited_creation_acp_get_type ())
G_DECLARE_FINAL_TYPE (WpLimitedCreationAcp, wp_limited_creation_acp, WP,
LIMITED_CREATION_ACP, WpLimitedCreation);
WpLimitedCreation * wp_limited_creation_acp_new (WpDevice *device);
G_END_DECLS
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment