default-routes.c 22.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* PipeWire
 *
 * Copyright © 2020 Wim Taymans
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>

#include "config.h"

#include <spa/utils/hook.h>
#include <spa/utils/result.h>
Wim Taymans's avatar
Wim Taymans committed
37
#include <spa/utils/json.h>
38
39
40
41
42
43
44
45
46
47
48
#include <spa/pod/parser.h>
#include <spa/pod/builder.h>
#include <spa/debug/pod.h>

#include "pipewire/pipewire.h"
#include "extensions/metadata.h"

#include "media-session.h"

#define NAME		"default-routes"
#define SESSION_KEY	"default-routes"
49
#define PREFIX		"default.route."
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

#define SAVE_INTERVAL	1

struct impl {
	struct timespec now;

	struct sm_media_session *session;
	struct spa_hook listener;

	struct pw_context *context;
	struct spa_source *idle_timeout;

	struct spa_hook meta_listener;

	struct pw_properties *to_restore;
};

struct device {
	struct sm_device *obj;

	uint32_t id;
	struct impl *impl;
	char *name;

	struct spa_hook listener;
75

76
	uint32_t active_profile;
77
	char profile_name[128];
78

79
80
	uint32_t generation;
	struct pw_array route_info;
81
82
83
84
85
86
87
88
};

static void remove_idle_timeout(struct impl *impl)
{
	struct pw_loop *main_loop = pw_context_get_main_loop(impl->context);
	int res;

	if (impl->idle_timeout) {
89
		if ((res = sm_media_session_save_state(impl->session,
90
						SESSION_KEY, PREFIX, impl->to_restore)) < 0)
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
			pw_log_error("can't save "SESSION_KEY" state: %s", spa_strerror(res));
		pw_loop_destroy_source(main_loop, impl->idle_timeout);
		impl->idle_timeout = NULL;
	}
}

static void idle_timeout(void *data, uint64_t expirations)
{
	struct impl *impl = data;
	pw_log_debug(NAME " %p: idle timeout", impl);
	remove_idle_timeout(impl);
}

static void add_idle_timeout(struct impl *impl)
{
	struct timespec value;
	struct pw_loop *main_loop = pw_context_get_main_loop(impl->context);

	if (impl->idle_timeout == NULL)
		impl->idle_timeout = pw_loop_add_timer(main_loop, idle_timeout, impl);

	value.tv_sec = SAVE_INTERVAL;
	value.tv_nsec = 0;
	pw_loop_update_timer(main_loop, impl->idle_timeout, &value, NULL, false);
}

117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
static uint32_t channel_from_name(const char *name)
{
	int i;
	for (i = 0; spa_type_audio_channel[i].name; i++) {
		if (strcmp(name, spa_debug_type_short_name(spa_type_audio_channel[i].name)) == 0)
			return spa_type_audio_channel[i].type;
	}
	return SPA_AUDIO_CHANNEL_UNKNOWN;
}

static const char *channel_to_name(uint32_t channel)
{
	int i;
	for (i = 0; spa_type_audio_channel[i].name; i++) {
		if (spa_type_audio_channel[i].type == channel)
			return spa_debug_type_short_name(spa_type_audio_channel[i].name);
	}
	return "UNK";
}

137
138
139
struct route_info {
	uint32_t index;
	uint32_t generation;
140
	uint32_t available;
141
142
	enum spa_direction direction;
	char name[64];
143
	unsigned int restore:1;
144
	unsigned int save:1;
145
146
147
148
149
150
151
152
};

struct route {
	struct sm_param *p;
	uint32_t index;
	uint32_t device_id;
	enum spa_direction direction;
	const char *name;
153
	uint32_t priority;
154
155
156
157
	uint32_t available;
	struct spa_pod *props;
};

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#define ROUTE_INIT(__p) (struct route) {				\
			.p = (__p),					\
			.available = SPA_PARAM_AVAILABILITY_unknown,	\
		}

static struct route_info *find_route_info(struct device *dev, struct route *r)
{
	struct route_info *i;

	pw_array_for_each(i, &dev->route_info) {
		if (i->index == r->index)
			return i;
	}
	i = pw_array_add(&dev->route_info, sizeof(*i));
	if (i == NULL)
		return NULL;

