2020-03-17 23:03:10 +02:00
|
|
|
/* WirePlumber
|
|
|
|
|
*
|
|
|
|
|
* Copyright © 2020 Collabora Ltd.
|
|
|
|
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <wp/wp.h>
|
2020-05-04 11:23:50 +03:00
|
|
|
#include <pipewire/keys.h>
|
2020-05-19 11:17:52 -04:00
|
|
|
#include <pipewire/properties.h>
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2020-05-04 11:23:50 +03:00
|
|
|
#include <spa/param/format.h>
|
2020-03-17 23:03:10 +02:00
|
|
|
#include <spa/param/audio/raw.h>
|
2021-10-08 18:22:37 +03:00
|
|
|
#include <spa/param/audio/format-utils.h>
|
2020-05-04 11:23:50 +03:00
|
|
|
#include <spa/param/param.h>
|
2020-03-17 23:03:10 +02:00
|
|
|
|
log: implement a log topics system, like pipewire
The intention is to make checks for enabled log topics faster.
Every topic has its own structure that is statically defined in the file
where the logs are printed from. The structure is initialized transparently
when it is first used and it contains all the log level flags for the levels
that this topic should print messages. It is then checked on the wp_log()
macro before printing the message.
Topics from SPA/PipeWire are also handled natively, so messages are printed
directly without checking if the topic is enabled, since the PipeWire and SPA
macros do the checking themselves.
Messages coming from GLib are checked inside the handler.
An internal WpLogFields object is used to manage the state of each log
message, populating all the fields appropriately from the place they
are coming from (wp_log, spa_log, glib log), formatting the message and
then printing it. For printing to the journald, we still use the glib
message handler, converting all the needed fields to GLogField on demand.
That message handler does not do any checks for the topic or the level, so
we can just call it to send the message.
2023-05-16 11:51:29 +03:00
|
|
|
WP_DEFINE_LOCAL_LOG_TOPIC ("m-si-audio-adapter")
|
|
|
|
|
|
2021-03-18 14:47:22 -04:00
|
|
|
#define SI_FACTORY_NAME "si-audio-adapter"
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-03-18 14:47:22 -04:00
|
|
|
struct _WpSiAudioAdapter
|
2020-03-17 23:03:10 +02:00
|
|
|
{
|
|
|
|
|
WpSessionItem parent;
|
|
|
|
|
|
|
|
|
|
/* configuration */
|
|
|
|
|
WpNode *node;
|
2021-09-29 13:13:19 -04:00
|
|
|
WpPort *port; /* only used for passthrough or convert mode */
|
2021-10-21 13:04:33 -04:00
|
|
|
gboolean no_format;
|
2020-03-17 23:03:10 +02:00
|
|
|
gboolean control_port;
|
|
|
|
|
gboolean monitor;
|
2021-09-29 13:31:22 -04:00
|
|
|
gboolean disable_dsp;
|
2021-09-23 18:58:12 +03:00
|
|
|
WpDirection portconfig_direction;
|
2021-05-06 11:25:12 -04:00
|
|
|
gboolean is_device;
|
2021-06-07 09:56:54 +02:00
|
|
|
gboolean dont_remix;
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
/* This is used during linking to pick the preferred format.
|
|
|
|
|
* If the priority in both in- and output ports is the same,
|
|
|
|
|
* the format of the input port is picked. If the priority
|
|
|
|
|
* is not set, the default 0 is used.
|
|
|
|
|
* Furthermore, if the priority is greater than 0, then in
|
|
|
|
|
* si_audio_adapter_find_format (), a new format is always
|
|
|
|
|
* picked, and in si_audio_adapter_enable_active (), the
|
|
|
|
|
* adapter is configured immediately. This is because such
|
|
|
|
|
* priorities indicate that this adapter's format is
|
|
|
|
|
* preferred during negotiation.
|
|
|
|
|
* Priorities lower than 0 are invalid. */
|
|
|
|
|
gint format_preference_priority;
|
2021-06-07 10:29:25 +02:00
|
|
|
gboolean is_autoconnect;
|
2021-10-08 18:22:37 +03:00
|
|
|
gboolean have_encoded;
|
|
|
|
|
gboolean encoded_only;
|
|
|
|
|
gboolean is_unpositioned;
|
|
|
|
|
struct spa_audio_info_raw raw_format;
|
|
|
|
|
|
|
|
|
|
gulong ports_changed_sigid;
|
2023-11-22 10:05:46 -05:00
|
|
|
gulong params_changed_sigid;
|
2021-10-08 18:22:37 +03:00
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
WpSpaPod *format;
|
|
|
|
|
gchar mode[32];
|
|
|
|
|
GTask *format_task;
|
2022-05-03 10:17:11 -04:00
|
|
|
WpSiAdapterPortsState ports_state;
|
2020-03-17 23:03:10 +02:00
|
|
|
};
|
|
|
|
|
|
2021-04-29 18:56:49 -04:00
|
|
|
static void si_audio_adapter_linkable_init (WpSiLinkableInterface * iface);
|
2021-05-06 11:25:12 -04:00
|
|
|
static void si_audio_adapter_adapter_init (WpSiAdapterInterface * iface);
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-03-18 14:47:22 -04:00
|
|
|
G_DECLARE_FINAL_TYPE(WpSiAudioAdapter, si_audio_adapter, WP, SI_AUDIO_ADAPTER,
|
|
|
|
|
WpSessionItem)
|
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (WpSiAudioAdapter, si_audio_adapter,
|
|
|
|
|
WP_TYPE_SESSION_ITEM,
|
2021-05-06 11:25:12 -04:00
|
|
|
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_LINKABLE, si_audio_adapter_linkable_init)
|
|
|
|
|
G_IMPLEMENT_INTERFACE (WP_TYPE_SI_ADAPTER, si_audio_adapter_adapter_init))
|
2020-03-17 23:03:10 +02:00
|
|
|
|
|
|
|
|
static void
|
2021-03-18 14:47:22 -04:00
|
|
|
si_audio_adapter_init (WpSiAudioAdapter * self)
|
2020-03-17 23:03:10 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-03 10:17:11 -04:00
|
|
|
static void
|
|
|
|
|
si_audio_adapter_set_ports_state (WpSiAudioAdapter *self, WpSiAdapterPortsState
|
|
|
|
|
new_state)
|
|
|
|
|
{
|
|
|
|
|
if (self->ports_state != new_state) {
|
|
|
|
|
WpSiAdapterPortsState old_state = self->ports_state;
|
|
|
|
|
self->ports_state = new_state;
|
|
|
|
|
g_signal_emit_by_name (self, "adapter-ports-state-changed", old_state,
|
|
|
|
|
new_state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 12:44:32 +02:00
|
|
|
static void
|
|
|
|
|
si_audio_adapter_soft_reset (WpSiAudioAdapter *self)
|
|
|
|
|
{
|
|
|
|
|
/* deactivate first */
|
|
|
|
|
wp_object_deactivate (WP_OBJECT (self), WP_SESSION_ITEM_FEATURE_ACTIVE);
|
|
|
|
|
|
|
|
|
|
/* reset back to the initial "configured" state */
|
|
|
|
|
if (self->format_task) {
|
|
|
|
|
g_task_return_new_error (self->format_task, WP_DOMAIN_LIBRARY,
|
|
|
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"item deactivated before format was set");
|
|
|
|
|
g_clear_object (&self->format_task);
|
|
|
|
|
}
|
|
|
|
|
g_clear_pointer (&self->format, wp_spa_pod_unref);
|
|
|
|
|
self->mode[0] = '\0';
|
|
|
|
|
si_audio_adapter_set_ports_state (self, WP_SI_ADAPTER_PORTS_STATE_NONE);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-17 23:03:10 +02:00
|
|
|
static void
|
2021-03-18 14:47:22 -04:00
|
|
|
si_audio_adapter_reset (WpSessionItem * item)
|
2020-03-17 23:03:10 +02:00
|
|
|
{
|
2021-03-18 14:47:22 -04:00
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2024-03-12 12:44:32 +02:00
|
|
|
si_audio_adapter_soft_reset (self);
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
/* reset */
|
2020-04-09 19:24:42 +03:00
|
|
|
g_clear_object (&self->node);
|
2021-09-29 13:13:19 -04:00
|
|
|
g_clear_object (&self->port);
|
2021-10-21 13:04:33 -04:00
|
|
|
self->no_format = FALSE;
|
2020-03-17 23:03:10 +02:00
|
|
|
self->control_port = FALSE;
|
|
|
|
|
self->monitor = FALSE;
|
2021-09-29 13:31:22 -04:00
|
|
|
self->disable_dsp = FALSE;
|
2021-10-08 00:09:42 +03:00
|
|
|
self->portconfig_direction = WP_DIRECTION_INPUT;
|
2021-05-06 11:25:12 -04:00
|
|
|
self->is_device = FALSE;
|
2021-06-07 09:56:54 +02:00
|
|
|
self->dont_remix = FALSE;
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
self->format_preference_priority = 0;
|
2021-06-07 10:29:25 +02:00
|
|
|
self->is_autoconnect = FALSE;
|
2021-10-08 18:22:37 +03:00
|
|
|
self->have_encoded = FALSE;
|
|
|
|
|
self->encoded_only = FALSE;
|
|
|
|
|
spa_memzero (&self->raw_format, sizeof(struct spa_audio_info_raw));
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-03-18 14:47:22 -04:00
|
|
|
WP_SESSION_ITEM_CLASS (si_audio_adapter_parent_class)->reset (item);
|
2021-03-17 14:52:41 -04:00
|
|
|
}
|
|
|
|
|
|
2021-10-08 18:22:37 +03:00
|
|
|
static guint
|
|
|
|
|
si_audio_adapter_get_default_clock_rate (WpSiAudioAdapter * self)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self));
|
|
|
|
|
g_autoptr (WpProperties) props = NULL;
|
|
|
|
|
const gchar *rate_str = NULL;
|
|
|
|
|
g_return_val_if_fail (core, 48000);
|
|
|
|
|
props = wp_core_get_remote_properties (core);
|
|
|
|
|
g_return_val_if_fail (props, 48000);
|
|
|
|
|
rate_str = wp_properties_get (props, "default.clock.rate");
|
|
|
|
|
return rate_str ? atoi (rate_str) : 48000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
is_unpositioned (struct spa_audio_info_raw *info)
|
|
|
|
|
{
|
|
|
|
|
uint32_t i;
|
|
|
|
|
if (SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED))
|
|
|
|
|
return TRUE;
|
|
|
|
|
for (i = 0; i < info->channels; i++)
|
|
|
|
|
if (info->position[i] >= SPA_AUDIO_CHANNEL_START_Aux &&
|
|
|
|
|
info->position[i] <= SPA_AUDIO_CHANNEL_LAST_Aux)
|
|
|
|
|
return TRUE;
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
si_audio_adapter_find_format (WpSiAudioAdapter * self, WpNode * node)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpIterator) formats = NULL;
|
|
|
|
|
g_auto (GValue) value = G_VALUE_INIT;
|
|
|
|
|
gboolean have_format = FALSE;
|
|
|
|
|
|
|
|
|
|
formats = wp_pipewire_object_enum_params_sync (WP_PIPEWIRE_OBJECT (node),
|
|
|
|
|
"EnumFormat", NULL);
|
2021-10-18 14:33:01 -04:00
|
|
|
if (!formats)
|
|
|
|
|
return FALSE;
|
2021-10-08 18:22:37 +03:00
|
|
|
|
|
|
|
|
for (; wp_iterator_next (formats, &value); g_value_unset (&value)) {
|
|
|
|
|
WpSpaPod *pod = g_value_get_boxed (&value);
|
|
|
|
|
uint32_t mtype, msubtype;
|
|
|
|
|
|
|
|
|
|
if (!wp_spa_pod_is_object (pod)) {
|
|
|
|
|
wp_warning_object (self,
|
|
|
|
|
"non-object POD appeared on formats list; this node is buggy");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!wp_spa_pod_get_object (pod, NULL,
|
|
|
|
|
"mediaType", "I", &mtype,
|
|
|
|
|
"mediaSubtype", "I", &msubtype,
|
|
|
|
|
NULL)) {
|
|
|
|
|
wp_warning_object (self, "format does not have media type / subtype");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mtype != SPA_MEDIA_TYPE_audio)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
switch (msubtype) {
|
|
|
|
|
case SPA_MEDIA_SUBTYPE_raw: {
|
|
|
|
|
struct spa_audio_info_raw raw_format;
|
2021-10-13 16:41:31 +02:00
|
|
|
struct spa_pod *position = NULL;
|
2021-10-08 18:22:37 +03:00
|
|
|
wp_spa_pod_fixate (pod);
|
|
|
|
|
|
2021-10-13 16:41:31 +02:00
|
|
|
spa_zero(raw_format);
|
|
|
|
|
if (spa_pod_parse_object(wp_spa_pod_get_spa_pod (pod),
|
|
|
|
|
SPA_TYPE_OBJECT_Format, NULL,
|
2022-02-21 15:21:36 +01:00
|
|
|
SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&raw_format.format),
|
2021-10-13 16:41:31 +02:00
|
|
|
SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&raw_format.rate),
|
2022-02-21 15:21:36 +01:00
|
|
|
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&raw_format.channels),
|
2021-10-13 16:41:31 +02:00
|
|
|
SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)) < 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (position == NULL ||
|
|
|
|
|
!spa_pod_copy_array(position, SPA_TYPE_Id, raw_format.position, SPA_AUDIO_MAX_CHANNELS))
|
|
|
|
|
SPA_FLAG_SET(raw_format.flags, SPA_AUDIO_FLAG_UNPOSITIONED);
|
2021-10-08 18:22:37 +03:00
|
|
|
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
/* Only use this new format if it has _more_ channels than the old one
|
|
|
|
|
* to avoid unnecessary renegotiations and reconfigurations. By default,
|
|
|
|
|
* it is preferable to open too many channels than too few, since the
|
|
|
|
|
* extra channels can simply remain unused.
|
|
|
|
|
* Exception: when format_preference_priority is >0, do use the new format
|
|
|
|
|
* always. Such priorities indicate that the user wants the new format to
|
|
|
|
|
* be picked over the current format in all cases. */
|
|
|
|
|
if ((self->format_preference_priority > 0) ||
|
|
|
|
|
(self->raw_format.channels < raw_format.channels)) {
|
2021-10-08 18:22:37 +03:00
|
|
|
self->raw_format = raw_format;
|
|
|
|
|
if (is_unpositioned(&raw_format))
|
|
|
|
|
self->is_unpositioned = TRUE;
|
|
|
|
|
}
|
|
|
|
|
have_format = TRUE;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case SPA_MEDIA_SUBTYPE_iec958:
|
|
|
|
|
case SPA_MEDIA_SUBTYPE_dsd:
|
|
|
|
|
wp_info_object (self, "passthrough IEC958/DSD node %d found",
|
|
|
|
|
wp_proxy_get_bound_id (WP_PROXY (node)));
|
|
|
|
|
self->have_encoded = TRUE;
|
|
|
|
|
break;
|
2022-12-02 16:01:51 +01:00
|
|
|
default: {
|
|
|
|
|
enum spa_audio_format audio_format;
|
|
|
|
|
if (spa_pod_parse_object(wp_spa_pod_get_spa_pod (pod),
|
|
|
|
|
SPA_TYPE_OBJECT_Format, NULL,
|
|
|
|
|
SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&audio_format)) >= 0) {
|
|
|
|
|
self->have_encoded = (audio_format == SPA_AUDIO_FORMAT_ENCODED);
|
|
|
|
|
}
|
2021-10-08 18:22:37 +03:00
|
|
|
break;
|
|
|
|
|
}
|
2022-12-02 16:01:51 +01:00
|
|
|
}
|
2021-10-08 18:22:37 +03:00
|
|
|
}
|
|
|
|
|
if (!have_format && self->have_encoded) {
|
2022-12-02 16:01:51 +01:00
|
|
|
wp_info_object (self, ".. passthrough IEC958/DSD/encoded only");
|
2021-10-08 18:22:37 +03:00
|
|
|
self->encoded_only = TRUE;
|
|
|
|
|
have_format = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return have_format;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-22 16:12:04 +02:00
|
|
|
static void
|
|
|
|
|
on_proxy_destroyed (WpNode * proxy, WpSiAudioAdapter * self)
|
|
|
|
|
{
|
|
|
|
|
if (self->node == proxy) {
|
|
|
|
|
wp_object_abort_activation (WP_OBJECT (self), "proxy destroyed");
|
2024-03-12 12:44:32 +02:00
|
|
|
si_audio_adapter_soft_reset (self);
|
2021-12-22 16:12:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
static gboolean
|
2021-03-18 14:47:22 -04:00
|
|
|
si_audio_adapter_configure (WpSessionItem * item, WpProperties *p)
|
2021-03-17 14:52:41 -04:00
|
|
|
{
|
2021-03-18 14:47:22 -04:00
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
2021-03-17 14:52:41 -04:00
|
|
|
g_autoptr (WpProperties) si_props = wp_properties_ensure_unique_owner (p);
|
|
|
|
|
WpNode *node = NULL;
|
|
|
|
|
const gchar *str;
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
/* reset previous config */
|
2021-03-18 14:47:22 -04:00
|
|
|
si_audio_adapter_reset (item);
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, "item.node");
|
2021-03-17 14:52:41 -04:00
|
|
|
if (!str || sscanf(str, "%p", &node) != 1 || !WP_IS_NODE (node))
|
|
|
|
|
return FALSE;
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, PW_KEY_MEDIA_CLASS);
|
|
|
|
|
if (!str)
|
|
|
|
|
return FALSE;
|
|
|
|
|
if ((strstr (str, "Source") || strstr (str, "Output"))
|
|
|
|
|
&& !strstr (str, "Virtual")) {
|
|
|
|
|
self->portconfig_direction = WP_DIRECTION_OUTPUT;
|
2020-03-17 23:03:10 +02:00
|
|
|
}
|
|
|
|
|
|
2021-10-21 13:04:33 -04:00
|
|
|
str = wp_properties_get (si_props, "item.features.no-format");
|
|
|
|
|
self->no_format = str && pw_properties_parse_bool (str);
|
|
|
|
|
if (!self->no_format && !si_audio_adapter_find_format (self, node)) {
|
2023-05-18 16:19:49 +03:00
|
|
|
wp_notice_object (item, "no usable format found for node %d",
|
2021-10-08 18:22:37 +03:00
|
|
|
wp_proxy_get_bound_id (WP_PROXY (node)));
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, "item.features.control-port");
|
|
|
|
|
self->control_port = str && pw_properties_parse_bool (str);
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, "item.features.monitor");
|
|
|
|
|
self->monitor = str && pw_properties_parse_bool (str);
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, "item.features.no-dsp");
|
|
|
|
|
self->disable_dsp = str && pw_properties_parse_bool (str);
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, "item.node.type");
|
|
|
|
|
self->is_device = !g_strcmp0 (str, "device");
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, PW_KEY_STREAM_DONT_REMIX);
|
|
|
|
|
self->dont_remix = str && pw_properties_parse_bool (str);
|
|
|
|
|
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
str = wp_properties_get (si_props, "stream.format-preference-priority");
|
|
|
|
|
self->format_preference_priority = (str) ? pw_properties_parse_int (str) : 0;
|
|
|
|
|
if (self->format_preference_priority < 0) {
|
|
|
|
|
wp_critical_object (item, "stream.format-preference-priority value %d "
|
|
|
|
|
"is invalid (must be >= 0)", self->format_preference_priority);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
str = wp_properties_get (si_props, PW_KEY_NODE_AUTOCONNECT);
|
|
|
|
|
self->is_autoconnect = str && pw_properties_parse_bool (str);
|
2021-09-29 13:31:22 -04:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
self->node = g_object_ref (node);
|
2021-12-22 16:12:04 +02:00
|
|
|
g_signal_connect_object (self->node, "pw-proxy-destroyed",
|
|
|
|
|
G_CALLBACK (on_proxy_destroyed), self, 0);
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-10-13 12:48:03 +03:00
|
|
|
wp_properties_set (si_props, "item.node.supports-encoded-fmts",
|
|
|
|
|
self->have_encoded ? "true" : "false");
|
|
|
|
|
|
|
|
|
|
wp_properties_set (si_props, "item.node.encoded-only",
|
|
|
|
|
self->encoded_only ? "true" : "false");
|
|
|
|
|
|
2021-10-14 16:38:49 +03:00
|
|
|
wp_properties_set (si_props, "item.node.unpositioned",
|
|
|
|
|
self->is_unpositioned ? "true" : "false");
|
|
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
wp_properties_set (si_props, "item.factory.name", SI_FACTORY_NAME);
|
|
|
|
|
wp_session_item_set_properties (item, g_steal_pointer (&si_props));
|
2020-03-17 23:03:10 +02:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
static gpointer
|
2021-03-18 14:47:22 -04:00
|
|
|
si_audio_adapter_get_associated_proxy (WpSessionItem * item, GType proxy_type)
|
2020-03-17 23:03:10 +02:00
|
|
|
{
|
2021-03-18 14:47:22 -04:00
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
if (proxy_type == WP_TYPE_NODE)
|
|
|
|
|
return self->node ? g_object_ref (self->node) : NULL;
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
return NULL;
|
2020-03-17 23:03:10 +02:00
|
|
|
}
|
|
|
|
|
|
2020-04-16 13:29:48 -04:00
|
|
|
static WpSpaPod *
|
|
|
|
|
format_audio_raw_build (const struct spa_audio_info_raw *info)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
|
2021-01-13 20:11:41 +02:00
|
|
|
"Spa:Pod:Object:Param:Format", "Format");
|
2020-04-16 13:29:48 -04:00
|
|
|
wp_spa_pod_builder_add (builder,
|
2021-01-14 10:50:43 +02:00
|
|
|
"mediaType", "K", "audio",
|
|
|
|
|
"mediaSubtype", "K", "raw",
|
2020-04-16 13:29:48 -04:00
|
|
|
"format", "I", info->format,
|
|
|
|
|
"rate", "i", info->rate,
|
|
|
|
|
"channels", "i", info->channels,
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
if (!SPA_FLAG_IS_SET (info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
|
|
|
|
|
/* Build the position array spa pod */
|
|
|
|
|
g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
|
|
|
|
|
for (guint i = 0; i < info->channels; i++)
|
|
|
|
|
wp_spa_pod_builder_add_id (position_builder, info->position[i]);
|
|
|
|
|
|
|
|
|
|
/* Add the position property */
|
|
|
|
|
wp_spa_pod_builder_add_property (builder, "position");
|
|
|
|
|
g_autoptr (WpSpaPod) position = wp_spa_pod_builder_end (position_builder);
|
|
|
|
|
wp_spa_pod_builder_add_pod (builder, position);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wp_spa_pod_builder_end (builder);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-20 11:46:18 -04:00
|
|
|
static gboolean
|
|
|
|
|
parse_adapter_format (WpSpaPod *format, gint *channels,
|
|
|
|
|
WpSpaPod **position)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpSpaPodParser) parser = NULL;
|
|
|
|
|
guint32 t = 0, s = 0, f = 0;
|
|
|
|
|
gint r = 0, c = 0;
|
|
|
|
|
g_autoptr (WpSpaPod) p = NULL;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (format, FALSE);
|
|
|
|
|
parser = wp_spa_pod_parser_new_object (format, NULL);
|
|
|
|
|
g_return_val_if_fail (parser, FALSE);
|
|
|
|
|
|
|
|
|
|
if (!wp_spa_pod_parser_get (parser, "mediaType", "I", &t, NULL) ||
|
|
|
|
|
!wp_spa_pod_parser_get (parser, "mediaSubtype", "I", &s, NULL) ||
|
|
|
|
|
!wp_spa_pod_parser_get (parser, "format", "I", &f, NULL) ||
|
|
|
|
|
!wp_spa_pod_parser_get (parser, "rate", "i", &r, NULL) ||
|
|
|
|
|
!wp_spa_pod_parser_get (parser, "channels", "i", &c, NULL))
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
/* position is optional */
|
|
|
|
|
wp_spa_pod_parser_get (parser, "position", "P", &p, NULL);
|
|
|
|
|
|
2022-09-20 15:02:25 +05:30
|
|
|
if (channels && c != 0)
|
2021-09-20 11:46:18 -04:00
|
|
|
*channels = c;
|
|
|
|
|
if (position)
|
|
|
|
|
*position = p ? wp_spa_pod_ref (p) : NULL;
|
|
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static WpSpaPod *
|
|
|
|
|
build_adapter_format (WpSiAudioAdapter * self, guint32 format, gint channels,
|
|
|
|
|
WpSpaPod *pos)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpSpaPod) position = pos;
|
|
|
|
|
g_autoptr (WpSpaPodBuilder) b = NULL;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail (channels > 0, NULL);
|
|
|
|
|
|
|
|
|
|
/* build the position array if not given */
|
|
|
|
|
if (!position) {
|
|
|
|
|
switch (channels) {
|
|
|
|
|
case 1: {
|
|
|
|
|
g_autoptr (WpSpaPodBuilder) pos_b = wp_spa_pod_builder_new_array ();
|
|
|
|
|
wp_spa_pod_builder_add_id (pos_b, SPA_AUDIO_CHANNEL_MONO);
|
|
|
|
|
position = wp_spa_pod_builder_end (pos_b);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 2: {
|
|
|
|
|
g_autoptr (WpSpaPodBuilder) pos_b = wp_spa_pod_builder_new_array ();
|
|
|
|
|
wp_spa_pod_builder_add_id (pos_b, SPA_AUDIO_CHANNEL_FL);
|
|
|
|
|
wp_spa_pod_builder_add_id (pos_b, SPA_AUDIO_CHANNEL_FR);
|
|
|
|
|
position = wp_spa_pod_builder_end (pos_b);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* build the format */
|
|
|
|
|
b = wp_spa_pod_builder_new_object ("Spa:Pod:Object:Param:Format", "Format");
|
|
|
|
|
wp_spa_pod_builder_add_property (b, "mediaType");
|
|
|
|
|
wp_spa_pod_builder_add_id (b, SPA_MEDIA_TYPE_audio);
|
|
|
|
|
wp_spa_pod_builder_add_property (b, "mediaSubtype");
|
|
|
|
|
wp_spa_pod_builder_add_id (b, SPA_MEDIA_SUBTYPE_raw);
|
|
|
|
|
wp_spa_pod_builder_add_property (b, "format");
|
|
|
|
|
wp_spa_pod_builder_add_id (b, format);
|
|
|
|
|
wp_spa_pod_builder_add_property (b, "rate");
|
2021-10-08 18:22:37 +03:00
|
|
|
wp_spa_pod_builder_add_int (b, si_audio_adapter_get_default_clock_rate (self));
|
2021-09-20 11:46:18 -04:00
|
|
|
wp_spa_pod_builder_add_property (b, "channels");
|
|
|
|
|
wp_spa_pod_builder_add_int (b, channels);
|
|
|
|
|
if (position) {
|
|
|
|
|
wp_spa_pod_builder_add_property (b, "position");
|
2021-10-11 03:08:34 -04:00
|
|
|
wp_spa_pod_builder_add_pod (b, position);
|
2021-09-20 11:46:18 -04:00
|
|
|
}
|
|
|
|
|
return wp_spa_pod_builder_end (b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static WpSpaPod *
|
|
|
|
|
build_adapter_dsp_format (WpSiAudioAdapter * self, WpSpaPod *dev_format)
|
|
|
|
|
{
|
|
|
|
|
g_autoptr (WpSpaPod) position = NULL;
|
|
|
|
|
gint channels = 2;
|
|
|
|
|
|
|
|
|
|
/* parse device format */
|
|
|
|
|
if (dev_format && !parse_adapter_format (dev_format, &channels, &position))
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
/* build F32P with same channels and position as device format */
|
|
|
|
|
return build_adapter_format (self, SPA_AUDIO_FORMAT_F32P, channels,
|
|
|
|
|
g_steal_pointer (&position));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static WpSpaPod *
|
|
|
|
|
build_adapter_default_format (WpSiAudioAdapter * self, const gchar *mode)
|
|
|
|
|
{
|
|
|
|
|
guint32 format = SPA_AUDIO_FORMAT_F32;
|
|
|
|
|
|
|
|
|
|
/* if dsp, use plannar format */
|
2021-09-29 13:04:51 -04:00
|
|
|
if (!mode || g_strcmp0 (mode, "dsp") == 0)
|
2021-09-20 11:46:18 -04:00
|
|
|
format = SPA_AUDIO_FORMAT_F32P;
|
|
|
|
|
|
|
|
|
|
return build_adapter_format (self, format, 2, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-17 23:03:10 +02:00
|
|
|
static void
|
2021-05-06 11:25:12 -04:00
|
|
|
on_format_set (GObject *obj, GAsyncResult * res, gpointer p)
|
2020-03-17 23:03:10 +02:00
|
|
|
{
|
2022-01-03 14:39:15 +01:00
|
|
|
g_autoptr(WpTransition) transition = p;
|
2021-05-06 11:25:12 -04:00
|
|
|
WpSiAudioAdapter *self = wp_transition_get_source_object (transition);
|
|
|
|
|
g_autoptr (GError) error = NULL;
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2022-01-03 14:39:15 +01:00
|
|
|
if (wp_transition_get_completed (transition))
|
|
|
|
|
return;
|
|
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
wp_si_adapter_set_ports_format_finish (WP_SI_ADAPTER (self), res, &error);
|
|
|
|
|
if (error) {
|
|
|
|
|
wp_transition_return_error (transition, g_steal_pointer (&error));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-17 23:03:10 +02:00
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
wp_object_update_features (WP_OBJECT (self),
|
|
|
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
|
2021-03-17 14:52:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2021-10-08 18:22:37 +03:00
|
|
|
si_audio_adapter_configure_node (WpSiAudioAdapter *self,
|
2021-03-17 14:52:41 -04:00
|
|
|
WpTransition * transition)
|
|
|
|
|
{
|
2021-05-06 11:25:12 -04:00
|
|
|
g_autoptr (WpSpaPod) format = NULL;
|
2021-09-20 11:46:18 -04:00
|
|
|
g_autoptr (WpSpaPod) ports_format = NULL;
|
2021-09-29 13:31:22 -04:00
|
|
|
const gchar *mode = NULL;
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
/* set the chosen format on the node */
|
2021-10-08 18:22:37 +03:00
|
|
|
format = format_audio_raw_build (&self->raw_format);
|
2021-05-06 11:25:12 -04:00
|
|
|
wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node), "Format", 0,
|
2021-06-03 08:58:15 -04:00
|
|
|
wp_spa_pod_ref (format));
|
2021-05-06 11:25:12 -04:00
|
|
|
|
2021-09-20 11:46:18 -04:00
|
|
|
/* build the ports format */
|
2021-09-29 13:31:22 -04:00
|
|
|
if (self->disable_dsp) {
|
|
|
|
|
mode = "passthrough";
|
|
|
|
|
ports_format = g_steal_pointer (&format);
|
|
|
|
|
} else {
|
|
|
|
|
mode = "dsp";
|
|
|
|
|
ports_format = build_adapter_dsp_format (self, format);
|
|
|
|
|
if (!ports_format) {
|
|
|
|
|
wp_transition_return_error (transition,
|
|
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"failed to build ports format"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-09-20 11:46:18 -04:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
/* set chosen format in the ports */
|
2021-09-20 11:46:18 -04:00
|
|
|
wp_si_adapter_set_ports_format (WP_SI_ADAPTER (self),
|
2022-01-03 14:39:15 +01:00
|
|
|
g_steal_pointer (&ports_format), mode, on_format_set, g_object_ref (transition));
|
2021-05-06 11:25:12 -04:00
|
|
|
}
|
|
|
|
|
|
2021-06-18 12:21:29 +03:00
|
|
|
static void
|
2021-09-29 13:13:19 -04:00
|
|
|
on_port_param_info (WpPipewireObject * port, GParamSpec * param,
|
|
|
|
|
WpSiAudioAdapter *self)
|
2021-06-18 12:21:29 +03:00
|
|
|
{
|
|
|
|
|
/* finish the task started by _set_ports_format() */
|
2021-09-29 13:13:19 -04:00
|
|
|
if (self->format_task) {
|
2021-06-18 12:21:29 +03:00
|
|
|
g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
|
2022-05-03 10:17:11 -04:00
|
|
|
si_audio_adapter_set_ports_state (self,
|
|
|
|
|
WP_SI_ADAPTER_PORTS_STATE_CONFIGURED);
|
2021-06-18 12:21:29 +03:00
|
|
|
g_task_return_boolean (t, TRUE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-29 13:13:19 -04:00
|
|
|
static void
|
|
|
|
|
on_node_ports_changed (WpObject * node, WpSiAudioAdapter *self)
|
|
|
|
|
{
|
|
|
|
|
/* clear port and handler */
|
|
|
|
|
if (self->port) {
|
|
|
|
|
g_signal_handlers_disconnect_by_func (self->port, on_port_param_info, self);
|
|
|
|
|
g_clear_object (&self->port);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (wp_node_get_n_ports (self->node) > 0) {
|
|
|
|
|
/* if non DSP mode, listen for param-info on the single port in order to
|
|
|
|
|
* be notified of format changed events */
|
|
|
|
|
if (g_strcmp0 (self->mode, "dsp") != 0) {
|
|
|
|
|
self->port = wp_node_lookup_port (self->node,
|
|
|
|
|
WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.direction", "=s",
|
|
|
|
|
self->portconfig_direction == WP_DIRECTION_INPUT ? "in" : "out",
|
|
|
|
|
NULL);
|
|
|
|
|
if (self->port)
|
|
|
|
|
g_signal_connect_object (self->port, "notify::param-info",
|
|
|
|
|
G_CALLBACK (on_port_param_info), self, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* finish the task started by _set_ports_format() */
|
|
|
|
|
if (self->format_task) {
|
|
|
|
|
g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
|
2022-05-03 10:17:11 -04:00
|
|
|
si_audio_adapter_set_ports_state (self,
|
|
|
|
|
WP_SI_ADAPTER_PORTS_STATE_CONFIGURED);
|
2021-09-29 13:13:19 -04:00
|
|
|
g_task_return_boolean (t, TRUE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-22 10:05:46 -05:00
|
|
|
static void
|
|
|
|
|
on_node_params_changed (WpPipewireObject * proxy, const gchar *param_name,
|
|
|
|
|
WpSiAudioAdapter *self)
|
|
|
|
|
{
|
|
|
|
|
/* Only handle Props param that has been emitted when setting ports format */
|
|
|
|
|
if (!g_str_equal (param_name, "Props") || !self->format_task)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/* If ports are already configured, finish task. This is the case for already
|
|
|
|
|
* loaded nodes such as virtual filters */
|
|
|
|
|
if (wp_node_get_n_ports (self->node) > 0)
|
|
|
|
|
on_node_ports_changed (WP_OBJECT (self->node), self);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
static void
|
2021-10-08 18:22:37 +03:00
|
|
|
si_audio_adapter_enable_active (WpSessionItem *si, WpTransition *transition)
|
2021-05-06 11:25:12 -04:00
|
|
|
{
|
2021-10-08 18:22:37 +03:00
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (si);
|
2021-05-06 11:25:12 -04:00
|
|
|
|
2021-10-08 18:22:37 +03:00
|
|
|
if (!wp_session_item_is_configured (si)) {
|
|
|
|
|
wp_transition_return_error (transition,
|
|
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
|
|
|
"si-audio-adapter: item is not configured"));
|
2021-05-06 11:25:12 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-08 12:23:00 +02:00
|
|
|
if (!(wp_object_test_active_features (WP_OBJECT (self->node),
|
|
|
|
|
WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL))) {
|
2021-10-08 18:22:37 +03:00
|
|
|
wp_transition_return_error (transition,
|
|
|
|
|
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
|
|
|
"si-audio-adapter: node minimal feature not enabled"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self->ports_changed_sigid = g_signal_connect_object (self->node,
|
|
|
|
|
"ports-changed", (GCallback) on_node_ports_changed, self, 0);
|
2023-11-22 10:05:46 -05:00
|
|
|
self->params_changed_sigid = g_signal_connect_object (self->node,
|
|
|
|
|
"params-changed", (GCallback) on_node_params_changed, self, 0);
|
2021-06-18 12:21:29 +03:00
|
|
|
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
/* If device node, enum available formats and set one of them.
|
|
|
|
|
* Also, if format_preference_priority is >0, do the same, since during
|
|
|
|
|
* linking, the link will try to use the format as soon as possible. */
|
2021-10-21 13:04:33 -04:00
|
|
|
if (!self->no_format && (self->is_device || self->dont_remix ||
|
si-linkables: add int format-preference-priority SI audio adapter property
This property influences how the SI standard link picks a format during
negotiations. An adapter with a higher priority is preferred then, that is,
its format has priority over the other adapter's.
The minimum valid priority is 0. The value 0 is the default. 0 means that
there is no priority for this adapter format. If both adapters have
priority 0, the behavior is as it was before this commit.
If an adapter's priority is greater than 0, the following changes in
behavior occur:
1. In si_audio_adapter_find_format (), new formats are always picked
over a format that is already in use. Normally, the new format would
only be picked over the one in use if the new format has a greater
number of channels.
2. si_audio_adapter_configure_node () is called immediately when the
adapter is activated. Normally, this is only done in certain cases,
like when the stream.dont-remix property is true.
3. In the SI link, the adapter is configured even if the unpositioned
flags of one or both adapters are set to true.
The intent of this property is to be used when "upstream" (that
is, the media source) wants to enforce its format "downstream". Normally,
for example, audio sinks tend to open an audio output device with the
maximum amount of channels it can handle, and then keep it open with that
amount, even if incoming streams have fewer channels (the extra channels
then remain unused). By using these format priorities, a different
behavior can be configured where the output device reconfigures the
audio hardware to use the amount of channels from upstream.
2024-07-12 14:14:26 +02:00
|
|
|
!self->is_autoconnect || self->disable_dsp || self->is_unpositioned ||
|
|
|
|
|
(self->format_preference_priority > 0)))
|
2021-10-08 18:22:37 +03:00
|
|
|
si_audio_adapter_configure_node (self, transition);
|
2021-05-06 11:25:12 -04:00
|
|
|
|
|
|
|
|
/* Otherwise just finish activating */
|
|
|
|
|
else
|
|
|
|
|
wp_object_update_features (WP_OBJECT (self),
|
|
|
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE, 0);
|
2020-03-17 23:03:10 +02:00
|
|
|
}
|
|
|
|
|
|
2021-03-17 14:52:41 -04:00
|
|
|
static void
|
2021-10-08 18:22:37 +03:00
|
|
|
si_audio_adapter_disable_active (WpSessionItem *si)
|
2021-03-17 14:52:41 -04:00
|
|
|
{
|
2021-03-18 14:47:22 -04:00
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (si);
|
2021-03-17 14:52:41 -04:00
|
|
|
|
2021-10-08 18:22:37 +03:00
|
|
|
if (self->ports_changed_sigid) {
|
|
|
|
|
g_signal_handler_disconnect (self->node, self->ports_changed_sigid);
|
|
|
|
|
self->ports_changed_sigid = 0;
|
2021-05-06 11:25:12 -04:00
|
|
|
}
|
2023-11-22 10:05:46 -05:00
|
|
|
if (self->params_changed_sigid) {
|
|
|
|
|
g_signal_handler_disconnect (self->node, self->params_changed_sigid);
|
|
|
|
|
self->params_changed_sigid = 0;
|
|
|
|
|
}
|
2021-05-06 11:25:12 -04:00
|
|
|
|
2021-10-08 18:22:37 +03:00
|
|
|
wp_object_update_features (WP_OBJECT (self), 0,
|
|
|
|
|
WP_SESSION_ITEM_FEATURE_ACTIVE);
|
2021-03-17 14:52:41 -04:00
|
|
|
}
|
|
|
|
|
|
2021-03-26 12:15:07 -04:00
|
|
|
static WpObjectFeatures
|
|
|
|
|
si_audio_adapter_get_supported_features (WpObject * self)
|
2021-03-17 14:52:41 -04:00
|
|
|
{
|
2021-03-26 12:15:07 -04:00
|
|
|
return WP_SESSION_ITEM_FEATURE_ACTIVE;
|
2020-04-16 17:38:31 +03:00
|
|
|
}
|
|
|
|
|
|
2020-03-17 23:03:10 +02:00
|
|
|
static void
|
2021-03-18 14:47:22 -04:00
|
|
|
si_audio_adapter_class_init (WpSiAudioAdapterClass * klass)
|
2020-03-17 23:03:10 +02:00
|
|
|
{
|
2021-03-26 12:15:07 -04:00
|
|
|
WpObjectClass * wpobject_class = (WpObjectClass *) klass;
|
2020-03-17 23:03:10 +02:00
|
|
|
WpSessionItemClass *si_class = (WpSessionItemClass *) klass;
|
|
|
|
|
|
2021-03-26 12:15:07 -04:00
|
|
|
wpobject_class->get_supported_features =
|
|
|
|
|
si_audio_adapter_get_supported_features;
|
|
|
|
|
|
2021-03-18 14:47:22 -04:00
|
|
|
si_class->reset = si_audio_adapter_reset;
|
|
|
|
|
si_class->configure = si_audio_adapter_configure;
|
|
|
|
|
si_class->get_associated_proxy = si_audio_adapter_get_associated_proxy;
|
|
|
|
|
si_class->disable_active = si_audio_adapter_disable_active;
|
|
|
|
|
si_class->enable_active = si_audio_adapter_enable_active;
|
2020-03-17 23:03:10 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-03 10:17:11 -04:00
|
|
|
static WpSiAdapterPortsState
|
|
|
|
|
si_audio_adapter_get_ports_state (WpSiAdapter * item)
|
|
|
|
|
{
|
|
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
|
|
|
return self->ports_state;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
static WpSpaPod *
|
|
|
|
|
si_audio_adapter_get_ports_format (WpSiAdapter * item, const gchar **mode)
|
|
|
|
|
{
|
|
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
|
|
|
|
if (mode)
|
|
|
|
|
*mode = self->mode;
|
|
|
|
|
return self->format ? wp_spa_pod_ref (self->format) : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2021-06-03 09:19:16 -04:00
|
|
|
si_audio_adapter_set_ports_format (WpSiAdapter * item, WpSpaPod *f,
|
2021-05-06 11:25:12 -04:00
|
|
|
const gchar *mode, GAsyncReadyCallback callback, gpointer data)
|
|
|
|
|
{
|
|
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
2021-06-03 09:19:16 -04:00
|
|
|
g_autoptr (WpSpaPod) format = f;
|
2021-11-18 11:58:18 -05:00
|
|
|
g_autoptr (GTask) task = g_task_new (self, NULL, callback, data);
|
2021-05-06 11:25:12 -04:00
|
|
|
|
|
|
|
|
/* cancel previous task if any */
|
|
|
|
|
if (self->format_task) {
|
2021-05-18 12:33:21 -04:00
|
|
|
g_autoptr (GTask) t = g_steal_pointer (&self->format_task);
|
|
|
|
|
g_task_return_new_error (t, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT,
|
|
|
|
|
"setting new format before previous done");
|
2021-05-06 11:25:12 -04:00
|
|
|
}
|
|
|
|
|
|
2021-09-20 11:46:18 -04:00
|
|
|
/* build default format if NULL was given */
|
2021-10-13 12:48:03 +03:00
|
|
|
if (!format && !g_strcmp0 (mode, "dsp")) {
|
2021-09-20 11:46:18 -04:00
|
|
|
format = build_adapter_default_format (self, mode);
|
2021-11-18 11:58:18 -05:00
|
|
|
if (!format) {
|
|
|
|
|
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
|
|
|
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"failed to build default format, aborting set format operation");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-09-20 11:46:18 -04:00
|
|
|
}
|
|
|
|
|
|
2021-11-18 11:58:18 -05:00
|
|
|
/* make sure the node has WP_NODE_FEATURE_PORTS */
|
2023-11-08 12:23:00 +02:00
|
|
|
if (G_UNLIKELY (!(wp_object_test_active_features (WP_OBJECT (self->node),
|
|
|
|
|
WP_NODE_FEATURE_PORTS)))) {
|
2021-11-18 11:58:18 -05:00
|
|
|
g_task_return_new_error (task, WP_DOMAIN_LIBRARY,
|
2021-06-18 12:21:29 +03:00
|
|
|
WP_LIBRARY_ERROR_OPERATION_FAILED,
|
|
|
|
|
"node feature ports is not enabled, aborting set format operation");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-14 18:31:19 +03:00
|
|
|
/* skip reconfiguring if the same mode & format are requested */
|
|
|
|
|
if (!g_strcmp0 (mode, self->mode) &&
|
|
|
|
|
((format == NULL && self->format == NULL) ||
|
|
|
|
|
wp_spa_pod_equal (format, self->format))) {
|
2021-11-18 11:58:18 -05:00
|
|
|
g_task_return_boolean (task, TRUE);
|
2021-10-14 18:31:19 +03:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-13 13:17:29 +03:00
|
|
|
/* ensure the node is suspended */
|
|
|
|
|
if (wp_node_get_state (self->node, NULL) >= WP_NODE_STATE_IDLE)
|
|
|
|
|
wp_node_send_command (self->node, "Suspend");
|
|
|
|
|
|
2021-11-18 11:58:18 -05:00
|
|
|
/* set task, format and mode */
|
|
|
|
|
self->format_task = g_steal_pointer (&task);
|
|
|
|
|
g_clear_pointer (&self->format, wp_spa_pod_unref);
|
|
|
|
|
self->format = g_steal_pointer (&format);
|
|
|
|
|
strncpy (self->mode, mode ? mode : "dsp", sizeof (self->mode) - 1);
|
|
|
|
|
|
2022-05-03 10:17:11 -04:00
|
|
|
si_audio_adapter_set_ports_state (self,
|
|
|
|
|
WP_SI_ADAPTER_PORTS_STATE_CONFIGURING);
|
|
|
|
|
|
2021-05-06 11:25:12 -04:00
|
|
|
/* configure DSP with chosen format */
|
|
|
|
|
wp_pipewire_object_set_param (WP_PIPEWIRE_OBJECT (self->node),
|
2021-06-03 08:58:15 -04:00
|
|
|
"PortConfig", 0, wp_spa_pod_new_object (
|
|
|
|
|
"Spa:Pod:Object:Param:PortConfig", "PortConfig",
|
2021-09-23 18:58:12 +03:00
|
|
|
"direction", "I", self->portconfig_direction,
|
2021-06-03 08:58:15 -04:00
|
|
|
"mode", "K", self->mode,
|
|
|
|
|
"monitor", "b", self->monitor,
|
|
|
|
|
"control", "b", self->control_port,
|
|
|
|
|
"format", "P", self->format,
|
|
|
|
|
NULL));
|
2021-05-06 11:25:12 -04:00
|
|
|
|
2021-06-18 12:21:29 +03:00
|
|
|
/* the task finishes with new ports being emitted -> on_node_ports_changed */
|
2021-05-06 11:25:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
si_audio_adapter_set_ports_format_finish (WpSiAdapter * item,
|
|
|
|
|
GAsyncResult * res, GError ** error)
|
|
|
|
|
{
|
|
|
|
|
return g_task_propagate_boolean (G_TASK (res), error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
si_audio_adapter_adapter_init (WpSiAdapterInterface * iface)
|
|
|
|
|
{
|
2022-05-03 10:17:11 -04:00
|
|
|
iface->get_ports_state = si_audio_adapter_get_ports_state;
|
2021-05-06 11:25:12 -04:00
|
|
|
iface->get_ports_format = si_audio_adapter_get_ports_format;
|
|
|
|
|
iface->set_ports_format = si_audio_adapter_set_ports_format;
|
|
|
|
|
iface->set_ports_format_finish = si_audio_adapter_set_ports_format_finish;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-04 10:12:35 +03:00
|
|
|
static GVariant *
|
2021-04-29 18:56:49 -04:00
|
|
|
si_audio_adapter_get_ports (WpSiLinkable * item, const gchar * context)
|
2020-05-04 10:12:35 +03:00
|
|
|
{
|
2021-03-18 14:47:22 -04:00
|
|
|
WpSiAudioAdapter *self = WP_SI_AUDIO_ADAPTER (item);
|
2020-05-04 10:12:35 +03:00
|
|
|
g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_ARRAY);
|
|
|
|
|
g_autoptr (WpIterator) it = NULL;
|
|
|
|
|
g_auto (GValue) val = G_VALUE_INIT;
|
2021-10-08 00:09:42 +03:00
|
|
|
WpDirection direction;
|
2020-05-04 10:12:35 +03:00
|
|
|
guint32 node_id;
|
|
|
|
|
|
2021-10-08 00:09:42 +03:00
|
|
|
if (!g_strcmp0 (context, "output")) {
|
|
|
|
|
direction = WP_DIRECTION_OUTPUT;
|
|
|
|
|
}
|
|
|
|
|
else if (!g_strcmp0 (context, "input")) {
|
|
|
|
|
direction = WP_DIRECTION_INPUT;
|
2020-05-04 10:12:35 +03:00
|
|
|
}
|
2021-10-08 00:09:42 +03:00
|
|
|
else {
|
2020-05-04 10:12:35 +03:00
|
|
|
/* on any other context, return an empty list of ports */
|
|
|
|
|
return g_variant_new_array (G_VARIANT_TYPE ("(uuu)"), NULL, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_variant_builder_init (&b, G_VARIANT_TYPE ("a(uuu)"));
|
|
|
|
|
node_id = wp_proxy_get_bound_id (WP_PROXY (self->node));
|
|
|
|
|
|
2021-02-05 10:20:33 -05:00
|
|
|
for (it = wp_node_new_ports_iterator (self->node);
|
2020-05-04 10:12:35 +03:00
|
|
|
wp_iterator_next (it, &val);
|
|
|
|
|
g_value_unset (&val))
|
|
|
|
|
{
|
|
|
|
|
WpPort *port = g_value_get_object (&val);
|
|
|
|
|
g_autoptr (WpProperties) props = NULL;
|
|
|
|
|
const gchar *channel;
|
|
|
|
|
guint32 port_id, channel_id = 0;
|
|
|
|
|
|
|
|
|
|
if (wp_port_get_direction (port) != direction)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
port_id = wp_proxy_get_bound_id (WP_PROXY (port));
|
2021-04-19 03:36:59 -04:00
|
|
|
props = wp_pipewire_object_get_properties (WP_PIPEWIRE_OBJECT (port));
|
2020-05-04 10:12:35 +03:00
|
|
|
|
2022-06-27 17:35:29 +03:00
|
|
|
/* skip control ports for now */
|
|
|
|
|
if (spa_atob (wp_properties_get (props, PW_KEY_PORT_CONTROL)))
|
|
|
|
|
continue;
|
|
|
|
|
|
2020-05-04 10:12:35 +03:00
|
|
|
/* try to find the audio channel; if channel is NULL, this will silently
|
|
|
|
|
leave the channel_id to its default value, 0 */
|
|
|
|
|
channel = wp_properties_get (props, PW_KEY_AUDIO_CHANNEL);
|
2021-01-13 20:11:41 +02:00
|
|
|
if (channel) {
|
|
|
|
|
WpSpaIdValue idval = wp_spa_id_value_from_short_name (
|
|
|
|
|
"Spa:Enum:AudioChannel", channel);
|
|
|
|
|
if (idval)
|
|
|
|
|
channel_id = wp_spa_id_value_number (idval);
|
|
|
|
|
}
|
2020-05-04 10:12:35 +03:00
|
|
|
|
|
|
|
|
g_variant_builder_add (&b, "(uuu)", node_id, port_id, channel_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return g_variant_builder_end (&b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2021-04-29 18:56:49 -04:00
|
|
|
si_audio_adapter_linkable_init (WpSiLinkableInterface * iface)
|
2020-05-04 10:12:35 +03:00
|
|
|
{
|
2021-03-18 14:47:22 -04:00
|
|
|
iface->get_ports = si_audio_adapter_get_ports;
|
2020-05-04 10:12:35 +03:00
|
|
|
}
|
|
|
|
|
|
2023-02-09 13:11:14 -05:00
|
|
|
WP_PLUGIN_EXPORT GObject *
|
2023-05-25 18:29:58 +03:00
|
|
|
wireplumber__module_init (WpCore * core, WpSpaJson * args, GError ** error)
|
2020-03-17 23:03:10 +02:00
|
|
|
{
|
2023-02-09 13:11:14 -05:00
|
|
|
return G_OBJECT (wp_si_factory_new_simple (SI_FACTORY_NAME,
|
2021-03-18 14:47:22 -04:00
|
|
|
si_audio_adapter_get_type ()));
|
2020-03-17 23:03:10 +02:00
|
|
|
}
|