mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-05-29 21:37:54 -04:00
344 lines
9.6 KiB
C
344 lines
9.6 KiB
C
/* PipeWire */
|
|
/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire authors */
|
|
/* SPDX-License-Identifier: MIT */
|
|
|
|
#include "config.h"
|
|
#include "pwtest.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <pipewire/pipewire.h>
|
|
|
|
#include <spa/filter-graph/filter-graph.h>
|
|
#include <spa/param/props.h>
|
|
#include <spa/pod/dynamic.h>
|
|
#include <spa/pod/iter.h>
|
|
#include <spa/pod/parser.h>
|
|
#include <spa/utils/keys.h>
|
|
|
|
#define EXPORT_RAMP_CURRENT "\"ramp:Current\" "
|
|
#define EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT EXPORT_RAMP_CURRENT \
|
|
EXPORT_RAMP_CURRENT EXPORT_RAMP_CURRENT \
|
|
EXPORT_RAMP_CURRENT EXPORT_RAMP_CURRENT \
|
|
EXPORT_RAMP_CURRENT EXPORT_RAMP_CURRENT \
|
|
EXPORT_RAMP_CURRENT EXPORT_RAMP_CURRENT
|
|
#define EXPORT_RAMP_CURRENT_100 EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT_10 \
|
|
EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT_10 \
|
|
EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT_10 \
|
|
EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT_10 \
|
|
EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT_10
|
|
#define EXPORT_RAMP_CURRENT_160 EXPORT_RAMP_CURRENT_100 EXPORT_RAMP_CURRENT_10 \
|
|
EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT_10 \
|
|
EXPORT_RAMP_CURRENT_10 EXPORT_RAMP_CURRENT_10 \
|
|
EXPORT_RAMP_CURRENT_10
|
|
|
|
static bool is_current_notify_info(struct spa_pod *pod)
|
|
{
|
|
const char *name = NULL;
|
|
|
|
if (pod == NULL || !spa_pod_is_object(pod))
|
|
return false;
|
|
if (SPA_POD_OBJECT_ID(pod) != SPA_PARAM_PropInfo)
|
|
return false;
|
|
if (spa_pod_parse_object(pod, SPA_TYPE_OBJECT_PropInfo, NULL,
|
|
SPA_PROP_INFO_name, SPA_POD_OPT_String(&name)) < 0)
|
|
return false;
|
|
return spa_streq(name, "ramp:Current");
|
|
}
|
|
|
|
static bool has_current_notify(struct spa_pod *pod)
|
|
{
|
|
struct spa_pod_object *obj = (struct spa_pod_object *)pod;
|
|
const struct spa_pod_prop *prop;
|
|
|
|
SPA_POD_OBJECT_FOREACH(obj, prop) {
|
|
struct spa_pod_parser prs;
|
|
struct spa_pod_frame f;
|
|
|
|
if (prop->key != SPA_PROP_params)
|
|
continue;
|
|
|
|
spa_pod_parser_pod(&prs, &prop->value);
|
|
if (spa_pod_parser_push_struct(&prs, &f) < 0)
|
|
return false;
|
|
|
|
while (true) {
|
|
const char *name;
|
|
float value;
|
|
|
|
if (spa_pod_parser_get_string(&prs, &name) < 0)
|
|
break;
|
|
if (spa_pod_parser_get_float(&prs, &value) < 0)
|
|
break;
|
|
if (spa_streq(name, "ramp:Current") && value > 0.0f)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
PWTEST(export_controls)
|
|
{
|
|
struct pw_loop *loop;
|
|
struct pw_context *context;
|
|
struct pw_properties *props;
|
|
struct spa_handle *handle;
|
|
struct spa_filter_graph *graph;
|
|
struct spa_pod_dynamic_builder b;
|
|
struct spa_pod *pod = NULL;
|
|
float out[64];
|
|
void *outputs[1] = { out };
|
|
int res;
|
|
|
|
pw_init(0, NULL);
|
|
|
|
loop = pw_loop_new(NULL);
|
|
pwtest_ptr_notnull(loop);
|
|
|
|
context = pw_context_new(loop, pw_properties_new(
|
|
PW_KEY_CONFIG_NAME, "null",
|
|
"context.modules.allow-empty", "true",
|
|
"support.dbus", "false",
|
|
NULL), 0);
|
|
pwtest_ptr_notnull(context);
|
|
|
|
pw_context_add_spa_lib(context,
|
|
"^filter\\.graph\\.plugin\\.builtin$",
|
|
"filter-graph/libspa-filter-graph-plugin-builtin");
|
|
pw_context_add_spa_lib(context,
|
|
"^filter\\.graph$",
|
|
"filter-graph/libspa-filter-graph");
|
|
|
|
props = pw_properties_new(
|
|
"clock.quantum-limit", "128",
|
|
"filter-graph.n_inputs", "0",
|
|
"filter-graph.n_outputs", "1",
|
|
SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph",
|
|
"filter.graph",
|
|
"{ nodes = [ { type = builtin name = ramp label = ramp "
|
|
"control = { Start = 0.0 Stop = 1.0 \"Duration (s)\" = 0.001 } } ] "
|
|
"outputs = [ \"ramp:Out\" ] "
|
|
"export.controls = [ \"ramp:Current\" ] }",
|
|
NULL);
|
|
pwtest_ptr_notnull(props);
|
|
|
|
handle = pw_context_load_spa_handle(context, "filter.graph", &props->dict);
|
|
pwtest_ptr_notnull(handle);
|
|
|
|
res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_FilterGraph,
|
|
(void **)&graph);
|
|
pwtest_neg_errno_ok(res);
|
|
pwtest_ptr_notnull(graph);
|
|
|
|
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
|
res = spa_filter_graph_enum_notify_info(graph, 0, &b.b, &pod);
|
|
pwtest_int_eq(res, 1);
|
|
pwtest_ptr_notnull(pod);
|
|
pwtest_bool_true(is_current_notify_info(pod));
|
|
spa_pod_dynamic_builder_clean(&b);
|
|
|
|
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
|
res = spa_filter_graph_enum_notify_info(graph, 1, &b.b, &pod);
|
|
pwtest_int_eq(res, 0);
|
|
spa_pod_dynamic_builder_clean(&b);
|
|
|
|
res = spa_filter_graph_activate(graph,
|
|
&SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_KEY_AUDIO_RATE, "48000")));
|
|
pwtest_neg_errno_ok(res);
|
|
|
|
spa_zero(out);
|
|
res = spa_filter_graph_process(graph, NULL, outputs, SPA_N_ELEMENTS(out));
|
|
pwtest_neg_errno_ok(res);
|
|
|
|
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
|
res = spa_filter_graph_get_notify(graph, &b.b, &pod);
|
|
pwtest_int_eq(res, 1);
|
|
pwtest_ptr_notnull(pod);
|
|
pwtest_bool_true(has_current_notify(pod));
|
|
spa_pod_dynamic_builder_clean(&b);
|
|
|
|
spa_filter_graph_deactivate(graph);
|
|
pw_unload_spa_handle(handle);
|
|
pw_properties_free(props);
|
|
pw_context_destroy(context);
|
|
pw_loop_destroy(loop);
|
|
pw_deinit();
|
|
|
|
return PWTEST_PASS;
|
|
}
|
|
|
|
PWTEST(export_controls_large_payload)
|
|
{
|
|
struct pw_loop *loop;
|
|
struct pw_context *context;
|
|
struct pw_properties *props;
|
|
struct spa_handle *handle;
|
|
struct spa_filter_graph *graph;
|
|
struct spa_pod_dynamic_builder b;
|
|
struct spa_pod *pod = NULL;
|
|
float out[64];
|
|
void *outputs[1] = { out };
|
|
int res;
|
|
|
|
pw_init(0, NULL);
|
|
|
|
loop = pw_loop_new(NULL);
|
|
pwtest_ptr_notnull(loop);
|
|
|
|
context = pw_context_new(loop, pw_properties_new(
|
|
PW_KEY_CONFIG_NAME, "null",
|
|
"context.modules.allow-empty", "true",
|
|
"support.dbus", "false",
|
|
NULL), 0);
|
|
pwtest_ptr_notnull(context);
|
|
|
|
pw_context_add_spa_lib(context,
|
|
"^filter\\.graph\\.plugin\\.builtin$",
|
|
"filter-graph/libspa-filter-graph-plugin-builtin");
|
|
pw_context_add_spa_lib(context,
|
|
"^filter\\.graph$",
|
|
"filter-graph/libspa-filter-graph");
|
|
|
|
props = pw_properties_new(
|
|
"clock.quantum-limit", "128",
|
|
"filter-graph.n_inputs", "0",
|
|
"filter-graph.n_outputs", "1",
|
|
SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph",
|
|
"filter.graph",
|
|
"{ nodes = [ { type = builtin name = ramp label = ramp "
|
|
"control = { Start = 0.0 Stop = 1.0 \"Duration (s)\" = 0.001 } } ] "
|
|
"outputs = [ \"ramp:Out\" ] "
|
|
"export.controls = [ " EXPORT_RAMP_CURRENT_160 "] }",
|
|
NULL);
|
|
pwtest_ptr_notnull(props);
|
|
|
|
handle = pw_context_load_spa_handle(context, "filter.graph", &props->dict);
|
|
pwtest_ptr_notnull(handle);
|
|
|
|
res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_FilterGraph,
|
|
(void **)&graph);
|
|
pwtest_neg_errno_ok(res);
|
|
pwtest_ptr_notnull(graph);
|
|
|
|
res = spa_filter_graph_activate(graph,
|
|
&SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_KEY_AUDIO_RATE, "48000")));
|
|
pwtest_neg_errno_ok(res);
|
|
|
|
spa_zero(out);
|
|
res = spa_filter_graph_process(graph, NULL, outputs, SPA_N_ELEMENTS(out));
|
|
pwtest_neg_errno_ok(res);
|
|
|
|
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
|
res = spa_filter_graph_get_notify(graph, &b.b, &pod);
|
|
pwtest_int_eq(res, 1);
|
|
pwtest_ptr_notnull(pod);
|
|
pwtest_int_gt((int)b.b.state.offset, 4096);
|
|
pwtest_bool_true(has_current_notify(pod));
|
|
spa_pod_dynamic_builder_clean(&b);
|
|
|
|
spa_filter_graph_deactivate(graph);
|
|
pw_unload_spa_handle(handle);
|
|
pw_properties_free(props);
|
|
pw_context_destroy(context);
|
|
pw_loop_destroy(loop);
|
|
pw_deinit();
|
|
|
|
return PWTEST_PASS;
|
|
}
|
|
|
|
PWTEST(export_controls_duplicated_graph_uses_scalar_values)
|
|
{
|
|
struct pw_loop *loop;
|
|
struct pw_context *context;
|
|
struct pw_properties *props;
|
|
struct spa_handle *handle;
|
|
struct spa_filter_graph *graph;
|
|
struct spa_pod_dynamic_builder b;
|
|
struct spa_pod *pod = NULL;
|
|
float in[2][64], out[2][64];
|
|
const void *inputs[2] = { in[0], in[1] };
|
|
void *outputs[2] = { out[0], out[1] };
|
|
int res;
|
|
|
|
pw_init(0, NULL);
|
|
|
|
loop = pw_loop_new(NULL);
|
|
pwtest_ptr_notnull(loop);
|
|
|
|
context = pw_context_new(loop, pw_properties_new(
|
|
PW_KEY_CONFIG_NAME, "null",
|
|
"context.modules.allow-empty", "true",
|
|
"support.dbus", "false",
|
|
NULL), 0);
|
|
pwtest_ptr_notnull(context);
|
|
|
|
pw_context_add_spa_lib(context,
|
|
"^filter\\.graph\\.plugin\\.builtin$",
|
|
"filter-graph/libspa-filter-graph-plugin-builtin");
|
|
pw_context_add_spa_lib(context,
|
|
"^filter\\.graph$",
|
|
"filter-graph/libspa-filter-graph");
|
|
|
|
props = pw_properties_new(
|
|
"clock.quantum-limit", "128",
|
|
"filter-graph.n_inputs", "2",
|
|
"filter-graph.n_outputs", "2",
|
|
SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph",
|
|
"filter.graph",
|
|
"{ nodes = [ "
|
|
"{ type = builtin name = copy label = copy } "
|
|
"{ type = builtin name = ramp label = ramp "
|
|
"control = { Start = 0.0 Stop = 1.0 \"Duration (s)\" = 0.001 } } ] "
|
|
"inputs = [ \"copy:In\" ] "
|
|
"outputs = [ \"copy:Out\" ] "
|
|
"export.controls = [ \"ramp:Current\" ] }",
|
|
NULL);
|
|
pwtest_ptr_notnull(props);
|
|
|
|
handle = pw_context_load_spa_handle(context, "filter.graph", &props->dict);
|
|
pwtest_ptr_notnull(handle);
|
|
|
|
res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_FilterGraph,
|
|
(void **)&graph);
|
|
pwtest_neg_errno_ok(res);
|
|
pwtest_ptr_notnull(graph);
|
|
|
|
res = spa_filter_graph_activate(graph,
|
|
&SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_KEY_AUDIO_RATE, "48000")));
|
|
pwtest_neg_errno_ok(res);
|
|
|
|
spa_zero(in);
|
|
spa_zero(out);
|
|
res = spa_filter_graph_process(graph, inputs, outputs, SPA_N_ELEMENTS(out[0]));
|
|
pwtest_neg_errno_ok(res);
|
|
|
|
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
|
|
res = spa_filter_graph_get_notify(graph, &b.b, &pod);
|
|
pwtest_int_eq(res, 1);
|
|
pwtest_ptr_notnull(pod);
|
|
pwtest_bool_true(has_current_notify(pod));
|
|
spa_pod_dynamic_builder_clean(&b);
|
|
|
|
spa_filter_graph_deactivate(graph);
|
|
pw_unload_spa_handle(handle);
|
|
pw_properties_free(props);
|
|
pw_context_destroy(context);
|
|
pw_loop_destroy(loop);
|
|
pw_deinit();
|
|
|
|
return PWTEST_PASS;
|
|
}
|
|
|
|
PWTEST_SUITE(filter_graph)
|
|
{
|
|
pwtest_add(export_controls, PWTEST_NOARG);
|
|
pwtest_add(export_controls_large_payload, PWTEST_NOARG);
|
|
pwtest_add(export_controls_duplicated_graph_uses_scalar_values, PWTEST_NOARG);
|
|
|
|
return PWTEST_PASS;
|
|
}
|
|
|
|
#undef EXPORT_RAMP_CURRENT_160
|
|
#undef EXPORT_RAMP_CURRENT_100
|
|
#undef EXPORT_RAMP_CURRENT_10
|
|
#undef EXPORT_RAMP_CURRENT
|