Skip to content

Commit 1ae28d3

Browse files
committed
Use params to get target channels
The audio.channels property may not exitst. Closes #74
1 parent e686237 commit 1ae28d3

File tree

3 files changed

+106
-52
lines changed

3 files changed

+106
-52
lines changed

src/pipewire-audio-capture-app.c

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
#include "pipewire-audio.h"
2222

23+
#include <spa/debug/types.h>
24+
2325
#include <util/dstr.h>
2426

2527
/* Source for capturing applciation audio using PipeWire */
@@ -549,52 +551,78 @@ static void destroy_capture_sink(struct obs_pw_audio_capture_app *pwac)
549551
/* ------------------------------------------------- */
550552

551553
/* Default system sink */
552-
static void on_default_sink_info_cb(void *data, const struct pw_node_info *info)
554+
static void on_default_sink_param_cb(void *data, int seq, uint32_t id, uint32_t index, uint32_t next,
555+
const struct spa_pod *param)
553556
{
554-
if ((info->change_mask & PW_NODE_CHANGE_MASK_PROPS) == 0 || !info->props || !info->props->n_items) {
557+
UNUSED_PARAMETER(seq);
558+
UNUSED_PARAMETER(index);
559+
UNUSED_PARAMETER(next);
560+
561+
if (id != SPA_PARAM_EnumFormat) {
555562
return;
556563
}
557564

558565
struct obs_pw_audio_capture_app *pwac = data;
559566

560-
/** Use stereo if
561-
* - The default sink uses the Pro Audio profile, since all streams will be configured to use stereo
562-
* https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#what-is-the-pro-audio-profile
563-
* - The default sink doesn't have the needed props and there isn't already an app capture sink */
567+
uint32_t media_type = 0, parsed_id = 0, channels = 0;
568+
struct spa_pod *position_pod = NULL;
564569

565-
const char *channels = spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS);
566-
const char *position = spa_dict_lookup(info->props, SPA_KEY_AUDIO_POSITION);
567-
if (!channels || !position) {
568-
if (pwac->sink.proxy) {
569-
return;
570+
struct spa_pod_parser p;
571+
spa_pod_parser_pod(&p, param);
572+
573+
spa_pod_parser_get_object(&p, SPA_TYPE_OBJECT_Format, &parsed_id, SPA_FORMAT_mediaType, SPA_POD_Id(&media_type),
574+
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&channels), SPA_FORMAT_AUDIO_position,
575+
SPA_POD_OPT_Pod(&position_pod));
576+
577+
if (parsed_id != SPA_PARAM_EnumFormat || media_type != SPA_MEDIA_TYPE_audio || !channels || !position_pod) {
578+
goto stereo_fallback;
579+
}
580+
581+
uint32_t position_n = 0;
582+
uint32_t *position_arr = spa_pod_get_array(position_pod, &position_n);
583+
584+
struct dstr position_str;
585+
dstr_init(&position_str);
586+
587+
for (size_t i = 0; i < position_n; i++) {
588+
const char *chn = spa_debug_type_find_short_name(spa_type_audio_channel, position_arr[i]);
589+
590+
if (strstr(chn, "AUX") != NULL) {
591+
// Sink is configured for pro audio, use stereo
592+
// https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/FAQ#what-is-the-pro-audio-profile
593+
dstr_copy(&position_str, "FL,FR");
594+
break;
595+
}
596+
597+
dstr_cat(&position_str, chn);
598+
599+
if (position_n - 1 != i) {
600+
dstr_cat_ch(&position_str, ',');
570601
}
571-
channels = "2";
572-
position = "FL,FR";
573-
} else if (astrstri(position, "AUX")) {
574-
/* Pro Audio sinks use AUX0,AUX1... and so on as their position (see link above) */
575-
channels = "2";
576-
position = "FL,FR";
577602
}
578603

579-
uint32_t c = strtoul(channels, NULL, 10);
580-
if (!c) {
581-
return;
604+
if (channels != pwac->sink.channels || dstr_cmpi(&position_str, pwac->sink.position.array) != 0) {
605+
destroy_capture_sink(pwac);
606+
make_capture_sink(pwac, channels, position_str.array);
582607
}
583608

584-
/* No need to create a new capture sink if the channels are the same */
585-
if (pwac->sink.channels == c && !dstr_is_empty(&pwac->sink.position) &&
586-
dstr_cmp(&pwac->sink.position, position) == 0) {
609+
dstr_free(&position_str);
610+
return;
611+
612+
stereo_fallback:
613+
if (pwac->sink.proxy) {
587614
return;
588615
}
589616

590-
destroy_capture_sink(pwac);
617+
blog(LOG_WARNING, "[pipewire-audio] Could not parse format of default sink. Falling back to stereo.");
591618

592-
make_capture_sink(pwac, c, position);
619+
destroy_capture_sink(pwac);
620+
make_capture_sink(pwac, 2, "[FL,FR]");
593621
}
594622

595623
static const struct pw_node_events default_sink_events = {
596624
PW_VERSION_NODE_EVENTS,
597-
.info = on_default_sink_info_cb,
625+
.param = on_default_sink_param_cb,
598626
};
599627

600628
static void on_default_sink_proxy_removed_cb(void *data)
@@ -661,6 +689,8 @@ static void default_node_cb(void *data, const char *name)
661689
pwac);
662690
pw_proxy_add_listener(pwac->default_sink.proxy, &pwac->default_sink.proxy_listener, &default_sink_proxy_events,
663691
pwac);
692+
693+
pw_node_subscribe_params((struct pw_node *)pwac->default_sink.proxy, (uint32_t[]){SPA_PARAM_EnumFormat}, 1);
664694
}
665695
/* ------------------------------------------------- */
666696

src/pipewire-audio-capture-device.c

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ static void start_streaming(struct obs_pw_audio_capture_device *pwac, struct tar
7878
pwac->connected_serial = SPA_ID_INVALID;
7979
}
8080

81-
if (!node->channels) {
82-
return;
83-
}
84-
8581
if (obs_pw_audio_stream_connect(&pwac->pw.audio, node->id, node->serial, node->channels) == 0) {
8682
pwac->connected_serial = node->serial;
8783
blog(LOG_INFO, "[pipewire-audio] %p streaming from %u", pwac->pw.audio.stream, node->serial);
@@ -124,24 +120,33 @@ struct target_node *get_node_by_serial(struct obs_pw_audio_capture_device *pwac,
124120
}
125121

126122
/* Target node */
127-
static void on_node_info_cb(void *data, const struct pw_node_info *info)
123+
static void on_node_param_cb(void *data, int seq, uint32_t id, uint32_t index, uint32_t next,
124+
const struct spa_pod *param)
128125
{
129-
if ((info->change_mask & PW_NODE_CHANGE_MASK_PROPS) == 0 || !info->props || !info->props->n_items) {
130-
return;
131-
}
126+
UNUSED_PARAMETER(seq);
127+
UNUSED_PARAMETER(index);
128+
UNUSED_PARAMETER(next);
132129

133-
const char *channels = spa_dict_lookup(info->props, PW_KEY_AUDIO_CHANNELS);
134-
if (!channels) {
130+
if (id != SPA_PARAM_EnumFormat) {
135131
return;
136132
}
137133

138-
uint32_t c = strtoul(channels, NULL, 10);
139-
140134
struct target_node *n = data;
141-
if (n->channels == c) {
142-
return;
135+
136+
struct spa_pod_parser p;
137+
spa_pod_parser_pod(&p, param);
138+
139+
uint32_t parsed_id = 0, channels = 0, media_type = 0;
140+
141+
spa_pod_parser_get_object(&p, SPA_TYPE_OBJECT_Format, &parsed_id, SPA_FORMAT_mediaType, SPA_POD_Id(&media_type),
142+
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&channels));
143+
144+
if (media_type != SPA_MEDIA_TYPE_audio) {
145+
blog(LOG_WARNING,
146+
"[pipewire-audio] Could not parse target node format. Channels may be mapped incorrectly.");
143147
}
144-
n->channels = c;
148+
149+
n->channels = channels;
145150

146151
struct obs_pw_audio_capture_device *pwac = n->pwac;
147152

@@ -161,7 +166,7 @@ static void on_node_info_cb(void *data, const struct pw_node_info *info)
161166

162167
static const struct pw_node_events node_events = {
163168
PW_VERSION_NODE_EVENTS,
164-
.info = on_node_info_cb,
169+
.param = on_node_param_cb,
165170
};
166171

167172
static void node_destroy_cb(void *data)
@@ -203,6 +208,8 @@ static void register_target_node(struct obs_pw_audio_capture_device *pwac, const
203208

204209
spa_zero(n->node_listener);
205210
pw_proxy_add_object_listener(node_proxy, &n->node_listener, &node_events, n);
211+
212+
pw_node_subscribe_params((struct pw_node *)node_proxy, (uint32_t[]){SPA_PARAM_EnumFormat}, 1);
206213
}
207214
/* ------------------------------------------------- */
208215

src/pipewire-audio.c

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -268,22 +268,39 @@ static const struct pw_stream_events stream_events = {
268268
int obs_pw_audio_stream_connect(struct obs_pw_audio_stream *s, uint32_t target_id, uint32_t target_serial,
269269
uint32_t audio_channels)
270270
{
271+
if (audio_channels == 0) {
272+
blog(LOG_WARNING,
273+
"[pipewire-audio] Stream %p connecting without channel info. Channels may be mapped incorrectly.",
274+
s);
275+
}
276+
271277
enum spa_audio_channel pos[8];
272-
obs_channels_to_spa_audio_position(pos, audio_channels);
273278

274279
uint8_t buffer[2048];
275280
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
276281
const struct spa_pod *params[1];
277282

278-
params[0] = spa_pod_builder_add_object(
279-
&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType,
280-
SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
281-
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(audio_channels), SPA_FORMAT_AUDIO_position,
282-
SPA_POD_Array(sizeof(enum spa_audio_channel), SPA_TYPE_Id, audio_channels, pos),
283-
SPA_FORMAT_AUDIO_format,
284-
SPA_POD_CHOICE_ENUM_Id(9, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16_LE,
285-
SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_U8P,
286-
SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32P));
283+
if (audio_channels) {
284+
obs_channels_to_spa_audio_position(pos, audio_channels);
285+
286+
params[0] = spa_pod_builder_add_object(
287+
&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType,
288+
SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
289+
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(audio_channels), SPA_FORMAT_AUDIO_position,
290+
SPA_POD_Array(sizeof(enum spa_audio_channel), SPA_TYPE_Id, audio_channels, pos),
291+
SPA_FORMAT_AUDIO_format,
292+
SPA_POD_CHOICE_ENUM_Id(9, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16_LE,
293+
SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_U8P,
294+
SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32P));
295+
} else {
296+
params[0] = spa_pod_builder_add_object(
297+
&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType,
298+
SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
299+
SPA_FORMAT_AUDIO_format,
300+
SPA_POD_CHOICE_ENUM_Id(9, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16_LE,
301+
SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_U8P,
302+
SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_F32P));
303+
}
287304

288305
struct pw_properties *stream_props = pw_properties_new(NULL, NULL);
289306
pw_properties_setf(stream_props, PW_KEY_TARGET_OBJECT, "%u", target_serial);

0 commit comments

Comments
 (0)