echo-cancel: add option to make echo-cancel sink node a monitor

The monitor mode does not create a echo-cancel-playback node, and makes the
echo-cancel-sink node to be a monitor node with media class Stream/Input/Audio
(instead of Audio/Sink).

Some applications get confused when their output stream node is not linked
directly to the actual device node. Having echo-cancel-sink as a monitor node
avoids this problem, allowing those applications to work properly even if echo
cancel is enabled.

This mode is disabled by default. You can enable it by passing the specific
option  (monitor.mode = true) in the args when loading the module in the
pipewire context.modules configuration section.
This commit is contained in:
Julian Bouzas 2022-11-03 17:56:25 -04:00 committed by Wim Taymans
parent 34db6b80c6
commit bc2a02c283

View file

@ -201,12 +201,14 @@ struct impl {
struct spa_handle *spa_handle;
struct spa_plugin_loader *loader;
bool monitor_mode;
};
static void process(struct impl *impl)
{
struct pw_buffer *cout;
struct pw_buffer *pout;
struct pw_buffer *pout = NULL;
float rec_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)];
float play_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)];
float play_delayed_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)];
@ -220,7 +222,7 @@ static void process(struct impl *impl)
uint32_t rindex, pindex, oindex, pdindex, avail;
int32_t stride = 0;
if ((pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) {
if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) {
pw_log_debug("out of playback buffers: %m");
goto done;
}
@ -258,6 +260,7 @@ static void process(struct impl *impl)
impl->play_ringsize, pdindex % impl->play_ringsize,
(void *)play_delayed[i], size);
if (pout != NULL) {
/* output to sink, just copy */
dd = &pout->buffer->datas[i];
memcpy(dd->data, play[i], size);
@ -266,11 +269,13 @@ static void process(struct impl *impl)
dd->chunk->size = size;
dd->chunk->stride = stride;
}
}
spa_ringbuffer_read_update(&impl->rec_ring, rindex + size);
spa_ringbuffer_read_update(&impl->play_ring, pindex + size);
spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size);
if (impl->playback != NULL)
pw_stream_queue_buffer(impl->playback, pout);
if (SPA_UNLIKELY (impl->current_delay < impl->buffer_delay)) {
@ -586,6 +591,7 @@ static void output_state_changed(void *data, enum pw_stream_state old,
switch (state) {
case PW_STREAM_STATE_PAUSED:
pw_stream_flush(impl->sink, false);
if (impl->playback != NULL)
pw_stream_flush(impl->playback, false);
if (old == PW_STREAM_STATE_STREAMING) {
impl->current_delay = 0;
@ -618,7 +624,7 @@ static void output_param_latency_changed(struct impl *impl, const struct spa_pod
if (latency.direction == SPA_DIRECTION_INPUT)
pw_stream_update_params(impl->sink, params, 1);
else
else if (impl->playback != NULL)
pw_stream_update_params(impl->playback, params, 1);
}
@ -728,8 +734,10 @@ static void sink_process(void *data)
static void playback_destroy(void *d)
{
struct impl *impl = d;
if (impl->playback != NULL) {
spa_hook_remove(&impl->playback_listener);
impl->playback = NULL;
}
}
static const struct pw_stream_events playback_events = {
@ -796,6 +804,9 @@ static int setup_streams(struct impl *impl)
&impl->source_listener,
&source_events, impl);
if (impl->monitor_mode) {
impl->playback = NULL;
} else {
props = pw_properties_new(
PW_KEY_NODE_NAME, "echo-cancel-playback",
PW_KEY_NODE_VIRTUAL, "true",
@ -824,6 +835,7 @@ static int setup_streams(struct impl *impl)
pw_stream_add_listener(impl->playback,
&impl->playback_listener,
&playback_events, impl);
}
impl->sink = pw_stream_new(impl->core,
"Echo-Cancel Sink", impl->sink_props);
@ -878,14 +890,15 @@ static int setup_streams(struct impl *impl)
if ((res = pw_stream_connect(impl->sink,
PW_DIRECTION_INPUT,
PW_ID_ANY,
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
impl->playback != NULL ?
PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS :
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS,
params, n_params)) < 0) {
spa_pod_dynamic_builder_clean(&b);
return res;
}
if ((res = pw_stream_connect(impl->playback,
if (impl->playback != NULL && (res = pw_stream_connect(impl->playback,
PW_DIRECTION_OUTPUT,
PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT |
@ -1084,6 +1097,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
goto error;
}
impl->monitor_mode = false;
if ((str = pw_properties_get(props, "monitor.mode")) != NULL)
impl->monitor_mode = pw_properties_parse_bool(str);
impl->module = module;
impl->context = context;
@ -1115,9 +1132,15 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
if (pw_properties_get(impl->sink_props, PW_KEY_NODE_DESCRIPTION) == NULL)
pw_properties_set(impl->sink_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Sink");
if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL)
pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS,
impl->monitor_mode ? "Stream/Input/Audio" : "Audio/Sink");
if (pw_properties_get(impl->sink_props, "resample.prefill") == NULL)
pw_properties_set(impl->sink_props, "resample.prefill", "true");
if (impl->monitor_mode) {
pw_properties_set(impl->sink_props, PW_KEY_NODE_PASSIVE, "true");
pw_properties_set(impl->sink_props, PW_KEY_STREAM_MONITOR, "true");
pw_properties_set(impl->sink_props, PW_KEY_STREAM_CAPTURE_SINK, "true");
}
if ((str = pw_properties_get(props, "aec.method")) != NULL)
pw_log_warn("aec.method is not supported anymore use library.name");