From 823dcd8843cd7edfea999af5466e65c4a700d2cf Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Fri, 10 Apr 2026 09:12:41 +0200 Subject: [PATCH] scheduler: make nodes move to IDLE when inactive When a node is inactive but linked to a driver, the only reason it is not being scheduled is because it is inactive. We already set up the links and negotiate the format and buffers to prepare going to RUNNING. This patch now also make the node go to IDLE, which makes the adapter negotiate a forma and buffers with the internal node. This makes things more symetrical, when linking a node, it becomes IDLE, when activating it becomes RUNNABLE, when inactive it goes back to IDLE. The switch to RUNNING will also be faster when things are already set up in the IDLE state. The main advantage is that it allows us to implement the startup of corked streams in pulseaudio better. Before this patch we had to set the stream to active to make it go through the Format and buffer negotiation and then quickly set it back to inactive, hopefully without skipping a cycle. After this patch, the corked stream goes all the way to IDLE, where it then waits to become active. See #4991 --- spa/plugins/audioconvert/audioadapter.c | 2 ++ spa/plugins/audioconvert/audioconvert.c | 2 ++ src/modules/module-protocol-pulse/pulse-server.c | 6 ++++-- src/modules/module-scheduler-v1.c | 14 ++++++++------ src/pipewire/impl-node.c | 2 +- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index ccc9f48df..51d87d4e3 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1086,6 +1086,8 @@ static int impl_node_send_command(void *object, const struct spa_command *comman spa_log_debug(this->log, "%p: suspending", this); break; case SPA_NODE_COMMAND_Pause: + if ((res = negotiate_format(this)) < 0) + return res; spa_log_debug(this->log, "%p: pausing", this); break; case SPA_NODE_COMMAND_Flush: diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 482200854..967587c14 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -2644,6 +2644,8 @@ static int impl_node_send_command(void *object, const struct spa_command *comman reset_node(this); SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: + if ((res = setup_convert(this)) < 0) + return res; this->started = false; break; case SPA_NODE_COMMAND_Flush: diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 2006a01cc..d1c7f7ccc 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1258,8 +1258,6 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * pw_stream_set_control(stream->stream, SPA_PROP_mute, 1, &val, 0); } - if (stream->corked) - stream_set_paused(stream, true, "cork after create"); /* if peer exists, reply immediately, otherwise reply when the link is created */ peer = find_linked(stream->client->manager, stream->id, stream->direction); @@ -1805,6 +1803,8 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui flags = 0; if (no_move) flags |= PW_STREAM_FLAG_DONT_RECONNECT; + if (corked) + flags |= PW_STREAM_FLAG_INACTIVE; if (sink_name != NULL) { if (o != NULL) @@ -2086,6 +2086,8 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint flags = 0; if (no_move) flags |= PW_STREAM_FLAG_DONT_RECONNECT; + if (corked) + flags |= PW_STREAM_FLAG_INACTIVE; if (direct_on_input_idx != SPA_ID_INVALID) { dont_inhibit_auto_suspend = false; diff --git a/src/modules/module-scheduler-v1.c b/src/modules/module-scheduler-v1.c index 0bfe43a4a..5756f7a56 100644 --- a/src/modules/module-scheduler-v1.c +++ b/src/modules/module-scheduler-v1.c @@ -95,12 +95,14 @@ struct impl { struct spa_hook module_listener; }; -static int ensure_state(struct pw_impl_node *node, bool running) +static int ensure_state(struct pw_impl_node *node, bool running, bool idle) { enum pw_node_state state = node->info.state; - if (node->active && node->runnable && - !SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE) && running) + bool need_config = SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE); + if (node->active && node->runnable && !need_config & running) state = PW_NODE_STATE_RUNNING; + else if (!node->active && !need_config & idle) + state = PW_NODE_STATE_IDLE; else if (state > PW_NODE_STATE_IDLE) state = PW_NODE_STATE_IDLE; return pw_impl_node_set_state(node, state); @@ -362,7 +364,7 @@ static void remove_from_driver(struct pw_context *context, struct spa_list *node spa_list_consume(n, nodes, sort_link) { spa_list_remove(&n->sort_link); pw_impl_node_set_driver(n, NULL); - ensure_state(n, false); + ensure_state(n, false, false); } } @@ -957,7 +959,7 @@ again: continue; pw_log_debug("%p: follower %p: active:%d '%s'", context, s, s->active, s->name); - ensure_state(s, running); + ensure_state(s, running, true); } if (transport != PW_NODE_ACTIVATION_COMMAND_NONE) { @@ -966,7 +968,7 @@ again: } /* now that all the followers are ready, start the driver */ - ensure_state(n, running); + ensure_state(n, running, false); } } diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 2cfb0c2c8..fb9cf56e4 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -306,7 +306,7 @@ static int idle_node(struct pw_impl_node *this) pw_node_state_as_string(impl->pending_state), this->pause_on_idle); - if (impl->pending_state <= PW_NODE_STATE_IDLE) + if (impl->pending_state == PW_NODE_STATE_IDLE) return 0; if (!this->pause_on_idle)