diff options
author | dec05eba <dec05eba@protonmail.com> | 2024-11-20 19:09:40 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2024-11-20 19:09:42 +0100 |
commit | cbf4cba5a97ed7ed9303627ceb644aff9c1fbf4e (patch) | |
tree | 38978d5c503ea54e100c660cd716c5bc6dd7d6b2 /src/pipewire_audio.c | |
parent | 6e0f5413d922f4c1d24bf236f98c9ead3ab0a447 (diff) |
Allow recording app audio with -a, deprecate -aa and -aai
Use -a "app:brave" instead of -aa "brave".
Use -a "app-inverse:brave" instead of -aai "brave".
This now allows merging audio devices and app audio into the same audio
track.
Diffstat (limited to 'src/pipewire_audio.c')
-rw-r--r-- | src/pipewire_audio.c | 149 |
1 files changed, 95 insertions, 54 deletions
diff --git a/src/pipewire_audio.c b/src/pipewire_audio.c index 4f9f05f..4af41e4 100644 --- a/src/pipewire_audio.c +++ b/src/pipewire_audio.c @@ -44,44 +44,44 @@ static gsr_pipewire_audio_port* gsr_pipewire_audio_get_node_port_by_name(gsr_pip } static bool requested_link_matches_name_case_insensitive(const gsr_pipewire_audio_requested_link *requested_link, const char *name) { - for(int i = 0; i < requested_link->num_app_names; ++i) { - if(strcasecmp(requested_link->app_names[i], name) == 0) + for(int i = 0; i < requested_link->num_output_names; ++i) { + if(strcasecmp(requested_link->output_names[i], name) == 0) return true; } return false; } static void gsr_pipewire_audio_create_link(gsr_pipewire_audio *self, const gsr_pipewire_audio_requested_link *requested_link) { - const gsr_pipewire_audio_node_type requested_link_node_type = requested_link->output_type == GSR_PIPEWIRE_AUDIO_LINK_OUTPUT_TYPE_STREAM ? GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT : GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK; - const gsr_pipewire_audio_node *stream_input_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, requested_link->output_name, requested_link_node_type); + const gsr_pipewire_audio_node_type requested_link_node_type = requested_link->input_type == GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM ? GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT : GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE; + const gsr_pipewire_audio_node *stream_input_node = gsr_pipewire_audio_get_node_by_name_case_insensitive(self, requested_link->input_name, requested_link_node_type); if(!stream_input_node) return; - const gsr_pipewire_audio_port *stream_input_fl_port = NULL; - const gsr_pipewire_audio_port *stream_input_fr_port = NULL; + const gsr_pipewire_audio_port *input_fl_port = NULL; + const gsr_pipewire_audio_port *input_fr_port = NULL; - switch(requested_link->output_type) { - case GSR_PIPEWIRE_AUDIO_LINK_OUTPUT_TYPE_STREAM: { - stream_input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "input_FL"); - stream_input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "input_FR"); + switch(requested_link->input_type) { + case GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM: { + input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "input_FL"); + input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "input_FR"); break; } - case GSR_PIPEWIRE_AUDIO_LINK_OUTPUT_TYPE_SINK: { - stream_input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "playback_FL"); - stream_input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "playback_FR"); + case GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK: { + input_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "playback_FL"); + input_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, stream_input_node->id, "playback_FR"); break; } } - if(!stream_input_fl_port || !stream_input_fr_port) + if(!input_fl_port || !input_fr_port) return; for(int i = 0; i < self->num_stream_nodes; ++i) { - const gsr_pipewire_audio_node *app_node = &self->stream_nodes[i]; - if(app_node->type != GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT) + const gsr_pipewire_audio_node *output_node = &self->stream_nodes[i]; + if(output_node->type != requested_link->output_type) continue; - const bool requested_link_matches_app = requested_link_matches_name_case_insensitive(requested_link, app_node->name); + const bool requested_link_matches_app = requested_link_matches_name_case_insensitive(requested_link, output_node->name); if(requested_link->inverted) { if(requested_link_matches_app) continue; @@ -90,9 +90,30 @@ static void gsr_pipewire_audio_create_link(gsr_pipewire_audio *self, const gsr_p continue; } - const gsr_pipewire_audio_port *app_output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, app_node->id, "output_FL"); - const gsr_pipewire_audio_port *app_output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, app_node->id, "output_FR"); - if(!app_output_fl_port || !app_output_fr_port) + const gsr_pipewire_audio_port *output_fl_port = NULL; + const gsr_pipewire_audio_port *output_fr_port = NULL; + + switch(requested_link->output_type) { + case GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT: + output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "output_FL"); + output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "output_FR"); + break; + case GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT: + output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FL"); + output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FR"); + break; + case GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE: { + output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FL"); + output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "monitor_FR"); + if(!output_fl_port || !output_fr_port) { + output_fl_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "capture_FL"); + output_fr_port = gsr_pipewire_audio_get_node_port_by_name(self, output_node->id, "capture_FR"); + } + break; + } + } + + if(!output_fl_port || !output_fr_port) continue; // TODO: Detect if link already exists before so we dont create these proxies when not needed @@ -101,8 +122,8 @@ static void gsr_pipewire_audio_create_link(gsr_pipewire_audio *self, const gsr_p // TODO: error check and cleanup { struct pw_properties *props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", app_output_fl_port->id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", stream_input_fl_port->id); + pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", output_fl_port->id); + pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", input_fl_port->id); // TODO: Clean this up when removing node struct pw_proxy *proxy = pw_core_create_object(self->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0); //self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync); @@ -111,8 +132,8 @@ static void gsr_pipewire_audio_create_link(gsr_pipewire_audio *self, const gsr_p { struct pw_properties *props = pw_properties_new(NULL, NULL); - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", app_output_fr_port->id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", stream_input_fr_port->id); + pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", output_fr_port->id); + pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", input_fr_port->id); // TODO: Clean this up when removing node struct pw_proxy *proxy = pw_core_create_object(self->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0); //self->server_version_sync = pw_core_sync(self->core, PW_ID_CORE, self->server_version_sync); @@ -145,7 +166,8 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const bool is_stream_output = media_class && strcmp(media_class, "Stream/Output/Audio") == 0; const bool is_stream_input = media_class && strcmp(media_class, "Stream/Input/Audio") == 0; const bool is_sink = media_class && strcmp(media_class, "Audio/Sink") == 0; - if(self->num_stream_nodes < GSR_PIPEWIRE_AUDIO_MAX_STREAM_NODES && node_name && (is_stream_output || is_stream_input || is_sink)) { + const bool is_source = media_class && strcmp(media_class, "Audio/Source") == 0; + if(self->num_stream_nodes < GSR_PIPEWIRE_AUDIO_MAX_STREAM_NODES && node_name && (is_stream_output || is_stream_input || is_sink || is_source)) { //const char *application_binary = spa_dict_lookup(props, PW_KEY_APP_PROCESS_BINARY); //const char *application_name = spa_dict_lookup(props, PW_KEY_APP_NAME); //fprintf(stderr, " node name: %s, app binary: %s, app name: %s\n", node_name, application_binary, application_name); @@ -158,8 +180,8 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, self->stream_nodes[self->num_stream_nodes].type = GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT; else if(is_stream_input) self->stream_nodes[self->num_stream_nodes].type = GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_INPUT; - else if(is_sink) - self->stream_nodes[self->num_stream_nodes].type = GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK; + else if(is_sink || is_source) + self->stream_nodes[self->num_stream_nodes].type = GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE; ++self->num_stream_nodes; gsr_pipewire_audio_create_links(self); @@ -323,11 +345,11 @@ void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self) { self->num_ports = 0; for(int i = 0; i < self->num_requested_links; ++i) { - for(int j = 0; j < self->requested_links[i].num_app_names; ++j) { - free(self->requested_links[i].app_names[j]); + for(int j = 0; j < self->requested_links[i].num_output_names; ++j) { + free(self->requested_links[i].output_names[j]); } - free(self->requested_links[i].app_names); - free(self->requested_links[i].output_name); + free(self->requested_links[i].output_names); + free(self->requested_links[i].input_name); } self->num_requested_links = 0; @@ -336,29 +358,44 @@ void gsr_pipewire_audio_deinit(gsr_pipewire_audio *self) { #endif } -static bool gsr_pipewire_audio_add_link_from_apps_to_output(gsr_pipewire_audio *self, const char **app_names_output, int num_app_names_output, const char *output_name, gsr_pipewire_audio_link_output_type output_type, bool inverted) { +static bool string_remove_suffix(char *str, const char *suffix) { + int str_len = strlen(str); + int suffix_len = strlen(suffix); + if(str_len >= suffix_len && memcmp(str + str_len - suffix_len, suffix, suffix_len) == 0) { + str[str_len - suffix_len] = '\0'; + return true; + } else { + return false; + } +} + +static bool gsr_pipewire_audio_add_link_from_apps_to_output(gsr_pipewire_audio *self, const char **output_names, int num_output_names, const char *input_name, gsr_pipewire_audio_node_type output_type, gsr_pipewire_audio_link_input_type input_type, bool inverted) { if(self->num_requested_links >= GSR_PIPEWIRE_AUDIO_MAX_REQUESTED_LINKS) return false; - char **app_names_output_copy = calloc(num_app_names_output, sizeof(char*)); - if(!app_names_output_copy) + char **output_names_copy = calloc(num_output_names, sizeof(char*)); + if(!output_names_copy) return false; - char *output_name_copy = strdup(output_name); - if(!output_name_copy) + char *input_name_copy = strdup(input_name); + if(!input_name_copy) goto error; - for(int i = 0; i < num_app_names_output; ++i) { - app_names_output_copy[i] = strdup(app_names_output[i]); - if(!app_names_output_copy[i]) + for(int i = 0; i < num_output_names; ++i) { + output_names_copy[i] = strdup(output_names[i]); + if(!output_names_copy[i]) goto error; + + if(output_type == GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE) + string_remove_suffix(output_names_copy[i], ".monitor"); } pw_thread_loop_lock(self->thread_loop); - self->requested_links[self->num_requested_links].app_names = app_names_output_copy; - self->requested_links[self->num_requested_links].num_app_names = num_app_names_output; - self->requested_links[self->num_requested_links].output_name = output_name_copy; + self->requested_links[self->num_requested_links].output_names = output_names_copy; + self->requested_links[self->num_requested_links].num_output_names = num_output_names; + self->requested_links[self->num_requested_links].input_name = input_name_copy; self->requested_links[self->num_requested_links].output_type = output_type; + self->requested_links[self->num_requested_links].input_type = input_type; self->requested_links[self->num_requested_links].inverted = inverted; ++self->num_requested_links; gsr_pipewire_audio_create_link(self, &self->requested_links[self->num_requested_links - 1]); @@ -367,28 +404,32 @@ static bool gsr_pipewire_audio_add_link_from_apps_to_output(gsr_pipewire_audio * return true; error: - free(output_name_copy); - for(int i = 0; i < num_app_names_output; ++i) { - free(app_names_output_copy[i]); + free(input_name_copy); + for(int i = 0; i < num_output_names; ++i) { + free(output_names_copy[i]); } - free(app_names_output_copy); + free(output_names_copy); return false; } -bool gsr_pipewire_audio_add_link_from_apps_to_stream(gsr_pipewire_audio *self, const char **app_names_output, int num_app_names_output, const char *stream_name_input) { - return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names_output, num_app_names_output, stream_name_input, GSR_PIPEWIRE_AUDIO_LINK_OUTPUT_TYPE_STREAM, false); +bool gsr_pipewire_audio_add_link_from_apps_to_stream(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *stream_name_input) { + return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, stream_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM, false); +} + +bool gsr_pipewire_audio_add_link_from_apps_to_stream_inverted(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *stream_name_input) { + return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, stream_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_STREAM, true); } -bool gsr_pipewire_audio_add_link_from_apps_to_stream_inverted(gsr_pipewire_audio *self, const char **app_names_output, int num_app_names_output, const char *stream_name_input) { - return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names_output, num_app_names_output, stream_name_input, GSR_PIPEWIRE_AUDIO_LINK_OUTPUT_TYPE_STREAM, true); +bool gsr_pipewire_audio_add_link_from_apps_to_sink(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *sink_name_input) { + return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, false); } -bool gsr_pipewire_audio_add_link_from_apps_to_sink(gsr_pipewire_audio *self, const char **app_names_output, int num_app_names_output, const char *sink_name_input) { - return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names_output, num_app_names_output, sink_name_input, GSR_PIPEWIRE_AUDIO_LINK_OUTPUT_TYPE_SINK, false); +bool gsr_pipewire_audio_add_link_from_apps_to_sink_inverted(gsr_pipewire_audio *self, const char **app_names, int num_app_names, const char *sink_name_input) { + return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names, num_app_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_STREAM_OUTPUT, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, true); } -bool gsr_pipewire_audio_add_link_from_apps_to_sink_inverted(gsr_pipewire_audio *self, const char **app_names_output, int num_app_names_output, const char *sink_name_input) { - return gsr_pipewire_audio_add_link_from_apps_to_output(self, app_names_output, num_app_names_output, sink_name_input, GSR_PIPEWIRE_AUDIO_LINK_OUTPUT_TYPE_SINK, true); +bool gsr_pipewire_audio_add_link_from_sources_to_sink(gsr_pipewire_audio *self, const char **source_names, int num_source_names, const char *sink_name_input) { + return gsr_pipewire_audio_add_link_from_apps_to_output(self, source_names, num_source_names, sink_name_input, GSR_PIPEWIRE_AUDIO_NODE_TYPE_SINK_OR_SOURCE, GSR_PIPEWIRE_AUDIO_LINK_INPUT_TYPE_SINK, false); } void gsr_pipewire_audio_for_each_app(gsr_pipewire_audio *self, gsr_pipewire_audio_app_query_callback callback, void *userdata) { |