	pw_log_info("device %d: new route %d '%s' found", dev->id, r->index, r->name);
	i->index = r->index;
177
178
	snprintf(i->name, sizeof(i->name), "%s", r->name);
	i->direction = r->direction;
179
180
181
182
183
184
185
	i->generation = dev->generation;
	i->available = r->available;
	i->restore = true;

	return i;
}

186
187
static int parse_route(struct sm_param *p, struct route *r)
{
188
	*r = ROUTE_INIT(p);
189
190
191
192
	return spa_pod_parse_object(p->param,
			SPA_TYPE_OBJECT_ParamRoute, NULL,
			SPA_PARAM_ROUTE_index, SPA_POD_Int(&r->index),
			SPA_PARAM_ROUTE_direction, SPA_POD_Id(&r->direction),
193
			SPA_PARAM_ROUTE_device, SPA_POD_Int(&r->device_id),
194
			SPA_PARAM_ROUTE_name, SPA_POD_String(&r->name),
195
196
			SPA_PARAM_ROUTE_priority,  SPA_POD_OPT_Int(&r->priority),
			SPA_PARAM_ROUTE_available,  SPA_POD_OPT_Id(&r->available),
197
198
199
			SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&r->props));
}

200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
static bool array_contains(struct spa_pod *pod, uint32_t val)
{
	uint32_t *vals, n_vals;
	uint32_t n;

	if (pod == NULL)
		return false;
	vals = spa_pod_get_array(pod, &n_vals);
	if (vals == NULL || n_vals == 0)
		return false;
	for (n = 0; n < n_vals; n++)
		if (vals[n] == val)
			return true;
	return false;
}

static int parse_enum_route(struct sm_param *p, uint32_t device_id, struct route *r)
{
	struct spa_pod *devices = NULL;
	int res;

	*r = ROUTE_INIT(p);
	if ((res = spa_pod_parse_object(p->param,
			SPA_TYPE_OBJECT_ParamRoute, NULL,
			SPA_PARAM_ROUTE_index, SPA_POD_Int(&r->index),
			SPA_PARAM_ROUTE_direction, SPA_POD_Id(&r->direction),
			SPA_PARAM_ROUTE_name, SPA_POD_String(&r->name),
			SPA_PARAM_ROUTE_priority,  SPA_POD_OPT_Int(&r->priority),
			SPA_PARAM_ROUTE_available,  SPA_POD_OPT_Id(&r->available),
			SPA_PARAM_ROUTE_devices, SPA_POD_OPT_Pod(&devices))) < 0)
		return res;

	if (!array_contains(devices, device_id))
		return -ENOENT;

	r->device_id = device_id;
	return 0;
}

239
240
241
242
243
static char *serialize_props(struct device *dev, const struct spa_pod *param)
{
	struct spa_pod_prop *prop;
	struct spa_pod_object *obj = (struct spa_pod_object *) param;
	float val = 0.0f;
244
	bool b = false, comma = false;
245
246
247
248
249
	char *ptr;
	size_t size;
	FILE *f;

        f = open_memstream(&ptr, &size);
250
	fprintf(f, "{");
251
252
253
254
255

	SPA_POD_OBJECT_FOREACH(obj, prop) {
		switch (prop->key) {
		case SPA_PROP_volume:
			spa_pod_get_float(&prop->value, &val);
256
			fprintf(f, "%s \"volume\": %f", (comma ? "," : ""), val);
257
258
259
			break;
		case SPA_PROP_mute:
			spa_pod_get_bool(&prop->value, &b);
260
			fprintf(f, "%s \"mute\": %s", (comma ? "," : ""), b ? "true" : "false");
261
262
263
264
265
266
267
268
			break;
		case SPA_PROP_channelVolumes:
		{
			uint32_t i, n_vals;
			float vals[SPA_AUDIO_MAX_CHANNELS];

			n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
					vals, SPA_AUDIO_MAX_CHANNELS);
269
270
			if (n_vals == 0)
				continue;
271

272
			fprintf(f, "%s \"volumes\": [", (comma ? "," : ""));
273
			for (i = 0; i < n_vals; i++)
274
				fprintf(f, "%s %f", (i == 0 ? "" : ","), vals[i]);
275
			fprintf(f, " ]");
276
277
			break;
		}
278
279
280
281
282
283
284
		case SPA_PROP_channelMap:
		{
			uint32_t i, n_vals;
			uint32_t map[SPA_AUDIO_MAX_CHANNELS];

			n_vals = spa_pod_copy_array(&prop->value, SPA_TYPE_Id,
					map, SPA_AUDIO_MAX_CHANNELS);
285
286
			if (n_vals == 0)
				continue;
287

288
			fprintf(f, "%s \"channels\": [", (comma ? "," : ""));
289
			for (i = 0; i < n_vals; i++)
290
				fprintf(f, "%s \"%s\"", (i == 0 ? "" : ","), channel_to_name(map[i]));
291
292
293
			fprintf(f, " ]");
			break;
		}
294
295
296
297
298
299
300
		case SPA_PROP_latencyOffsetNsec:
		{
			int64_t delay;
			spa_pod_get_long(&prop->value, &delay);
			fprintf(f, "%s \"latencyOffsetNsec\": %"PRIi64, (comma ? "," : ""), delay);
			break;
		}
301
		default:
302
			continue;
303
		}
304
		comma = true;
305
	}
