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

Last change on this file was 36795edf, checked in by Martin Decky <martin@…>, 4 years ago

Improve lists and other data structures

Provide more standard-compliant member_to_inst implementation that uses
offsetof. Avoid potential undefined behavior in list_foreach and
list_foreach_rev by avoiding assinging an unaligned pointer value. Use
size_t instead of unsigned long for list length.

  • Property mode set to 100644
File size: 12.7 KB
Line 
1/*
2 * Copyright (c) 2013 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 <macros.h>
38#include <errno.h>
39#include <stdlib.h>
40#include <str_error.h>
41
42#include "hound_ctx.h"
43#include "audio_data.h"
44#include "connection.h"
45#include "log.h"
46
47static errno_t update_data(audio_source_t *source, size_t size);
48static errno_t new_data(audio_sink_t *sink);
49
50/**
51 * Allocate and initialize hound context structure.
52 * @param name String identifier.
53 * @return Pointer to a new context structure, NULL on failure
54 *
55 * Creates record context structure.
56 */
57hound_ctx_t *hound_record_ctx_get(const char *name)
58{
59 hound_ctx_t *ctx = malloc(sizeof(hound_ctx_t));
60 if (ctx) {
61 link_initialize(&ctx->link);
62 list_initialize(&ctx->streams);
63 fibril_mutex_initialize(&ctx->guard);
64 ctx->source = NULL;
65 ctx->sink = malloc(sizeof(audio_sink_t));
66 if (!ctx->sink) {
67 free(ctx);
68 return NULL;
69 }
70 const errno_t ret = audio_sink_init(ctx->sink, name, ctx, NULL,
71 NULL, new_data, &AUDIO_FORMAT_DEFAULT);
72 if (ret != EOK) {
73 free(ctx->sink);
74 free(ctx);
75 return NULL;
76 }
77 }
78 return ctx;
79}
80
81/**
82 * Allocate and initialize hound context structure.
83 * @param name String identifier.
84 * @return Pointer to a new context structure, NULL on failure
85 *
86 * Creates record context structure.
87 */
88hound_ctx_t *hound_playback_ctx_get(const char *name)
89{
90 hound_ctx_t *ctx = malloc(sizeof(hound_ctx_t));
91 if (ctx) {
92 link_initialize(&ctx->link);
93 list_initialize(&ctx->streams);
94 fibril_mutex_initialize(&ctx->guard);
95 ctx->sink = NULL;
96 ctx->source = malloc(sizeof(audio_source_t));
97 if (!ctx->source) {
98 free(ctx);
99 return NULL;
100 }
101 const errno_t ret = audio_source_init(ctx->source, name, ctx, NULL,
102 update_data, &AUDIO_FORMAT_DEFAULT);
103 if (ret != EOK) {
104 free(ctx->source);
105 free(ctx);
106 return NULL;
107 }
108 }
109 return ctx;
110}
111
112/**
113 * Destroy existing context structure.
114 * @param ctx hound cotnext to destroy.
115 */
116void hound_ctx_destroy(hound_ctx_t *ctx)
117{
118 assert(ctx);
119 assert(!link_in_use(&ctx->link));
120 assert(list_empty(&ctx->streams));
121 if (ctx->source)
122 audio_source_fini(ctx->source);
123 if (ctx->sink)
124 audio_sink_fini(ctx->sink);
125 free(ctx->source);
126 free(ctx->sink);
127 free(ctx);
128}
129
130/**
131 * Retrieve associated context id.
132 * @param ctx hound context.
133 * @return context id of the context.
134 */
135hound_context_id_t hound_ctx_get_id(hound_ctx_t *ctx)
136{
137 assert(ctx);
138 return (hound_context_id_t)ctx;
139}
140
141/**
142 * Query playback/record status of a hound context.
143 * @param ctx Hound context.
144 * @return True if ctx is a recording context.
145 */
146bool hound_ctx_is_record(hound_ctx_t *ctx)
147{
148 assert(ctx);
149 return ctx->source == NULL;
150}
151
152/*
153 * STREAMS
154 */
155
156/** Hound stream structure. */
157typedef struct hound_ctx_stream {
158 /** Hound context streams link */
159 link_t link;
160 /** Audio data pipe */
161 audio_pipe_t fifo;
162 /** Parent context */
163 hound_ctx_t *ctx;
164 /** Stream data format */
165 pcm_format_t format;
166 /** Stream modifiers */
167 int flags;
168 /** Maximum allowed buffer size */
169 size_t allowed_size;
170 /** Fifo access synchronization */
171 fibril_mutex_t guard;
172 /** buffer status change condition */
173 fibril_condvar_t change;
174} hound_ctx_stream_t;
175
176/**
177 * New stream append helper.
178 * @param ctx hound context.
179 * @param stream A new stream.
180 */
181static inline void stream_append(hound_ctx_t *ctx, hound_ctx_stream_t *stream)
182{
183 assert(ctx);
184 assert(stream);
185 fibril_mutex_lock(&ctx->guard);
186 list_append(&stream->link, &ctx->streams);
187 if (list_count(&ctx->streams) == 1) {
188 if (ctx->source && list_count(&ctx->source->connections) == 0)
189 ctx->source->format = stream->format;
190 }
191 fibril_mutex_unlock(&ctx->guard);
192}
193
194/**
195 * Push new data to stream, do not block.
196 * @param stream The target stream.
197 * @param adata The new data.
198 * @return Error code.
199 */
200static errno_t stream_push_data(hound_ctx_stream_t *stream, audio_data_t *adata)
201{
202 assert(stream);
203 assert(adata);
204
205 if (stream->allowed_size && adata->size > stream->allowed_size)
206 return EINVAL;
207
208 fibril_mutex_lock(&stream->guard);
209 if (stream->allowed_size &&
210 (audio_pipe_bytes(&stream->fifo) + adata->size >
211 stream->allowed_size)) {
212 fibril_mutex_unlock(&stream->guard);
213 return EOVERFLOW;
214
215 }
216
217 const errno_t ret = audio_pipe_push(&stream->fifo, adata);
218 fibril_mutex_unlock(&stream->guard);
219 if (ret == EOK)
220 fibril_condvar_signal(&stream->change);
221 return ret;
222}
223
224/**
225 * Old stream remove helper.
226 * @param ctx hound context.
227 * @param stream An old stream.
228 */
229static inline void stream_remove(hound_ctx_t *ctx, hound_ctx_stream_t *stream)
230{
231 assert(ctx);
232 assert(stream);
233 fibril_mutex_lock(&ctx->guard);
234 list_remove(&stream->link);
235 fibril_mutex_unlock(&ctx->guard);
236}
237
238/**
239 * Create new stream.
240 * @param ctx Assocaited hound context.
241 * @param flags Stream modidfiers.
242 * @param format PCM data format.
243 * @param buffer_size Maximum allowed buffer size.
244 * @return Pointer to a new stream structure, NULL on failure.
245 */
246hound_ctx_stream_t *hound_ctx_create_stream(hound_ctx_t *ctx, int flags,
247 pcm_format_t format, size_t buffer_size)
248{
249 assert(ctx);
250 hound_ctx_stream_t *stream = malloc(sizeof(hound_ctx_stream_t));
251 if (stream) {
252 audio_pipe_init(&stream->fifo);
253 link_initialize(&stream->link);
254 fibril_mutex_initialize(&stream->guard);
255 fibril_condvar_initialize(&stream->change);
256 stream->ctx = ctx;
257 stream->flags = flags;
258 stream->format = format;
259 stream->allowed_size = buffer_size;
260 stream_append(ctx, stream);
261 log_verbose("CTX: %p added stream; flags:%#x ch: %u r:%u f:%s",
262 ctx, flags, format.channels, format.sampling_rate,
263 pcm_sample_format_str(format.sample_format));
264 }
265 return stream;
266}
267
268/**
269 * Destroy existing stream structure.
270 * @param stream The stream to destroy.
271 *
272 * The function will print warning if there are data in the buffer.
273 */
274void hound_ctx_destroy_stream(hound_ctx_stream_t *stream)
275{
276 if (stream) {
277 stream_remove(stream->ctx, stream);
278 if (audio_pipe_bytes(&stream->fifo))
279 log_warning("Destroying stream with non empty buffer");
280 log_verbose("CTX: %p remove stream (%zu/%zu); "
281 "flags:%#x ch: %u r:%u f:%s",
282 stream->ctx, audio_pipe_bytes(&stream->fifo),
283 stream->allowed_size, stream->flags,
284 stream->format.channels, stream->format.sampling_rate,
285 pcm_sample_format_str(stream->format.sample_format));
286 audio_pipe_fini(&stream->fifo);
287 free(stream);
288 }
289}
290
291/**
292 * Write new data to a stream.
293 * @param stream The destination stream.
294 * @param data audio data buffer.
295 * @param size size of the @p data buffer.
296 * @return Error code.
297 */
298errno_t hound_ctx_stream_write(hound_ctx_stream_t *stream, void *data,
299 size_t size)
300{
301 assert(stream);
302
303 if (stream->allowed_size && size > stream->allowed_size)
304 return EINVAL;
305
306 fibril_mutex_lock(&stream->guard);
307 while (stream->allowed_size &&
308 (audio_pipe_bytes(&stream->fifo) + size > stream->allowed_size)) {
309 fibril_condvar_wait(&stream->change, &stream->guard);
310
311 }
312
313 const errno_t ret =
314 audio_pipe_push_data(&stream->fifo, data, size, stream->format);
315 fibril_mutex_unlock(&stream->guard);
316 if (ret == EOK)
317 fibril_condvar_signal(&stream->change);
318 return ret;
319}
320
321/**
322 * Read data from a buffer.
323 * @param stream The source buffer.
324 * @param data Destination data buffer.
325 * @param size Size of the @p data buffer.
326 * @return Error code.
327 */
328errno_t hound_ctx_stream_read(hound_ctx_stream_t *stream, void *data, size_t size)
329{
330 assert(stream);
331
332 if (stream->allowed_size && size > stream->allowed_size)
333 return EINVAL;
334
335 fibril_mutex_lock(&stream->guard);
336 while (audio_pipe_bytes(&stream->fifo) < size) {
337 fibril_condvar_wait(&stream->change, &stream->guard);
338 }
339
340 pcm_format_silence(data, size, &stream->format);
341 const size_t ret =
342 audio_pipe_mix_data(&stream->fifo, data, size, &stream->format);
343 fibril_mutex_unlock(&stream->guard);
344 if (ret > 0) {
345 fibril_condvar_signal(&stream->change);
346 return EOK;
347 }
348 return EEMPTY;
349}
350
351/**
352 * Add (mix) stream data to the destination buffer.
353 * @param stream The source stream.
354 * @param data Destination audio buffer.
355 * @param size Size of the @p data buffer.
356 * @param format Destination data format.
357 * @return Size of the destination buffer touch with stream's data.
358 */
359size_t hound_ctx_stream_add_self(hound_ctx_stream_t *stream, void *data,
360 size_t size, const pcm_format_t *f)
361{
362 assert(stream);
363 fibril_mutex_lock(&stream->guard);
364 const size_t ret = audio_pipe_mix_data(&stream->fifo, data, size, f);
365 fibril_condvar_signal(&stream->change);
366 fibril_mutex_unlock(&stream->guard);
367 return ret;
368}
369
370/**
371 * Block until the stream's buffer is empty.
372 * @param stream Target stream.
373 */
374void hound_ctx_stream_drain(hound_ctx_stream_t *stream)
375{
376 assert(stream);
377 log_debug("Draining stream");
378 fibril_mutex_lock(&stream->guard);
379 while (audio_pipe_bytes(&stream->fifo))
380 fibril_condvar_wait(&stream->change, &stream->guard);
381 fibril_mutex_unlock(&stream->guard);
382}
383
384/**
385 * Update context data.
386 * @param source Source abstraction.
387 * @param size Required size in source's format.
388 * @return error code.
389 *
390 * Mixes data from all streams and pushes it to all connections.
391 */
392errno_t update_data(audio_source_t *source, size_t size)
393{
394 assert(source);
395 assert(source->private_data);
396 hound_ctx_t *ctx = source->private_data;
397 void *buffer = malloc(size);
398 if (!buffer)
399 return ENOMEM;
400 audio_data_t *adata = audio_data_create(buffer, size, source->format);
401 if (!adata) {
402 free(buffer);
403 return ENOMEM;
404 }
405 log_verbose("CTX: %p: Mixing %zu streams", ctx,
406 list_count(&ctx->streams));
407 pcm_format_silence(buffer, size, &source->format);
408 fibril_mutex_lock(&ctx->guard);
409 list_foreach(ctx->streams, link, hound_ctx_stream_t, stream) {
410 ssize_t copied = hound_ctx_stream_add_self(
411 stream, buffer, size, &source->format);
412 if (copied != (ssize_t)size)
413 log_warning("Not enough data in stream buffer");
414 }
415 log_verbose("CTX: %p. Pushing audio to %zu connections", ctx,
416 list_count(&source->connections));
417 list_foreach(source->connections, source_link, connection_t, conn) {
418 connection_push_data(conn, adata);
419 }
420 /* all connections should now have their refs */
421 audio_data_unref(adata);
422 fibril_mutex_unlock(&ctx->guard);
423 return EOK;
424}
425
426errno_t new_data(audio_sink_t *sink)
427{
428 assert(sink);
429 assert(sink->private_data);
430 hound_ctx_t *ctx = sink->private_data;
431
432 fibril_mutex_lock(&ctx->guard);
433
434 /* count available data */
435 size_t available_frames = -1; /* this is ugly.... */
436 list_foreach(sink->connections, source_link, connection_t, conn) {
437 available_frames = min(available_frames,
438 audio_pipe_frames(&conn->fifo));
439 }
440
441 const size_t bsize =
442 available_frames * pcm_format_frame_size(&sink->format);
443 void *buffer = malloc(bsize);
444 if (!buffer) {
445 fibril_mutex_unlock(&ctx->guard);
446 return ENOMEM;
447 }
448 audio_data_t *adata = audio_data_create(buffer, bsize, sink->format);
449 if (!adata) {
450 fibril_mutex_unlock(&ctx->guard);
451 free(buffer);
452 return ENOMEM;
453 }
454
455 /* mix data */
456 pcm_format_silence(buffer, bsize, &sink->format);
457 list_foreach(sink->connections, source_link, connection_t, conn) {
458 /* This should not trigger data update on the source */
459 connection_add_source_data(
460 conn, buffer, bsize, sink->format);
461 }
462 /* push to all streams */
463 list_foreach(ctx->streams, link, hound_ctx_stream_t, stream) {
464 const errno_t ret = stream_push_data(stream, adata);
465 if (ret != EOK)
466 log_error("Failed to push data to stream: %s",
467 str_error(ret));
468 }
469 audio_data_unref(adata);
470 fibril_mutex_unlock(&ctx->guard);
471 return ENOTSUP;
472}
473
474/**
475 * @}
476 */
Note: See TracBrowser for help on using the repository browser.