From dd1273da13184ca7ef918e3c98499b0c791fe1b2 Mon Sep 17 00:00:00 2001 From: dimtpap Date: Sun, 6 Oct 2024 21:48:00 +0300 Subject: [PATCH] Reconfigure the adapter with the wanted position Instead of having to know the number of audio channels a device has when connecting to set the position, get the info in on_param_changed and reconfigure the adapter with the wanted position This only works after https://gitlab.freedesktop.org/pipewire/pipewire/-/commit/088d741cda1ad5cac715d36615a28285ffb9b233 --- src/pipewire-audio-capture-app.c | 2 +- src/pipewire-audio-capture-device.c | 86 +++++++++++++---------------- src/pipewire-audio.c | 65 +++++++++++----------- src/pipewire-audio.h | 3 +- 4 files changed, 72 insertions(+), 84 deletions(-) diff --git a/src/pipewire-audio-capture-app.c b/src/pipewire-audio-capture-app.c index 8c2f592..a62389b 100644 --- a/src/pipewire-audio-capture-app.c +++ b/src/pipewire-audio-capture-app.c @@ -527,7 +527,7 @@ static bool make_capture_sink(struct obs_pw_audio_capture_app *pwac, uint32_t ch pwac->sink.autoconnect_targets = true; - if (obs_pw_audio_stream_connect(&pwac->pw.audio, pwac->sink.id, pwac->sink.serial, channels) < 0) { + if (obs_pw_audio_stream_connect(&pwac->pw.audio, pwac->sink.id, pwac->sink.serial) < 0) { blog(LOG_WARNING, "[pipewire] Error connecting stream %p to app capture sink %u", pwac->pw.audio.stream, pwac->sink.id); } diff --git a/src/pipewire-audio-capture-device.c b/src/pipewire-audio-capture-device.c index 1dfefc0..03d78cd 100644 --- a/src/pipewire-audio-capture-device.c +++ b/src/pipewire-audio-capture-device.c @@ -25,11 +25,10 @@ /* Source for capturing device audio using PipeWire */ struct target_node { - const char *friendly_name; - const char *name; + struct dstr friendly_name; + struct dstr name; uint32_t serial; uint32_t id; - uint32_t channels; struct spa_hook node_listener; @@ -66,7 +65,7 @@ struct obs_pw_audio_capture_device { static void start_streaming(struct obs_pw_audio_capture_device *pwac, struct target_node *node) { - dstr_copy(&pwac->target_name, node->name); + dstr_copy_dstr(&pwac->target_name, &node->name); if (pw_stream_get_state(pwac->pw.audio.stream, NULL) != PW_STREAM_STATE_UNCONNECTED) { if (node->serial == pwac->connected_serial) { @@ -78,11 +77,7 @@ static void start_streaming(struct obs_pw_audio_capture_device *pwac, struct tar pwac->connected_serial = SPA_ID_INVALID; } - if (!node->channels) { - return; - } - - if (obs_pw_audio_stream_connect(&pwac->pw.audio, node->id, node->serial, node->channels) == 0) { + if (obs_pw_audio_stream_connect(&pwac->pw.audio, node->id, node->serial) == 0) { pwac->connected_serial = node->serial; blog(LOG_INFO, "[pipewire] %p streaming from %u", pwac->pw.audio.stream, node->serial); } else { @@ -100,7 +95,7 @@ struct target_node *get_node_by_name(struct obs_pw_audio_capture_device *pwac, c struct target_node *node; while (obs_pw_audio_proxy_list_iter_next(&iter, (void **)&node)) { - if (strcmp(node->name, name) == 0) { + if (!dstr_is_empty(&node->name) && dstr_cmp(&node->name, name) == 0) { return node; } } @@ -130,28 +125,37 @@ static void on_node_info_cb(void *data, const struct pw_node_info *info) return; } - const char *channels = spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS); - if (!channels) { - return; - } - - uint32_t c = strtoul(channels, NULL, 10); - struct target_node *n = data; - if (n->channels == c) { + + const char *serial_str = spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL); + if (!serial_str) { + blog(LOG_WARNING, "[pipewire] No object serial found on node %u", n->id); return; } - n->channels = c; + uint32_t serial = strtoul(serial_str, NULL, 10); + + const char *node_name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME); + const char *node_friendly_name = spa_dict_lookup(info->props, PW_KEY_NODE_NICK); + if (!node_friendly_name) { + node_friendly_name = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION); + if (!node_friendly_name) { + node_friendly_name = node_name; + } + } + + dstr_copy(&n->name, node_name); + dstr_copy(&n->friendly_name, node_friendly_name); + n->serial = serial; struct obs_pw_audio_capture_device *pwac = n->pwac; bool not_streamed = pwac->connected_serial != n->serial; bool has_default_node_name = !dstr_is_empty(&pwac->default_info.name) && - dstr_cmp(&pwac->default_info.name, n->name) == 0; + dstr_cmp(&pwac->default_info.name, node_name) == 0; bool is_new_default_node = not_streamed && has_default_node_name; bool stream_is_unconnected = pw_stream_get_state(pwac->pw.audio.stream, NULL) == PW_STREAM_STATE_UNCONNECTED; - bool node_has_target_name = !dstr_is_empty(&pwac->target_name) && dstr_cmp(&pwac->target_name, n->name) == 0; + bool node_has_target_name = !dstr_is_empty(&pwac->target_name) && dstr_cmp(&pwac->target_name, node_name) == 0; if ((pwac->default_info.autoconnect && is_new_default_node) || (stream_is_unconnected && node_has_target_name)) { start_streaming(pwac, n); @@ -177,12 +181,11 @@ static void node_destroy_cb(void *data) spa_hook_remove(&n->node_listener); - bfree((void *)n->friendly_name); - bfree((void *)n->name); + dstr_free(&n->friendly_name); + dstr_free(&n->name); } -static void register_target_node(struct obs_pw_audio_capture_device *pwac, const char *friendly_name, const char *name, - uint32_t object_serial, uint32_t global_id) +static void register_target_node(struct obs_pw_audio_capture_device *pwac, uint32_t global_id) { struct pw_proxy *node_proxy = pw_registry_bind(pwac->pw.registry, global_id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, sizeof(struct target_node)); @@ -191,11 +194,10 @@ static void register_target_node(struct obs_pw_audio_capture_device *pwac, const } struct target_node *n = pw_proxy_get_user_data(node_proxy); - n->friendly_name = bstrdup(friendly_name); - n->name = bstrdup(name); + dstr_init(&n->friendly_name); + dstr_init(&n->name); + n->serial = SPA_ID_INVALID; n->id = global_id; - n->serial = object_serial; - n->channels = 0; n->pwac = pwac; obs_pw_audio_proxy_list_append(&pwac->targets, node_proxy); @@ -238,9 +240,8 @@ static void on_global_cb(void *data, uint32_t id, uint32_t permissions, const ch } if (strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { - const char *node_name, *media_class; - if (!(node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME)) || - !(media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS))) { + const char *media_class; + if (!(media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS))) { return; } @@ -250,22 +251,7 @@ static void on_global_cb(void *data, uint32_t id, uint32_t permissions, const ch (pwac->capture_type == CAPTURE_TYPE_OUTPUT && (strcmp(media_class, "Audio/Sink") == 0 || strcmp(media_class, "Audio/Duplex") == 0))) { - const char *ser = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL); - if (!ser) { - blog(LOG_WARNING, "[pipewire] No object serial found on node %u", id); - return; - } - uint32_t object_serial = strtoul(ser, NULL, 10); - - const char *node_friendly_name = spa_dict_lookup(props, PW_KEY_NODE_NICK); - if (!node_friendly_name) { - node_friendly_name = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); - if (!node_friendly_name) { - node_friendly_name = node_name; - } - } - - register_target_node(pwac, node_friendly_name, node_name, object_serial, id); + register_target_node(pwac, id); } } else if (strcmp(type, PW_TYPE_INTERFACE_Metadata) == 0) { const char *name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); @@ -364,7 +350,9 @@ static obs_properties_t *pipewire_audio_capture_properties(void *data) struct target_node *node; while (obs_pw_audio_proxy_list_iter_next(&iter, (void **)&node)) { - obs_property_list_add_int(targets_list, node->friendly_name, node->serial); + if (node->serial != SPA_ID_INVALID) { + obs_property_list_add_int(targets_list, node->friendly_name.array, node->serial); + } } pw_thread_loop_unlock(pwac->pw.thread_loop); diff --git a/src/pipewire-audio.c b/src/pipewire-audio.c index 36df21a..2e752a6 100644 --- a/src/pipewire-audio.c +++ b/src/pipewire-audio.c @@ -152,25 +152,6 @@ enum speaker_layout spa_to_obs_speakers(uint32_t channels) } } -bool spa_to_obs_pw_audio_info(struct obs_pw_audio_info *info, const struct spa_pod *param) -{ - struct spa_audio_info_raw audio_info; - - if (spa_format_audio_raw_parse(param, &audio_info) < 0) { - info->sample_rate = 0; - info->format = AUDIO_FORMAT_UNKNOWN; - info->speakers = SPEAKERS_UNKNOWN; - - return false; - } - - info->sample_rate = audio_info.rate; - info->speakers = spa_to_obs_speakers(audio_info.channels); - info->format = spa_to_obs_audio_format(audio_info.format); - - return true; -} - static void on_process_cb(void *data) { uint64_t now = os_gettime_ns(); @@ -238,12 +219,38 @@ static void on_param_changed_cb(void *data, uint32_t id, const struct spa_pod *p struct obs_pw_audio_stream *s = data; - if (!spa_to_obs_pw_audio_info(&s->info, param)) { - blog(LOG_WARNING, "[pipewire] Stream %p failed to parse audio format info", s->stream); - } else { - blog(LOG_INFO, "[pipewire] %p Got format: rate %u - channels %u - format %u", s->stream, s->info.sample_rate, - s->info.speakers, s->info.format); + struct spa_audio_info_raw info; + + if (spa_format_audio_raw_parse(param, &info) < 0) { + blog(LOG_WARNING, "[pipewire] Stream %p failed to parse format", s->stream); + + s->info.sample_rate = 0; + s->info.format = AUDIO_FORMAT_UNKNOWN; + s->info.speakers = SPEAKERS_UNKNOWN; + + return; + } + + if (info.channels > 8) { + info.channels = 8; } + + obs_channels_to_spa_audio_position(info.position, info.channels); + + uint8_t buffer[2048]; + struct spa_pod_builder b; + const struct spa_pod *params[1]; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); + pw_stream_update_params(s->stream, params, 1); + + s->info.sample_rate = info.rate; + s->info.speakers = spa_to_obs_speakers(info.channels); + s->info.format = spa_to_obs_audio_format(info.format); + + blog(LOG_INFO, "[pipewire] Stream %p negotiated format: Format: %u, Channels: %u, Rate: %u", s->stream, info.format, + info.channels, info.rate); } static void on_io_changed_cb(void *data, uint32_t id, void *area, uint32_t size) @@ -265,21 +272,15 @@ static const struct pw_stream_events stream_events = { .io_changed = on_io_changed_cb, }; -int obs_pw_audio_stream_connect(struct obs_pw_audio_stream *s, uint32_t target_id, uint32_t target_serial, - uint32_t audio_channels) +int obs_pw_audio_stream_connect(struct obs_pw_audio_stream *s, uint32_t target_id, uint32_t target_serial) { - enum spa_audio_channel pos[8]; - obs_channels_to_spa_audio_position(pos, audio_channels); - uint8_t buffer[2048]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_pod *params[1]; params[0] = spa_pod_builder_add_object( &b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_channels, - SPA_POD_Int(audio_channels), SPA_FORMAT_AUDIO_position, - SPA_POD_Array(sizeof(enum spa_audio_channel), SPA_TYPE_Id, audio_channels, pos), SPA_FORMAT_AUDIO_format, + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(8, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16_LE, SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32P)); diff --git a/src/pipewire-audio.h b/src/pipewire-audio.h index 422b484..30f8ed9 100644 --- a/src/pipewire-audio.h +++ b/src/pipewire-audio.h @@ -55,8 +55,7 @@ struct obs_pw_audio_stream { * Connect a stream with the default params * @return 0 on success, < 0 on error */ -int obs_pw_audio_stream_connect(struct obs_pw_audio_stream *s, uint32_t target_id, uint32_t target_serial, - uint32_t channels); +int obs_pw_audio_stream_connect(struct obs_pw_audio_stream *s, uint32_t target_id, uint32_t target_serial); /* ------------------------------------------------- */ /**