306
	fprintf(f, " }");
307
308
309
310
        fclose(f);
	return ptr;
}

311
static int restore_route_params(struct device *dev, const char *val, uint32_t index, uint32_t device_id)
312
{
313
	struct spa_json it[3];
Wim Taymans's avatar
Wim Taymans committed
314
	char buf[1024], key[128];
315
	const char *value;
316
317
318
	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
	struct spa_pod_frame f[2];
	struct spa_pod *param;
319
320
321
322
323

	spa_json_init(&it[0], val, strlen(val));

	if (spa_json_enter_object(&it[0], &it[1]) <= 0)
                return -EINVAL;
324
325
326
327
328
329
330
331
332
333

	spa_pod_builder_push_object(&b, &f[0],
			SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route);
	spa_pod_builder_add(&b,
			SPA_PARAM_ROUTE_index, SPA_POD_Int(index),
			SPA_PARAM_ROUTE_device, SPA_POD_Int(device_id),
			0);
	spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_props, 0);
	spa_pod_builder_push_object(&b, &f[1],
			SPA_TYPE_OBJECT_Props, SPA_PARAM_Route);
334

Wim Taymans's avatar
Wim Taymans committed
335
336
	while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) {
		if (strcmp(key, "volume") == 0) {
337
338
339
			float vol;
			if (spa_json_get_float(&it[1], &vol) <= 0)
                                continue;
340
341
342
			spa_pod_builder_prop(&b, SPA_PROP_volume, 0);
			spa_pod_builder_float(&b, vol);
		}
Wim Taymans's avatar
Wim Taymans committed
343
		else if (strcmp(key, "mute") == 0) {
344
345
346
			bool mute;
			if (spa_json_get_bool(&it[1], &mute) <= 0)
                                continue;
347
348
349
			spa_pod_builder_prop(&b, SPA_PROP_mute, 0);
			spa_pod_builder_bool(&b, mute);
		}
Wim Taymans's avatar
Wim Taymans committed
350
		else if (strcmp(key, "volumes") == 0) {
351
352
353
354
			uint32_t n_vols;
			float vols[SPA_AUDIO_MAX_CHANNELS];

			if (spa_json_enter_array(&it[1], &it[2]) <= 0)
355
356
				continue;

357
358
359
360
			for (n_vols = 0; n_vols < SPA_AUDIO_MAX_CHANNELS; n_vols++) {
                                if (spa_json_get_float(&it[2], &vols[n_vols]) <= 0)
                                        break;
                        }
361
362
363
			if (n_vols == 0)
				continue;

364
365
366
			spa_pod_builder_prop(&b, SPA_PROP_channelVolumes, 0);
			spa_pod_builder_array(&b, sizeof(float), SPA_TYPE_Float,
					n_vols, vols);
367
		}
Wim Taymans's avatar
Wim Taymans committed
368
		else if (strcmp(key, "channels") == 0) {
369
370
371
372
373
374
375
376
377
378
379
380
			uint32_t n_ch;
			uint32_t map[SPA_AUDIO_MAX_CHANNELS];

			if (spa_json_enter_array(&it[1], &it[2]) <= 0)
				continue;

			for (n_ch = 0; n_ch < SPA_AUDIO_MAX_CHANNELS; n_ch++) {
				char chname[16];
                                if (spa_json_get_string(&it[2], chname, sizeof(chname)) <= 0)
                                        break;
				map[n_ch] = channel_from_name(chname);
                        }
381
382
383
			if (n_ch == 0)
				continue;

384
385
386
			spa_pod_builder_prop(&b, SPA_PROP_channelMap, 0);
			spa_pod_builder_array(&b, sizeof(uint32_t), SPA_TYPE_Id,
					n_ch, map);
387
388
389
390
391
392
393
		}
		else if (strcmp(key, "latencyOffsetNsec") == 0) {
			float delay;
			if (spa_json_get_float(&it[1], &delay) <= 0)
                                continue;
			spa_pod_builder_prop(&b, SPA_PROP_latencyOffsetNsec, 0);
			spa_pod_builder_long(&b, (int64_t)SPA_CLAMP(delay, INT64_MIN, INT64_MAX));
394
		} else {
395
396
			if (spa_json_next(&it[1], &value) <= 0)
                                break;
397
398
399
400
		}
	}
	spa_pod_builder_pop(&b, &f[1]);
	param = spa_pod_builder_pop(&b, &f[0]);
