Skip to content

Commit

Permalink
Reconfigure the adapter with the wanted position
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dimtpap committed Oct 6, 2024
1 parent d5d51d4 commit 9ccaa47
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 84 deletions.
2 changes: 1 addition & 1 deletion src/pipewire-audio-capture-app.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
86 changes: 37 additions & 49 deletions src/pipewire-audio-capture-device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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);
Expand All @@ -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));
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
65 changes: 33 additions & 32 deletions src/pipewire-audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand All @@ -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));
Expand Down
3 changes: 1 addition & 2 deletions src/pipewire-audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
/* ------------------------------------------------- */

/**
Expand Down

0 comments on commit 9ccaa47

Please sign in to comment.