2019-08-13 14:18:34 -04:00
|
|
|
|
/* WirePlumber
|
|
|
|
|
|
*
|
|
|
|
|
|
* Copyright © 2019 Collabora Ltd.
|
|
|
|
|
|
* @author Julian Bouzas <julian.bouzas@collabora.com>
|
|
|
|
|
|
*
|
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <pipewire/pipewire.h>
|
|
|
|
|
|
#include <spa/utils/names.h>
|
|
|
|
|
|
#include <spa/param/audio/format-utils.h>
|
|
|
|
|
|
#include <spa/pod/builder.h>
|
|
|
|
|
|
#include <spa/param/props.h>
|
|
|
|
|
|
|
|
|
|
|
|
#include "convert.h"
|
2019-10-09 17:51:24 +03:00
|
|
|
|
#include "../algorithms.h"
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
|
PROP_0,
|
|
|
|
|
|
PROP_TARGET,
|
2019-09-25 11:58:02 +02:00
|
|
|
|
PROP_FORMAT,
|
2019-08-13 14:18:34 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct _WpAudioConvert
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioStream parent;
|
|
|
|
|
|
|
|
|
|
|
|
/* Props */
|
2019-10-02 21:27:44 +03:00
|
|
|
|
WpAudioStream *target;
|
2019-09-25 11:58:02 +02:00
|
|
|
|
struct spa_audio_info_raw format;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
/* Proxies */
|
2019-10-02 21:27:44 +03:00
|
|
|
|
GPtrArray *link_proxies;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static GAsyncInitableIface *wp_audio_convert_parent_interface = NULL;
|
|
|
|
|
|
static void wp_audio_convert_async_initable_init (gpointer iface,
|
|
|
|
|
|
gpointer iface_data);
|
|
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (WpAudioConvert, wp_audio_convert, WP_TYPE_AUDIO_STREAM,
|
|
|
|
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
|
|
|
|
|
|
wp_audio_convert_async_initable_init))
|
|
|
|
|
|
|
2019-10-02 21:27:44 +03:00
|
|
|
|
static void
|
|
|
|
|
|
create_link_cb (WpProperties *props, gpointer user_data)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioConvert *self = WP_AUDIO_CONVERT (user_data);
|
|
|
|
|
|
g_autoptr (WpCore) core = NULL;
|
|
|
|
|
|
WpProxy *proxy;
|
|
|
|
|
|
|
|
|
|
|
|
core = wp_audio_stream_get_core (WP_AUDIO_STREAM (self));
|
|
|
|
|
|
g_return_if_fail (core);
|
|
|
|
|
|
|
|
|
|
|
|
/* make the link passive, which means it will not keep
|
|
|
|
|
|
the audioconvert node in the running state if the number of non-passive
|
|
|
|
|
|
links (i.e. the ones linking another endpoint to this one) drops to 0 */
|
|
|
|
|
|
wp_properties_set (props, PW_KEY_LINK_PASSIVE, "1");
|
|
|
|
|
|
|
|
|
|
|
|
/* Create the link */
|
|
|
|
|
|
proxy = wp_core_create_remote_object(core, "link-factory",
|
2020-01-09 12:39:45 -05:00
|
|
|
|
PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, props);
|
2019-10-02 21:27:44 +03:00
|
|
|
|
g_return_if_fail (proxy);
|
|
|
|
|
|
g_ptr_array_add(self->link_proxies, proxy);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-08-13 14:18:34 -04:00
|
|
|
|
static void
|
2019-08-29 21:21:33 +03:00
|
|
|
|
on_audio_convert_running(WpAudioConvert *self)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
2019-10-02 21:27:44 +03:00
|
|
|
|
g_autoptr (GVariant) src_props = NULL;
|
|
|
|
|
|
g_autoptr (GVariant) sink_props = NULL;
|
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
enum pw_direction direction =
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_audio_stream_get_direction (WP_AUDIO_STREAM (self));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-10-02 21:27:44 +03:00
|
|
|
|
g_debug ("%p linking audio convert to target", self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
if (direction == PW_DIRECTION_INPUT) {
|
2019-10-02 21:27:44 +03:00
|
|
|
|
wp_audio_stream_prepare_link (WP_AUDIO_STREAM (self), &src_props, &error);
|
|
|
|
|
|
wp_audio_stream_prepare_link (self->target, &sink_props, &error);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
} else {
|
2019-10-02 21:27:44 +03:00
|
|
|
|
wp_audio_stream_prepare_link (self->target, &src_props, &error);
|
|
|
|
|
|
wp_audio_stream_prepare_link (WP_AUDIO_STREAM (self), &sink_props, &error);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2019-10-02 21:27:44 +03:00
|
|
|
|
multiport_link_create (src_props, sink_props, create_link_cb, self, &error);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_audio_convert_event_info (WpProxyNode * proxy, GParamSpec *spec,
|
|
|
|
|
|
WpAudioConvert * self)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
2019-08-29 21:21:33 +03:00
|
|
|
|
const struct pw_node_info *info = wp_proxy_node_get_info (proxy);
|
|
|
|
|
|
|
|
|
|
|
|
/* Handle the different states */
|
|
|
|
|
|
switch (info->state) {
|
|
|
|
|
|
case PW_NODE_STATE_IDLE:
|
2019-10-02 21:27:44 +03:00
|
|
|
|
g_ptr_array_set_size (self->link_proxies, 0);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
break;
|
|
|
|
|
|
case PW_NODE_STATE_RUNNING:
|
|
|
|
|
|
on_audio_convert_running (self);
|
|
|
|
|
|
break;
|
|
|
|
|
|
case PW_NODE_STATE_SUSPENDED:
|
|
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2019-08-29 21:21:33 +03:00
|
|
|
|
on_audio_convert_proxy_done (WpProxy *proxy, GAsyncResult *res,
|
|
|
|
|
|
WpAudioConvert *self)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_autoptr (GError) error = NULL;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
enum pw_direction direction =
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_audio_stream_get_direction (WP_AUDIO_STREAM (self));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
uint8_t buf[1024];
|
|
|
|
|
|
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
|
2019-10-02 21:27:44 +03:00
|
|
|
|
struct spa_pod *format;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
struct spa_pod *param;
|
|
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_proxy_sync_finish (proxy, res, &error);
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
g_message("WpAudioConvert:%p initial sync failed: %s", self, error->message);
|
|
|
|
|
|
wp_audio_stream_init_task_finish (WP_AUDIO_STREAM (self),
|
|
|
|
|
|
g_steal_pointer (&error));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
return;
|
2019-08-29 21:21:33 +03:00
|
|
|
|
}
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_debug ("%s:%p setting format", G_OBJECT_TYPE_NAME (self), self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-10-02 21:27:44 +03:00
|
|
|
|
format = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format,
|
|
|
|
|
|
&self->format);
|
|
|
|
|
|
|
|
|
|
|
|
/* Configure audioconvert to be both merger and splitter; this means it will
|
|
|
|
|
|
have an equal number of input and output ports and just passthrough the
|
|
|
|
|
|
same format, but with altered volume.
|
|
|
|
|
|
In the future we need to consider writing a simpler volume node for this,
|
|
|
|
|
|
as doing merge + split is heavy for our needs */
|
2019-08-13 14:18:34 -04:00
|
|
|
|
param = spa_pod_builder_add_object(&pod_builder,
|
|
|
|
|
|
SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
|
2019-10-02 21:27:44 +03:00
|
|
|
|
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(pw_direction_reverse(direction)),
|
2019-08-13 14:18:34 -04:00
|
|
|
|
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
|
2019-10-02 21:27:44 +03:00
|
|
|
|
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(format));
|
|
|
|
|
|
wp_audio_stream_set_port_config (WP_AUDIO_STREAM (self), param);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-10-02 21:27:44 +03:00
|
|
|
|
param = spa_pod_builder_add_object(&pod_builder,
|
|
|
|
|
|
SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
|
|
|
|
|
|
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction),
|
|
|
|
|
|
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
|
|
|
|
|
|
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(format));
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_audio_stream_set_port_config (WP_AUDIO_STREAM (self), param);
|
2019-10-02 21:27:44 +03:00
|
|
|
|
wp_audio_stream_finish_port_config (WP_AUDIO_STREAM (self));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_convert_init_async (GAsyncInitable *initable, int io_priority,
|
|
|
|
|
|
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioConvert *self = WP_AUDIO_CONVERT (initable);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_autoptr (WpProxy) proxy = NULL;
|
|
|
|
|
|
g_autoptr (WpProperties) props = NULL;
|
2019-09-07 17:55:46 +03:00
|
|
|
|
g_autoptr (WpCore) core = wp_audio_stream_get_core (WP_AUDIO_STREAM (self));
|
2019-10-02 21:27:44 +03:00
|
|
|
|
WpProxyNode *node;
|
2019-08-29 21:21:33 +03:00
|
|
|
|
|
|
|
|
|
|
/* Create the properties */
|
2019-10-02 21:27:44 +03:00
|
|
|
|
node = wp_audio_stream_get_proxy_node (self->target);
|
|
|
|
|
|
props = wp_properties_copy (wp_proxy_node_get_properties (node));
|
|
|
|
|
|
|
2019-10-07 16:57:06 +03:00
|
|
|
|
wp_properties_setf (props, PW_KEY_OBJECT_PATH, "%s:%s",
|
|
|
|
|
|
wp_properties_get(props, PW_KEY_OBJECT_PATH),
|
|
|
|
|
|
wp_audio_stream_get_name (WP_AUDIO_STREAM (self)));
|
2019-10-02 21:27:44 +03:00
|
|
|
|
wp_properties_setf (props, PW_KEY_NODE_NAME, "%s/%s/%s",
|
|
|
|
|
|
SPA_NAME_AUDIO_CONVERT,
|
|
|
|
|
|
wp_properties_get(props, PW_KEY_NODE_NAME),
|
2019-08-29 21:21:33 +03:00
|
|
|
|
wp_audio_stream_get_name (WP_AUDIO_STREAM (self)));
|
|
|
|
|
|
wp_properties_set (props, PW_KEY_MEDIA_CLASS, "Audio/Convert");
|
|
|
|
|
|
wp_properties_set (props, "factory.name", SPA_NAME_AUDIO_CONVERT);
|
|
|
|
|
|
|
|
|
|
|
|
/* Create the proxy */
|
2019-09-07 17:55:46 +03:00
|
|
|
|
proxy = wp_core_create_remote_object (core, "spa-node-factory",
|
2020-01-09 12:39:45 -05:00
|
|
|
|
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, props);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_return_if_fail (proxy);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_object_set (self, "proxy-node", proxy, NULL);
|
|
|
|
|
|
g_signal_connect_object (proxy, "notify::info",
|
|
|
|
|
|
(GCallback) wp_audio_convert_event_info, self, 0);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
/* Call the parent interface */
|
|
|
|
|
|
wp_audio_convert_parent_interface->init_async (initable, io_priority,
|
|
|
|
|
|
cancellable, callback, data);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
|
|
|
|
|
|
/* Register a callback to be called after all the initialization is done */
|
|
|
|
|
|
wp_proxy_sync (proxy, NULL,
|
|
|
|
|
|
(GAsyncReadyCallback) on_audio_convert_proxy_done, self);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_convert_async_initable_init (gpointer iface, gpointer iface_data)
|
|
|
|
|
|
{
|
|
|
|
|
|
GAsyncInitableIface *ai_iface = iface;
|
|
|
|
|
|
|
|
|
|
|
|
/* Set the parent interface */
|
|
|
|
|
|
wp_audio_convert_parent_interface = g_type_interface_peek_parent (iface);
|
|
|
|
|
|
|
|
|
|
|
|
ai_iface->init_async = wp_audio_convert_init_async;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_convert_set_property (GObject * object, guint property_id,
|
|
|
|
|
|
const GValue * value, GParamSpec * pspec)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioConvert *self = WP_AUDIO_CONVERT (object);
|
|
|
|
|
|
|
|
|
|
|
|
switch (property_id) {
|
|
|
|
|
|
case PROP_TARGET:
|
2019-08-29 21:21:33 +03:00
|
|
|
|
self->target = g_value_dup_object (value);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
break;
|
2019-09-25 11:58:02 +02:00
|
|
|
|
case PROP_FORMAT: {
|
|
|
|
|
|
const struct spa_audio_info_raw *f = g_value_get_pointer (value);
|
|
|
|
|
|
if (f)
|
|
|
|
|
|
self->format = *f;
|
|
|
|
|
|
else
|
|
|
|
|
|
g_warning ("WpAudioConvert:%p Format needs to be valid", self);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2019-08-13 14:18:34 -04:00
|
|
|
|
default:
|
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_convert_get_property (GObject * object, guint property_id,
|
|
|
|
|
|
GValue * value, GParamSpec * pspec)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioConvert *self = WP_AUDIO_CONVERT (object);
|
|
|
|
|
|
|
|
|
|
|
|
switch (property_id) {
|
|
|
|
|
|
case PROP_TARGET:
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_value_set_object (value, self->target);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
break;
|
2019-09-25 11:58:02 +02:00
|
|
|
|
case PROP_FORMAT:
|
|
|
|
|
|
g_value_set_pointer (value, &self->format);
|
|
|
|
|
|
break;
|
2019-08-13 14:18:34 -04:00
|
|
|
|
default:
|
|
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_convert_finalize (GObject * object)
|
|
|
|
|
|
{
|
|
|
|
|
|
WpAudioConvert *self = WP_AUDIO_CONVERT (object);
|
|
|
|
|
|
|
2019-10-02 21:27:44 +03:00
|
|
|
|
g_clear_pointer (&self->link_proxies, g_ptr_array_unref);
|
2019-08-29 21:21:33 +03:00
|
|
|
|
g_clear_object (&self->target);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (wp_audio_convert_parent_class)->finalize (object);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_convert_init (WpAudioConvert * self)
|
|
|
|
|
|
{
|
2019-10-02 21:27:44 +03:00
|
|
|
|
self->link_proxies = g_ptr_array_new_with_free_func (g_object_unref);
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
|
wp_audio_convert_class_init (WpAudioConvertClass * klass)
|
|
|
|
|
|
{
|
|
|
|
|
|
GObjectClass *object_class = (GObjectClass *) klass;
|
|
|
|
|
|
|
|
|
|
|
|
object_class->finalize = wp_audio_convert_finalize;
|
|
|
|
|
|
object_class->set_property = wp_audio_convert_set_property;
|
|
|
|
|
|
object_class->get_property = wp_audio_convert_get_property;
|
|
|
|
|
|
|
|
|
|
|
|
/* Install the properties */
|
|
|
|
|
|
g_object_class_install_property (object_class, PROP_TARGET,
|
2019-10-02 21:27:44 +03:00
|
|
|
|
g_param_spec_object ("target", "target", "The target stream",
|
|
|
|
|
|
WP_TYPE_AUDIO_STREAM,
|
2019-08-13 14:18:34 -04:00
|
|
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
2019-09-25 11:58:02 +02:00
|
|
|
|
g_object_class_install_property (object_class, PROP_FORMAT,
|
|
|
|
|
|
g_param_spec_pointer ("format", "format", "The accepted format",
|
|
|
|
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
2019-08-13 14:18:34 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2019-12-09 12:34:06 +02:00
|
|
|
|
wp_audio_convert_new (WpBaseEndpoint *endpoint, guint stream_id,
|
2019-08-13 14:18:34 -04:00
|
|
|
|
const char *stream_name, enum pw_direction direction,
|
2019-10-02 21:27:44 +03:00
|
|
|
|
WpAudioStream *target, const struct spa_audio_info_raw *format,
|
2019-09-25 11:58:02 +02:00
|
|
|
|
GAsyncReadyCallback callback, gpointer user_data)
|
2019-08-13 14:18:34 -04:00
|
|
|
|
{
|
|
|
|
|
|
g_async_initable_new_async (
|
|
|
|
|
|
WP_TYPE_AUDIO_CONVERT, G_PRIORITY_DEFAULT, NULL, callback, user_data,
|
|
|
|
|
|
"endpoint", endpoint,
|
|
|
|
|
|
"id", stream_id,
|
|
|
|
|
|
"name", stream_name,
|
|
|
|
|
|
"direction", direction,
|
|
|
|
|
|
"target", target,
|
2019-09-25 11:58:02 +02:00
|
|
|
|
"format", format,
|
2019-08-13 14:18:34 -04:00
|
|
|
|
NULL);
|
|
|
|
|
|
}
|