401
402
403

	if (pw_log_level_enabled(SPA_LOG_LEVEL_DEBUG))
		spa_debug_pod(2, NULL, param);
404
405
406
407
408
409

	pw_device_set_param((struct pw_node*)dev->obj->obj.proxy,
			SPA_PARAM_Route, 0, param);
	return 0;
}

410
411
412
struct profile {
	uint32_t index;
	const char *name;
413
	struct spa_pod *classes;
414
};
415

416
static int parse_profile(struct sm_param *p, struct profile *pr)
417
{
418
419
420
	int res;
	spa_zero(*pr);
	if ((res = spa_pod_parse_object(p->param,
421
			SPA_TYPE_OBJECT_ParamProfile, NULL,
422
423
424
425
426
			SPA_PARAM_PROFILE_index,   SPA_POD_Int(&pr->index),
			SPA_PARAM_PROFILE_name,    SPA_POD_String(&pr->name),
			SPA_PARAM_PROFILE_classes, SPA_POD_OPT_Pod(&pr->classes))) < 0)
		return res;
	return 0;
427
428
429
430
431
432
433
434
435
436
437
438
439
}

static int find_current_profile(struct device *dev, struct profile *pr)
{
	struct sm_param *p;
	spa_list_for_each(p, &dev->obj->param_list, link) {
		if (p->id == SPA_PARAM_Profile &&
		    parse_profile(p, pr) >= 0)
			return 0;
	}
	return -ENOENT;
}

440
static int restore_route(struct device *dev, struct route *r, bool save)
441
442
443
444
{
	struct impl *impl = dev->impl;
	char key[1024];
	const char *val;
445
446
447
448
	struct route_info *ri;

	if ((ri = find_route_info(dev, r)) == NULL)
		return -errno;
449

450
	snprintf(key, sizeof(key), PREFIX"%s:%s:%s", dev->name,
451
452
453
454
455
456
		r->direction == SPA_DIRECTION_INPUT ? "input" : "output", r->name);

	val = pw_properties_get(impl->to_restore, key);
	if (val == NULL)
		val = "{ \"volumes\": [ 0.4 ], \"mute\": false }";

457
	pw_log_info("device %d: restore route %d '%s' to %s", dev->id, r->index, key, val);
458
459

	restore_route_params(dev, val, r->index, r->device_id);
460
461
462
	ri->generation = dev->generation;
	ri->restore = false;
	ri->save = save;
463
464
465
466
467
468
469
470
471
472
473
474

	return 0;
}

static int save_route(struct device *dev, struct route *r)
{
	struct impl *impl = dev->impl;
	char key[1024], *val;

	if (r->props == NULL)
		return -EINVAL;

475
	snprintf(key, sizeof(key), PREFIX"%s:%s:%s", dev->name,
476
477
478
479
480
481
482
483
484
485
486
		r->direction == SPA_DIRECTION_INPUT ? "input" : "output", r->name);

	val = serialize_props(dev, r->props);
	if (pw_properties_set(impl->to_restore, key, val)) {
		pw_log_info("device %d: route properties changed %s %s", dev->id, key, val);
		add_idle_timeout(impl);
	}
	free(val);
	return 0;
}

