source: mainline/uspace/srv/audio/hound/hound.c

Last change on this file was 2fbd49c, checked in by Jiri Svoboda <jiri@…>, 3 years ago

Audio synk needs locking

Without locking the list of sink connections, we are exposed
to a race between removing a connection at the end of playback
(typically while destroying a hound context) and audio device
event PCM_EVENT_FRAMES_PLAYED which causes audio mixing to occur
via audio_sink_mix_inputs(), causing hound to crash often at
the end of playback.

  • Property mode set to 100644
File size: 15.8 KB
Line 
1/*
2 * Copyright (c) 2012 Jan Vesely
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/**
30 * @addtogroup audio
31 * @brief HelenOS sound server.
32 * @{
33 */
34/** @file
35 */
36
37#include <assert.h>
38#include <stdlib.h>
39#include <str.h>
40
41#include "hound.h"
42#include "audio_device.h"
43#include "audio_sink.h"
44#include "audio_source.h"
45#include "connection.h"
46#include "log.h"
47#include "errno.h"
48#include "str_error.h"
49
50/**
51 * Search devices by name.
52 * @param name String identifier.
53 * @return Pointer to the found device, NULL on failure.
54 */
55static audio_device_t *find_device_by_name(list_t *list, const char *name)
56{
57 assert(list);
58 assert(name);
59
60 list_foreach(*list, link, audio_device_t, dev) {
61 if (str_cmp(name, dev->name) == 0) {
62 log_debug("device with name '%s' is in the list",
63 name);
64 return dev;
65 }
66 }
67
68 return NULL;
69}
70
71/**
72 * Search sources by name.
73 * @param name String identifier.
74 * @return Pointer to the found source, NULL on failure.
75 */
76static audio_source_t *find_source_by_name(list_t *list, const char *name)
77{
78 assert(list);
79 assert(name);
80
81 list_foreach(*list, link, audio_source_t, dev) {
82 if (str_cmp(name, dev->name) == 0) {
83 log_debug("source with name '%s' is in the list",
84 name);
85 return dev;
86 }
87 }
88
89 return NULL;
90}
91
92/**
93 * Search sinks by name.
94 * @param name String identifier.
95 * @return Pointer to the found sink, NULL on failure.
96 */
97static audio_sink_t *find_sink_by_name(list_t *list, const char *name)
98{
99 assert(list);
100 assert(name);
101
102 list_foreach(*list, link, audio_sink_t, dev) {
103 if (str_cmp(name, dev->name) == 0) {
104 log_debug("sink with name '%s' is in the list",
105 name);
106 return dev;
107 }
108 }
109
110 return NULL;
111}
112
113static errno_t hound_disconnect_internal(hound_t *hound, const char *source_name, const char *sink_name);
114
115/**
116 * Remove provided sink.
117 * @param hound The hound structure.
118 * @param sink Target sink to remove.
119 *
120 * This function has to be called with the list_guard lock held.
121 */
122static void hound_remove_sink_internal(hound_t *hound, audio_sink_t *sink)
123{
124 assert(hound);
125 assert(sink);
126 assert(fibril_mutex_is_locked(&hound->list_guard));
127 log_verbose("Removing sink '%s'.", sink->name);
128 fibril_mutex_lock(&sink->lock);
129 if (!list_empty(&sink->connections))
130 log_warning("Removing sink '%s' while still connected.", sink->name);
131 while (!list_empty(&sink->connections)) {
132 connection_t *conn = list_get_instance(
133 list_first(&sink->connections), connection_t, sink_link);
134 list_remove(&conn->hound_link);
135 connection_destroy(conn);
136 }
137 list_remove(&sink->link);
138 fibril_mutex_unlock(&sink->lock);
139}
140
141/**
142 * Remove provided source.
143 * @param hound The hound structure.
144 * @param sink Target source to remove.
145 *
146 * This function has to be called with the guard lock held.
147 */
148static void hound_remove_source_internal(hound_t *hound, audio_source_t *source)
149{
150 assert(fibril_mutex_is_locked(&hound->list_guard));
151 log_verbose("Removing source '%s'.", source->name);
152 if (!list_empty(&source->connections))
153 log_warning("Removing source '%s' while still connected.", source->name);
154 while (!list_empty(&source->connections)) {
155 connection_t *conn = list_get_instance(
156 list_first(&source->connections), connection_t, source_link);
157 list_remove(&conn->hound_link);
158 connection_destroy(conn);
159 }
160 list_remove(&source->link);
161}
162
163/**
164 * Initialize hound structure.
165 * @param hound The structure to initialize.
166 * @return Error code.
167 */
168errno_t hound_init(hound_t *hound)
169{
170 assert(hound);
171 fibril_mutex_initialize(&hound->list_guard);
172 list_initialize(&hound->devices);
173 list_initialize(&hound->contexts);
174 list_initialize(&hound->sources);
175 list_initialize(&hound->sinks);
176 list_initialize(&hound->connections);
177 return EOK;
178}
179
180/**
181 * Add a new application context.
182 * @param hound Hound structure.
183 * @param ctx Context to add.
184 * @return Error code.
185 */
186errno_t hound_add_ctx(hound_t *hound, hound_ctx_t *ctx)
187{
188 log_info("Trying to add context %p", ctx);
189 assert(hound);
190 if (!ctx)
191 return EINVAL;
192 fibril_mutex_lock(&hound->list_guard);
193 list_append(&ctx->link, &hound->contexts);
194 fibril_mutex_unlock(&hound->list_guard);
195 errno_t ret = EOK;
196 if (ret == EOK && ctx->source)
197 ret = hound_add_source(hound, ctx->source);
198 if (ret == EOK && ctx->sink)
199 ret = hound_add_sink(hound, ctx->sink);
200 if (ret != EOK) {
201 fibril_mutex_lock(&hound->list_guard);
202 list_remove(&ctx->link);
203 fibril_mutex_unlock(&hound->list_guard);
204 }
205 return ret;
206}
207
208/**
209 * Remove existing application context.
210 * @param hound Hound structure.
211 * @param ctx Context to remove.
212 * @return Error code.
213 */
214errno_t hound_remove_ctx(hound_t *hound, hound_ctx_t *ctx)
215{
216 assert(hound);
217 if (!ctx)
218 return EINVAL;
219 if (!list_empty(&ctx->streams))
220 return EBUSY;
221 fibril_mutex_lock(&hound->list_guard);
222 list_remove(&ctx->link);
223 if (ctx->source)
224 hound_remove_source_internal(hound, ctx->source);
225 if (ctx->sink)
226 hound_remove_sink_internal(hound, ctx->sink);
227 fibril_mutex_unlock(&hound->list_guard);
228 return EOK;
229}
230
231/**
232 * Search registered contexts for the matching id.
233 * @param hound The hound structure.
234 * @param id Requested id.
235 * @return Pointer to the found structure, NULL on failure.
236 */
237hound_ctx_t *hound_get_ctx_by_id(hound_t *hound, hound_context_id_t id)
238{
239 assert(hound);
240
241 fibril_mutex_lock(&hound->list_guard);
242 hound_ctx_t *res = NULL;
243 list_foreach(hound->contexts, link, hound_ctx_t, ctx) {
244 if (hound_ctx_get_id(ctx) == id) {
245 res = ctx;
246 break;
247 }
248 }
249 fibril_mutex_unlock(&hound->list_guard);
250 return res;
251}
252
253/**
254 * Add a new device.
255 * @param hound The hound structure.
256 * @param id Locations service id representing the device driver.
257 * @param name String identifier.
258 * @return Error code.
259 */
260errno_t hound_add_device(hound_t *hound, service_id_t id, const char *name)
261{
262 log_verbose("Adding device \"%s\", service: %zu", name, id);
263
264 assert(hound);
265 if (!name || !id) {
266 log_debug("Incorrect parameters.");
267 return EINVAL;
268 }
269
270 list_foreach(hound->devices, link, audio_device_t, dev) {
271 if (dev->id == id) {
272 log_debug("Device with id %zu is already present", id);
273 return EEXIST;
274 }
275 }
276
277 audio_device_t *dev = find_device_by_name(&hound->devices, name);
278 if (dev) {
279 log_debug("Device with name %s is already present", name);
280 return EEXIST;
281 }
282
283 dev = malloc(sizeof(audio_device_t));
284 if (!dev) {
285 log_debug("Failed to malloc device structure.");
286 return ENOMEM;
287 }
288
289 const errno_t ret = audio_device_init(dev, id, name);
290 if (ret != EOK) {
291 log_debug("Failed to initialize new audio device: %s",
292 str_error(ret));
293 free(dev);
294 return ret;
295 }
296
297 list_append(&dev->link, &hound->devices);
298 log_info("Added new device: '%s'", dev->name);
299
300 audio_source_t *source = audio_device_get_source(dev);
301 if (source) {
302 const errno_t ret = hound_add_source(hound, source);
303 if (ret != EOK) {
304 log_debug("Failed to add device source: %s",
305 str_error(ret));
306 audio_device_fini(dev);
307 return ret;
308 }
309 log_verbose("Added source: '%s'.", source->name);
310 }
311
312 audio_sink_t *sink = audio_device_get_sink(dev);
313 if (sink) {
314 const errno_t ret = hound_add_sink(hound, sink);
315 if (ret != EOK) {
316 log_debug("Failed to add device sink: %s",
317 str_error(ret));
318 audio_device_fini(dev);
319 return ret;
320 }
321 log_verbose("Added sink: '%s'.", sink->name);
322 }
323
324 if (!source && !sink)
325 log_warning("Neither sink nor source on device '%s'.", name);
326
327 return ret;
328}
329
330/**
331 * Register a new source.
332 * @param hound The hound structure.
333 * @param source A new source to add.
334 * @return Error code.
335 */
336errno_t hound_add_source(hound_t *hound, audio_source_t *source)
337{
338 assert(hound);
339 if (!source || !source->name || str_cmp(source->name, "default") == 0) {
340 log_debug("Invalid source specified.");
341 return EINVAL;
342 }
343 fibril_mutex_lock(&hound->list_guard);
344 if (find_source_by_name(&hound->sources, source->name)) {
345 log_debug("Source by that name already exists");
346 fibril_mutex_unlock(&hound->list_guard);
347 return EEXIST;
348 }
349 list_append(&source->link, &hound->sources);
350 fibril_mutex_unlock(&hound->list_guard);
351 return EOK;
352}
353
354/**
355 * Register a new sink.
356 * @param hound The hound structure.
357 * @param sink A new sink to add.
358 * @return Error code.
359 */
360errno_t hound_add_sink(hound_t *hound, audio_sink_t *sink)
361{
362 assert(hound);
363 if (!sink || !sink->name || str_cmp(sink->name, "default") == 0) {
364 log_debug("Invalid source specified.");
365 return EINVAL;
366 }
367 fibril_mutex_lock(&hound->list_guard);
368 if (find_sink_by_name(&hound->sinks, sink->name)) {
369 log_debug("Sink by that name already exists");
370 fibril_mutex_unlock(&hound->list_guard);
371 return EEXIST;
372 }
373 list_append(&sink->link, &hound->sinks);
374 fibril_mutex_unlock(&hound->list_guard);
375 return EOK;
376}
377
378/**
379 * Remove a registered source.
380 * @param hound The hound structure.
381 * @param source A registered source to remove.
382 * @return Error code.
383 */
384errno_t hound_remove_source(hound_t *hound, audio_source_t *source)
385{
386 assert(hound);
387 if (!source)
388 return EINVAL;
389 fibril_mutex_lock(&hound->list_guard);
390 hound_remove_source_internal(hound, source);
391 fibril_mutex_unlock(&hound->list_guard);
392 return EOK;
393}
394
395/**
396 * Remove a registered sink.
397 * @param hound The hound structure.
398 * @param sink A registered sink to remove.
399 * @return Error code.
400 */
401errno_t hound_remove_sink(hound_t *hound, audio_sink_t *sink)
402{
403 assert(hound);
404 if (!sink)
405 return EINVAL;
406 fibril_mutex_lock(&hound->list_guard);
407 hound_remove_sink_internal(hound, sink);
408 fibril_mutex_unlock(&hound->list_guard);
409 return EOK;
410}
411
412/**
413 * List all registered sources.
414 * @param[in] hound The hound structure.
415 * @param[out] list List of the string identifiers.
416 * @param[out] size Number of identifiers int he @p list.
417 * @return Error code.
418 */
419errno_t hound_list_sources(hound_t *hound, char ***list, size_t *size)
420{
421 assert(hound);
422 if (!list || !size)
423 return EINVAL;
424
425 fibril_mutex_lock(&hound->list_guard);
426 const unsigned long count = list_count(&hound->sources);
427 if (count == 0) {
428 *list = NULL;
429 *size = 0;
430 fibril_mutex_unlock(&hound->list_guard);
431 return EOK;
432 }
433 char **names = calloc(count, sizeof(char *));
434 errno_t ret = names ? EOK : ENOMEM;
435 for (unsigned long i = 0; i < count && ret == EOK; ++i) {
436 link_t *slink = list_nth(&hound->sources, i);
437 audio_source_t *source = audio_source_list_instance(slink);
438 names[i] = str_dup(source->name);
439 if (names[i])
440 ret = ENOMEM;
441 }
442 if (ret == EOK) {
443 *size = count;
444 *list = names;
445 } else {
446 for (size_t i = 0; i < count; ++i)
447 free(names[i]);
448 free(names);
449 }
450 fibril_mutex_unlock(&hound->list_guard);
451 return ret;
452}
453
454/**
455 * List all registered sinks.
456 * @param[in] hound The hound structure.
457 * @param[out] list List of the string identifiers.
458 * @param[out] size Number of identifiers int he @p list.
459 * @return Error code.
460 */
461errno_t hound_list_sinks(hound_t *hound, char ***list, size_t *size)
462{
463 assert(hound);
464 if (!list || !size)
465 return EINVAL;
466
467 fibril_mutex_lock(&hound->list_guard);
468 const size_t count = list_count(&hound->sinks);
469 if (count == 0) {
470 *list = NULL;
471 *size = 0;
472 fibril_mutex_unlock(&hound->list_guard);
473 return EOK;
474 }
475 char **names = calloc(count, sizeof(char *));
476 errno_t ret = names ? EOK : ENOMEM;
477 for (size_t i = 0; i < count && ret == EOK; ++i) {
478 link_t *slink = list_nth(&hound->sinks, i);
479 audio_sink_t *sink = audio_sink_list_instance(slink);
480 names[i] = str_dup(sink->name);
481 if (!names[i])
482 ret = ENOMEM;
483 }
484 if (ret == EOK) {
485 *size = count;
486 *list = names;
487 } else {
488 for (size_t i = 0; i < count; ++i)
489 free(names[i]);
490 free(names);
491 }
492 fibril_mutex_unlock(&hound->list_guard);
493 return ret;
494}
495
496/**
497 * List all connections
498 * @param[in] hound The hound structure.
499 * @param[out] sources List of the source string identifiers.
500 * @param[out] sinks List of the sinks string identifiers.
501 * @param[out] size Number of identifiers int he @p list.
502 * @return Error code.
503 *
504 * Lists include duplicit name entries. The order of entries is important,
505 * identifiers with the same index are connected.
506 */
507errno_t hound_list_connections(hound_t *hound, const char ***sources,
508 const char ***sinks, size_t *size)
509{
510 fibril_mutex_lock(&hound->list_guard);
511 fibril_mutex_unlock(&hound->list_guard);
512 return ENOTSUP;
513}
514
515/**
516 * Create and register a new connection.
517 * @param hound The hound structure.
518 * @param source_name Source's string id.
519 * @param sink_name Sink's string id.
520 * @return Error code.
521 */
522errno_t hound_connect(hound_t *hound, const char *source_name, const char *sink_name)
523{
524 assert(hound);
525 log_verbose("Connecting '%s' to '%s'.", source_name, sink_name);
526 fibril_mutex_lock(&hound->list_guard);
527
528 audio_source_t *source =
529 audio_source_list_instance(list_first(&hound->sources));
530 if (str_cmp(source_name, "default") != 0)
531 source = find_source_by_name(&hound->sources, source_name);
532
533 audio_sink_t *sink =
534 audio_sink_list_instance(list_first(&hound->sinks));
535 if (str_cmp(sink_name, "default") != 0)
536 sink = find_sink_by_name(&hound->sinks, sink_name);
537
538 if (!source || !sink) {
539 fibril_mutex_unlock(&hound->list_guard);
540 log_debug("Source (%p), or sink (%p) not found", source, sink);
541 return ENOENT;
542 }
543 connection_t *conn = connection_create(source, sink);
544 if (!conn) {
545 fibril_mutex_unlock(&hound->list_guard);
546 log_debug("Failed to create connection");
547 return ENOMEM;
548 }
549 list_append(&conn->hound_link, &hound->connections);
550 fibril_mutex_unlock(&hound->list_guard);
551 return EOK;
552}
553
554/**
555 * Find and destroy connection between source and sink.
556 * @param hound The hound structure.
557 * @param source_name Source's string id.
558 * @param sink_name Sink's string id.
559 * @return Error code.
560 */
561errno_t hound_disconnect(hound_t *hound, const char *source_name, const char *sink_name)
562{
563 assert(hound);
564 fibril_mutex_lock(&hound->list_guard);
565 const errno_t ret = hound_disconnect_internal(hound, source_name, sink_name);
566 fibril_mutex_unlock(&hound->list_guard);
567 return ret;
568}
569
570/**
571 * Internal disconnect helper.
572 * @param hound The hound structure.
573 * @param source_name Source's string id.
574 * @param sink_name Sink's string id.
575 * @return Error code.
576 *
577 * This function has to be called with the list_guard lock held.
578 */
579static errno_t hound_disconnect_internal(hound_t *hound, const char *source_name,
580 const char *sink_name)
581{
582 assert(hound);
583 assert(fibril_mutex_is_locked(&hound->list_guard));
584 log_debug("Disconnecting '%s' to '%s'.", source_name, sink_name);
585
586 list_foreach_safe(hound->connections, it, next) {
587 connection_t *conn = connection_from_hound_list(it);
588 if (str_cmp(connection_source_name(conn), source_name) == 0 ||
589 str_cmp(connection_sink_name(conn), sink_name) == 0) {
590 log_debug("Removing %s -> %s", connection_source_name(conn),
591 connection_sink_name(conn));
592 list_remove(it);
593 connection_destroy(conn);
594 }
595 }
596
597 return EOK;
598}
599/**
600 * @}
601 */
Note: See TracBrowser for help on using the repository browser.