Commit 6f358e9a authored by George Kiagiadakis's avatar George Kiagiadakis
Browse files

adapter: select a reasonable device format instead of letting pipewire choose its own

parent 23b66a8f
Pipeline #80062 failed with stage
in 1 minute and 56 seconds
......@@ -29,7 +29,7 @@ shared_library(
'wireplumber-module-pipewire',
[
'module-pipewire.c',
'module-pipewire/multiport-link-algorithm.c',
'module-pipewire/algorithms.c',
'module-pipewire/simple-endpoint-link.c',
'module-pipewire/audio-softdsp-endpoint.c',
'module-pipewire/audio-softdsp-endpoint/stream.c',
......
......@@ -11,7 +11,11 @@
#include <spa/debug/types.h>
#include <spa/param/audio/type-info.h>
#include "multiport-link-algorithm.h"
// for choose_sensible_raw_audio_format
#include <spa/param/format-utils.h>
#include <spa/param/audio/format.h>
#include "algorithms.h"
gboolean
multiport_link_create (
......@@ -110,3 +114,258 @@ invalid_argument:
"Endpoint node/port descriptions don't have the required fields");
return FALSE;
}
static enum spa_audio_format
select_format (uint32_t *vals, uint32_t n_vals, uint32_t choice)
{
enum spa_audio_format fmt_order[] = {
/* float 32 is the best because it needs
no conversion from our internal pipeline format */
SPA_AUDIO_FORMAT_F32,
/* signed 16-bit is known to work very well;
unsigned should also be fine */
SPA_AUDIO_FORMAT_S16,
SPA_AUDIO_FORMAT_U16,
/* then go for the formats that are aligned to sizeof(int),
from the best quality to the worst */
SPA_AUDIO_FORMAT_S32,
SPA_AUDIO_FORMAT_U32,
SPA_AUDIO_FORMAT_S24_32,
SPA_AUDIO_FORMAT_U24_32,
/* then float 64, which should need little conversion from float 32 */
SPA_AUDIO_FORMAT_F64,
/* and then try the reverse endianess too */
SPA_AUDIO_FORMAT_F32_OE,
SPA_AUDIO_FORMAT_S16_OE,
SPA_AUDIO_FORMAT_U16_OE,
SPA_AUDIO_FORMAT_S32_OE,
SPA_AUDIO_FORMAT_U32_OE,
SPA_AUDIO_FORMAT_S24_32_OE,
SPA_AUDIO_FORMAT_U24_32_OE,
SPA_AUDIO_FORMAT_F64_OE,
/* then go for unaligned strange formats */
SPA_AUDIO_FORMAT_S24,
SPA_AUDIO_FORMAT_U24,
SPA_AUDIO_FORMAT_S20,
SPA_AUDIO_FORMAT_U20,
SPA_AUDIO_FORMAT_S18,
SPA_AUDIO_FORMAT_U18,
SPA_AUDIO_FORMAT_S24_OE,
SPA_AUDIO_FORMAT_U24_OE,
SPA_AUDIO_FORMAT_S20_OE,
SPA_AUDIO_FORMAT_U20_OE,
SPA_AUDIO_FORMAT_S18_OE,
SPA_AUDIO_FORMAT_U18_OE,
/* leave 8-bit last, that's bad quality */
SPA_AUDIO_FORMAT_S8,
SPA_AUDIO_FORMAT_U8,
/* plannar formats are problematic currently, discourage their use */
SPA_AUDIO_FORMAT_F32P,
SPA_AUDIO_FORMAT_S16P,
SPA_AUDIO_FORMAT_S32P,
SPA_AUDIO_FORMAT_S24_32P,
SPA_AUDIO_FORMAT_S24P,
SPA_AUDIO_FORMAT_F64P,
SPA_AUDIO_FORMAT_U8P,
};
uint32_t i, j, best = SPA_N_ELEMENTS(fmt_order);
switch (choice) {
case SPA_CHOICE_None:
g_return_val_if_fail (n_vals != 0, SPA_AUDIO_FORMAT_UNKNOWN);
return vals[0];
case SPA_CHOICE_Enum:
for (i = 0; i < n_vals; ++i, ++vals) {
for (j = 0; j < SPA_N_ELEMENTS(fmt_order); j++) {
if (*vals == fmt_order[j] && best > j) {
best = j;
break;
}
}
}
return (best < SPA_N_ELEMENTS(fmt_order)) ?
fmt_order[best] : SPA_AUDIO_FORMAT_UNKNOWN;
default:
g_return_val_if_reached (SPA_AUDIO_FORMAT_UNKNOWN);
}
}
static int32_t
select_rate (int32_t *vals, uint32_t n_vals, uint32_t choice)
{
uint32_t i;
int32_t result = 0, min, max;
switch (choice) {
case SPA_CHOICE_None:
g_return_val_if_fail (n_vals != 0, 0);
return vals[0];
case SPA_CHOICE_Enum:
/* pick the one closest to 48Khz */
g_return_val_if_fail (n_vals != 0, 0);
result = vals[0];
for (i = 1, ++vals; i < n_vals; ++i, ++vals) {
if (abs(*vals - 48000) < abs(result - 48000))
result = *vals;
}
return result;
case SPA_CHOICE_Range:
/* a range is typically 3 items: default, min, max;
however, sometimes ALSA drivers give bad min & max values
and pipewire picks a bad default... try to fix that here;
the default should be the one closest to 48K */
g_return_val_if_fail (n_vals == 3, 0);
min = SPA_MIN (vals[1], vals[2]);
max = SPA_MAX (vals[1], vals[2]);
return SPA_CLAMP(48000, min, max);
default:
g_return_val_if_reached (0);
}
}
static uint32_t
select_channels (uint32_t *vals, uint32_t n_vals, uint32_t choice)
{
uint32_t i;
uint32_t result = 0;
switch (choice) {
case SPA_CHOICE_None:
g_return_val_if_fail (n_vals != 0, 0);
return vals[0];
case SPA_CHOICE_Enum:
/* choose the most channels */
g_return_val_if_fail (n_vals != 0, 0);
result = vals[0];
for (i = 1, ++vals; i < n_vals; ++i, ++vals) {
if (*vals > result)
result = *vals;
}
return result;
case SPA_CHOICE_Range:
/* a range is typically 3 items: default, min, max;
we want the most channels, but let's not trust max
to really be the max... ALSA drivers can be broken */
g_return_val_if_fail (n_vals == 3, 0);
return SPA_MAX (vals[1], vals[2]);
default:
g_return_val_if_reached (0);
}
}
gboolean
choose_sensible_raw_audio_format (GPtrArray *formats,
struct spa_audio_info_raw *result)
{
guint i, most_channels = 0;
struct spa_audio_info_raw *raw;
raw = g_alloca (sizeof (struct spa_audio_info_raw) * formats->len);
for (i = 0; i < formats->len; i++) {
struct spa_pod *pod;
struct spa_pod_prop *prop;
uint32_t mtype, mstype;
/* initialize all fields to zero (SPA_AUDIO_FORMAT_UNKNOWN etc) and set
the unpositioned flag, which means there is no channel position array */
spa_memzero (&raw[i], sizeof(struct spa_audio_info_raw));
SPA_FLAG_SET(raw[i].flags, SPA_AUDIO_FLAG_UNPOSITIONED);
pod = g_ptr_array_index (formats, i);
if (!spa_pod_is_object (pod)) {
g_warning ("non-object POD appeared on formats list; this node is buggy");
continue;
}
if (spa_format_parse (pod, &mtype, &mstype) < 0) {
g_warning ("format does not have media type / subtype");
continue;
}
if (!(mtype == SPA_MEDIA_TYPE_audio && mstype == SPA_MEDIA_SUBTYPE_raw))
continue;
/* go through the fields and populate raw[i] */
SPA_POD_OBJECT_FOREACH ((struct spa_pod_object *) pod, prop) {
uint32_t type, size, n_vals, choice;
const struct spa_pod *val;
void *vals;
if (prop->key == SPA_FORMAT_mediaType ||
prop->key == SPA_FORMAT_mediaSubtype)
continue;
val = spa_pod_get_values (&prop->value, &n_vals, &choice);
type = val->type;
size = val->size;
vals = SPA_POD_BODY(val);
#define test_invariant(x) \
G_STMT_START { \
if (G_LIKELY (x)) ; \
else { \
g_warn_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, #x); \
raw[i].format = SPA_AUDIO_FORMAT_UNKNOWN; \
goto next; \
} \
} G_STMT_END
switch (prop->key) {
case SPA_FORMAT_AUDIO_format:
test_invariant (type == SPA_TYPE_Id);
test_invariant (size == sizeof(uint32_t));
raw[i].format = select_format ((uint32_t *) vals, n_vals, choice);
break;
case SPA_FORMAT_AUDIO_rate:
test_invariant (type == SPA_TYPE_Int);
test_invariant (size == sizeof(int32_t));
raw[i].rate = select_rate ((int32_t *) vals, n_vals, choice);
break;
case SPA_FORMAT_AUDIO_channels:
test_invariant (type == SPA_TYPE_Int);
test_invariant (size == sizeof(uint32_t));
raw[i].channels = select_channels ((uint32_t *) vals, n_vals, choice);
break;
case SPA_FORMAT_AUDIO_position:
/* just copy the array, there is no choice here */
SPA_FLAG_CLEAR (raw[i].flags, SPA_AUDIO_FLAG_UNPOSITIONED);
spa_pod_copy_array (val, SPA_TYPE_Id, raw[i].position,
SPA_AUDIO_MAX_CHANNELS);
break;
default:
if (prop->value.type == SPA_TYPE_Choice)
SPA_POD_CHOICE_TYPE(&prop->value) = SPA_CHOICE_None;
break;
}
}
next:
/* figure out if this one is the best so far */
if (raw[i].format != SPA_AUDIO_FORMAT_UNKNOWN &&
raw[i].channels > most_channels ) {
most_channels = raw[i].channels;
*result = raw[i];
}
}
/* if we picked a format, most_channels must be > 0 */
return (most_channels > 0);
}
......@@ -9,3 +9,7 @@
typedef void (*CreateLinkCb) (WpProperties *, gpointer);
gboolean multiport_link_create (GVariant * src_data, GVariant * sink_data,
CreateLinkCb create_link_cb, gpointer user_data, GError ** error);
struct spa_audio_info_raw;
gboolean choose_sensible_raw_audio_format (GPtrArray *formats,
struct spa_audio_info_raw *result);
......@@ -12,6 +12,7 @@
#include <spa/param/props.h>
#include "adapter.h"
#include "../algorithms.h"
enum {
PROP_0,
......@@ -48,7 +49,6 @@ on_proxy_enum_format_done (WpProxyNode *proxy, GAsyncResult *res,
uint8_t buf[1024];
struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
struct spa_pod *param;
uint32_t media_type, media_subtype;
formats = wp_proxy_node_enum_params_collect_finish (proxy, res, &error);
if (error) {
......@@ -58,23 +58,35 @@ on_proxy_enum_format_done (WpProxyNode *proxy, GAsyncResult *res,
return;
}
if (formats->len == 0 ||
!(param = g_ptr_array_index (formats, 0)) ||
spa_format_parse (param, &media_type, &media_subtype) < 0 ||
media_type != SPA_MEDIA_TYPE_audio ||
media_subtype != SPA_MEDIA_SUBTYPE_raw) {
g_message("WpAudioAdapter:%p node does not support audio/raw format", self);
wp_audio_stream_init_task_finish (WP_AUDIO_STREAM (self),
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"node does not support audio/raw format"));
return;
if (!choose_sensible_raw_audio_format (formats, &self->format)) {
uint32_t media_type, media_subtype;
g_warning ("WpAudioAdapter:%p failed to choose a sensible audio format",
self);
/* fall back to spa_pod_fixate */
if (formats->len == 0 ||
!(param = g_ptr_array_index (formats, 0)) ||
spa_format_parse (param, &media_type, &media_subtype) < 0 ||
media_type != SPA_MEDIA_TYPE_audio ||
media_subtype != SPA_MEDIA_SUBTYPE_raw) {
g_message("WpAudioAdapter:%p node does not support audio/raw format", self);
wp_audio_stream_init_task_finish (WP_AUDIO_STREAM (self),
g_error_new (WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"node does not support audio/raw format"));
return;
}
spa_pod_fixate (param);
spa_format_audio_raw_parse (param, &self->format);
}
/* Parse the raw audio format */
spa_pod_fixate (param);
spa_format_audio_raw_parse (param, &self->format);
/* set the chosen device/client format on the node */
param = spa_format_audio_raw_build (&pod_builder, SPA_PARAM_Format,
&self->format);
wp_proxy_node_set_param (proxy, SPA_PARAM_Format, 0, param);
/* Keep the chanels but use the default format */
/* now choose the DSP format: keep the chanels but use F32 plannar @ 48K */
self->format.format = SPA_AUDIO_FORMAT_F32P;
self->format.rate = 48000;
......
......@@ -13,7 +13,7 @@
#include <spa/param/props.h>
#include "convert.h"
#include "../multiport-link-algorithm.h"
#include "../algorithms.h"
enum {
PROP_0,
......
......@@ -23,7 +23,7 @@
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include "multiport-link-algorithm.h"
#include "algorithms.h"
struct _WpPipewireSimpleEndpointLink
{
......
subdir('modules')
subdir('wp')
/* WirePlumber
*
* Copyright © 2019 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include <wp/wp.h>
#include <pipewire/pipewire.h>
#include <spa/pod/builder.h>
#include <spa/param/audio/raw.h>
#include <spa/param/audio/layout.h>
#include <spa/param/audio/format.h>
#include <spa/param/audio/format-utils.h>
#include "../../modules/module-pipewire/algorithms.h"
static void
test_choose_sensible_raw_audio_format (void)
{
uint32_t layout[] = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR,
SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE,
SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR };
guint8 buffer[2048];
struct spa_pod_builder b;
struct spa_pod_frame f;
struct spa_pod *param;
struct spa_audio_info_raw info;
g_autoptr (GPtrArray) formats = g_ptr_array_new ();
/* test 1 */
g_ptr_array_remove_range (formats, 0, formats->len);
spa_pod_builder_init(&b, buffer, sizeof(buffer));
spa_pod_builder_push_object (&b, &f, SPA_TYPE_OBJECT_Format, 0);
spa_pod_builder_add (&b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3,
SPA_AUDIO_FORMAT_F32_OE,
SPA_AUDIO_FORMAT_S16,
SPA_AUDIO_FORMAT_S20),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(22000, 44100, 8000),
SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 8),
0);
param = (struct spa_pod *) spa_pod_builder_pop (&b, &f);
g_assert_nonnull (param);
g_ptr_array_add (formats, param);
g_assert_true (choose_sensible_raw_audio_format (formats, &info));
g_assert_cmpint (info.format, ==, SPA_AUDIO_FORMAT_S16);
g_assert_cmpint (info.rate, ==, 44100);
g_assert_cmpint (info.channels, ==, 8);
g_assert_cmpint (info.flags, ==, SPA_AUDIO_FLAG_UNPOSITIONED);
/* test 2 */
g_ptr_array_remove_range (formats, 0, formats->len);
spa_pod_builder_init (&b, buffer, sizeof(buffer));
spa_pod_builder_push_object (&b, &f, SPA_TYPE_OBJECT_Format, 0);
spa_pod_builder_add (&b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3,
SPA_AUDIO_FORMAT_S32,
SPA_AUDIO_FORMAT_U8,
SPA_AUDIO_FORMAT_F32),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(56000, 44100, 96000),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2),
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 2, layout),
0);
param = (struct spa_pod *) spa_pod_builder_pop (&b, &f);
g_assert_nonnull (param);
g_ptr_array_add (formats, param);
spa_pod_builder_push_object (&b, &f, SPA_TYPE_OBJECT_Format, 0);
spa_pod_builder_add (&b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3,
SPA_AUDIO_FORMAT_S32,
SPA_AUDIO_FORMAT_U8,
SPA_AUDIO_FORMAT_F32),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(56000, 44100, 96000),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(5),
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 5, layout),
0);
param = (struct spa_pod *) spa_pod_builder_pop (&b, &f);
g_assert_nonnull (param);
g_ptr_array_add (formats, param);
g_assert_true (choose_sensible_raw_audio_format (formats, &info));
g_assert_cmpint (info.format, ==, SPA_AUDIO_FORMAT_F32);
g_assert_cmpint (info.rate, ==, 48000);
g_assert_cmpint (info.channels, ==, 5);
g_assert_cmpint (info.flags, ==, SPA_AUDIO_FLAG_NONE);
g_assert_cmpint (info.position[0], ==, layout[0]);
g_assert_cmpint (info.position[1], ==, layout[1]);
g_assert_cmpint (info.position[2], ==, layout[2]);
g_assert_cmpint (info.position[3], ==, layout[3]);
g_assert_cmpint (info.position[4], ==, layout[4]);
g_assert_cmpint (info.position[5], ==, 0);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/modules/algorithms/choose_sensible_raw_audio_format",
test_choose_sensible_raw_audio_format);
return g_test_run ();
}
common_deps = [gobject_dep, gio_dep, wp_dep, pipewire_dep]
common_env = [
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
]
test(
'test-algorithms',
executable('test-algorithms',
[
'algorithms.c',
'../../modules/module-pipewire/algorithms.c'
],
dependencies: common_deps),
env: common_env,
)
Supports Markdown
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