487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
static char *serialize_routes(struct device *dev)
{
	char *ptr;
	size_t size;
	FILE *f;
	struct route_info *ri;
	int count = 0;

	f = open_memstream(&ptr, &size);
	fprintf(f, "[");

	pw_array_for_each(ri, &dev->route_info) {
		if (ri->save) {
			fprintf(f, "%s \"%s\"", count++ == 0 ? "" : ",", ri->name);
		}
	}
	fprintf(f, " ]");
	fclose(f);
	return ptr;
}

static int save_profile(struct device *dev)
{
	struct impl *impl = dev->impl;
	char key[1024], *val;

	if (pw_array_get_len(&dev->route_info, struct route_info) == 0)
		return 0;

516
	snprintf(key, sizeof(key), PREFIX"%s:profile:%s", dev->name, dev->profile_name);
517
518
519
520
521
522
523
524
525
526

	val = serialize_routes(dev);
	if (pw_properties_set(impl->to_restore, key, val)) {
		pw_log_info("device %d: profile routes changed %s %s", dev->id, key, val);
		add_idle_timeout(impl);
	}
	free(val);
	return 0;
}

527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
static int find_best_route(struct device *dev, uint32_t device_id, struct route *r)
{
	struct sm_param *p;
	struct route best, best_avail, best_unk;

	spa_zero(best_avail);
	spa_zero(best_unk);

	spa_list_for_each(p, &dev->obj->param_list, link) {
		struct route t;

		if (p->id != SPA_PARAM_EnumRoute ||
		    parse_enum_route(p, device_id, &t) < 0)
			continue;

		if (t.available == SPA_PARAM_AVAILABILITY_yes) {
			if (best_avail.name == NULL || t.priority > best_avail.priority)
				best_avail = t;
		}
		else if (t.available != SPA_PARAM_AVAILABILITY_no) {
			if (best_unk.name == NULL || t.priority > best_unk.priority)
				best_unk = t;
		}
	}
	best = best_avail;
	if (best.name == NULL)
		best = best_unk;
	if (best.name == NULL)
		return -ENOENT;
	*r = best;
	return 0;
}

560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
static int find_route(struct device *dev, uint32_t device_id, const char *name, struct route *r)
{
	struct sm_param *p;

	spa_list_for_each(p, &dev->obj->param_list, link) {
		if (p->id != SPA_PARAM_EnumRoute ||
		    parse_enum_route(p, device_id, r) < 0)
			continue;
		if (strcmp(r->name, name) != 0)
			continue;
		return 0;
	}
	return -ENOENT;
}

static int find_saved_route(struct device *dev, const char *val, uint32_t device_id, struct route *r)
{
	struct spa_json it[2];
	char key[128];

	if (val == NULL)
		return -ENOENT;

	spa_json_init(&it[0], val, strlen(val));
	if (spa_json_enter_array(&it[0], &it[1]) <= 0)
                return -EINVAL;

	while (spa_json_get_string(&it[1], key, sizeof(key)-1) > 0) {
		if (find_route(dev, device_id, key, r) >= 0)
			return 0;
	}
	return -ENOENT;
}

static int restore_device_route(struct device *dev, const char *val, uint32_t device_id)
595
596
597
{
	int res;
	struct route t;
598
	bool save = false;
599
600
601

	pw_log_info("device %d: restoring device %u", dev->id, device_id);

602
	res = find_saved_route(dev, val, device_id, &t);
603
	if (res < 0) {
604
605
606
607
608
609
610
611
		/* we could not find a saved route, try to find a new best */
		res = find_best_route(dev, device_id, &t);
		if (res < 0) {
			pw_log_info("device %d: can't find best route", dev->id);
		} else {
			pw_log_info("device %d: found best route '%s'", dev->id,
					t.name);
		}
612
	} else {
613
614
		/* we found a saved route */
		pw_log_info("device %d: found saved route '%s'", dev->id,
615
				t.name);
616
617
618
619
620
		/* make sure we save it again */
		save = true;
	}
	if (res >= 0) {
		restore_route(dev, &t, save);
621
622
623
624
	}
	return res;
}

625
626
static int handle_profile(struct device *dev)
{
627
	struct impl *impl = dev->impl;
628
	struct profile pr;
629
	int res;
630
631
	char key[1024];
	const char *json;
632

633
	if ((res = find_current_profile(dev, &pr)) < 0)
634
		return res;
635
636
637
638
639
	if (dev->active_profile == pr.index)
		return 0;

	pw_log_info("device %s: restore routes for profile '%s'",
			dev->name, pr.name);
640
641
642
	dev->active_profile = pr.index;
	snprintf(dev->profile_name, sizeof(dev->profile_name), "%s", pr.name);

643
	snprintf(key, sizeof(key), PREFIX"%s:profile:%s", dev->name, dev->profile_name);
644
	json = pw_properties_get(impl->to_restore, key);
645

646
647
	if (pr.classes != NULL) {
		struct spa_pod *iter;
648

649
650
651
652
653
		SPA_POD_STRUCT_FOREACH(pr.classes, iter) {
			struct spa_pod_parser prs;
			struct spa_pod_frame f[1];
			struct spa_pod *val;
			char *key;
654

655
656
657
			spa_pod_parser_pod(&prs, iter);
			if (spa_pod_parser_push_struct(&prs, &f[0]) < 0)
				continue;
658

659
660
661
662
663
664
665
666
667
668
669
670
671
672
			while (spa_pod_parser_get(&prs,
						SPA_POD_String(&key),
						SPA_POD_Pod(&val),
						NULL) >= 0) {
				if (key == NULL || val == NULL)
					break;
				if (strcmp(key, "card.profile.devices") == 0) {
					uint32_t *devices, n_devices, i;

					devices = spa_pod_get_array(val, &n_devices);
					if (devices == NULL || n_devices == 0)
						continue;

					for (i = 0; i < n_devices; i++)
673
						restore_device_route(dev, json, devices[i]);
674
675
676
677
				}
			}
                        spa_pod_parser_pop(&prs, &f[0]);
		}
678
679
	}

680
	return 0;
681
682
683
684
685
686
687
688
689
690
691
692
693
}

static void prune_route_info(struct device *dev)
{
	struct route_info *i;

	for (i = pw_array_first(&dev->route_info);
	     pw_array_check(&dev->route_info, i);) {
		if (i->generation != dev->generation) {
			pw_log_info("device %d: route %d unused", dev->id, i->index);
			pw_array_remove(&dev->route_info, i);
		} else
			i++;
694
	}
695
696
697
698
}

static int handle_route(struct device *dev, struct route *r)
{
699
	struct route_info *ri;
700
	bool restore = false;
701
	char key[1024];
702
	int res;
703
	bool save = false;
704

705
	pw_log_info("device %d: route %s %p", dev->id, r->name, r->p);
706
	if ((ri = find_route_info(dev, r)) == NULL)
707
		return -errno;
708

709
710
711
	if (ri->restore) {
		/* a new port has been found, restore the volume and make sure we
		 * save this as a prefered port */
712
		pw_log_info("device %d: new port found '%s'", dev->id, r->name);
713
		save = true;
714
715
		restore = true;
	} else {
716
717
718
		if (r->available != SPA_PARAM_AVAILABILITY_yes && ri->available != r->available) {
			struct route t;

719
720
721
722
723
724
725
726
727
728
			/* an existing port has changed to unavailable */
			pw_log_info("device %d: route '%s' not available", dev->id, r->name);

			/* try to find a new best port */
			res = find_best_route(dev, r->device_id, &t);
			if (res < 0) {
				pw_log_info("device %d: can't find best route", dev->id);
			} else {
				pw_log_info("device %d: found best route '%s'", dev->id,
						t.name);
729
				*r = t;
730
731
732
				restore = true;
			}
		}
733
		ri->available = r->available;
734
	}
735
	ri->generation = dev->generation;
736
737
738
739

	if (r == NULL)
		return -EIO;

740
	snprintf(key, sizeof(key), PREFIX"%s:%s:%s", dev->name,
741
			r->direction == SPA_DIRECTION_INPUT ? "input" : "output", r->name);
742

743
	if (restore) {
744
		restore_route(dev, r, save);
745
	} else if (r->props) {
746
		save_route(dev, r);
747
748
749
750
751
752
753
754
755
756
757
758
759
760
	}
	return 0;
}

static int handle_routes(struct device *dev)
{
	struct sm_param *p;

	spa_list_for_each(p, &dev->obj->param_list, link) {
		struct route r;
		if (p->id != SPA_PARAM_Route ||
		    parse_route(p, &r) < 0)
			continue;
		handle_route(dev, &r);
761
	}
762
	prune_route_info(dev);
763
764
765

	save_profile(dev);

766
767
768
769
770
771
	return 0;
}

static int handle_device(struct device *dev)
{
	int res;
772
773
774

	dev->generation++;

775
776
777
778
	if ((res = handle_profile(dev)) < 0)
		return res;
	if ((res = handle_routes(dev)) < 0)
		return res;
779
780
781
782
783
784
785
786
787
788
789
	return 0;
}

static void object_update(void *data)
{
	struct device *dev = data;
	struct impl *impl = dev->impl;

	pw_log_debug(NAME" %p: device %p %08x/%08x", impl, dev,
			dev->obj->obj.changed, dev->obj->obj.avail);

790
791
	if (dev->obj->obj.changed & SM_DEVICE_CHANGE_MASK_PARAMS)
		handle_device(dev);
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
}

static const struct sm_object_events object_events = {
	SM_VERSION_OBJECT_EVENTS,
	.update = object_update
};

static void session_create(void *data, struct sm_object *object)
{
	struct impl *impl = data;
	struct device *dev;
	const char *name;

	if (strcmp(object->type, PW_TYPE_INTERFACE_Device) != 0 ||
	    object->props == NULL ||
	    (name = pw_properties_get(object->props, PW_KEY_DEVICE_NAME)) == NULL)
		return;

	pw_log_debug(NAME " %p: add device '%d' %s", impl, object->id, name);

	dev = sm_object_add_data(object, SESSION_KEY, sizeof(struct device));
	dev->obj = (struct sm_device*)object;
	dev->id = object->id;
	dev->impl = impl;
	dev->name = strdup(name);
817
818
819
	dev->active_profile = SPA_ID_INVALID;
	dev->generation = 0;
	pw_array_init(&dev->route_info, sizeof(struct route_info) * 16);
820
821
822
823
824
825
826

	dev->obj->obj.mask |= SM_DEVICE_CHANGE_MASK_PARAMS;
	sm_object_add_listener(&dev->obj->obj, &dev->listener, &object_events, dev);
}

static void destroy_device(struct impl *impl, struct device *dev)
{
827
	spa_hook_remove(&dev->listener);
828
	pw_array_clear(&dev->route_info);
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
	free(dev->name);
	sm_object_remove_data((struct sm_object*)dev->obj, SESSION_KEY);
}

static void session_remove(void *data, struct sm_object *object)
{
	struct impl *impl = data;
	struct device *dev;

	if (strcmp(object->type, PW_TYPE_INTERFACE_Device) != 0)
		return;

	pw_log_debug(NAME " %p: remove device '%d'", impl, object->id);

	if ((dev = sm_object_get_data(object, SESSION_KEY)) != NULL)
		destroy_device(impl, dev);
}

847
848
849
850
851
852
853
854
855
static void session_destroy(void *data)
{
	struct impl *impl = data;
	remove_idle_timeout(impl);
	spa_hook_remove(&impl->listener);
	pw_properties_free(impl->to_restore);
	free(impl);
}

856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
static const struct sm_media_session_events session_events = {
	SM_VERSION_MEDIA_SESSION_EVENTS,
	.create = session_create,
	.remove = session_remove,
	.destroy = session_destroy,
};

int sm_default_routes_start(struct sm_media_session *session)
{
	struct impl *impl;
	int res;

	impl = calloc(1, sizeof(struct impl));
	if (impl == NULL)
		return -errno;

	impl->session = session;
	impl->context = session->context;

	impl->to_restore = pw_properties_new(NULL, NULL);
	if (impl->to_restore == NULL) {
		res = -errno;
		goto exit_free;
	}

881
882
	if ((res = sm_media_session_load_state(impl->session,
					SESSION_KEY, PREFIX, impl->to_restore)) < 0)
883
884
885
886
887
888
889
890
891
892
		pw_log_info("can't load "SESSION_KEY" state: %s", spa_strerror(res));

	sm_media_session_add_listener(impl->session, &impl->listener, &session_events, impl);

	return 0;

exit_free:
	free(impl);
	return res;
}