source: mainline/uspace/srv/audio/hound/audio_device.c@ 43ba118

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 43ba118 was 984a9ba, checked in by Martin Decky <martin@…>, 7 years ago

do not expose the call capability handler from the async framework

Keep the call capability handler encapsulated within the async framework
and do not expose it explicitly via its API. Use the pointer to
ipc_call_t as the sole object identifying an IPC call in the code that
uses the async framework.

This plugs a major leak in the abstraction and also simplifies both the
async framework (slightly) and all IPC servers.

  • Property mode set to 100644
File size: 11.4 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 <async.h>
39#include <errno.h>
40#include <inttypes.h>
41#include <loc.h>
42#include <stdbool.h>
43#include <str.h>
44#include <str_error.h>
45#include <as.h>
46
47
48#include "audio_device.h"
49#include "log.h"
50
51/* hardwired to provide ~21ms per fragment */
52#define BUFFER_PARTS 16
53
54static errno_t device_sink_connection_callback(audio_sink_t *sink, bool new);
55static errno_t device_source_connection_callback(audio_source_t *source, bool new);
56static void device_event_callback(ipc_call_t *icall, void *arg);
57static errno_t device_check_format(audio_sink_t *sink);
58static errno_t get_buffer(audio_device_t *dev);
59static errno_t release_buffer(audio_device_t *dev);
60static void advance_buffer(audio_device_t *dev, size_t size);
61static inline bool is_running(audio_device_t *dev)
62{
63 assert(dev);
64 /* we release buffer on stop so this should be enough */
65 return dev->buffer.base != NULL;
66}
67
68/**
69 * Initialize audio device structure.
70 * @param dev The structure to initialize.
71 * @param id Location service id of the device driver.
72 * @param name Name of the device.
73 * @return Error code.
74 */
75errno_t audio_device_init(audio_device_t *dev, service_id_t id, const char *name)
76{
77 assert(dev);
78 link_initialize(&dev->link);
79 dev->id = id;
80 dev->name = str_dup(name);
81 dev->sess = audio_pcm_open_service(id);
82 if (!dev->sess) {
83 log_debug("Failed to connect to device \"%s\"", name);
84 return ENOMEM;
85 }
86
87 audio_sink_init(&dev->sink, name, dev, device_sink_connection_callback,
88 device_check_format, NULL, &AUDIO_FORMAT_ANY);
89 audio_source_init(&dev->source, name, dev,
90 device_source_connection_callback, NULL, &AUDIO_FORMAT_ANY);
91
92 /* Init buffer members */
93 dev->buffer.base = NULL;
94 dev->buffer.position = NULL;
95 dev->buffer.size = 0;
96 dev->buffer.fragment_size = 0;
97
98 log_verbose("Initialized device (%p) '%s' with id %" PRIun ".",
99 dev, dev->name, dev->id);
100
101 return EOK;
102}
103
104/**
105 * Restore resource cplaimed during initialization.
106 * @param dev The device to release.
107 *
108 * NOT IMPLEMENTED
109 */
110void audio_device_fini(audio_device_t *dev)
111{
112 //TODO implement;
113}
114
115/**
116 * Get device provided audio source.
117 * @param dev Th device.
118 * @return pointer to aa audio source structure, NULL if the device is not
119 * capable of capturing audio.
120 */
121audio_source_t *audio_device_get_source(audio_device_t *dev)
122{
123 assert(dev);
124 sysarg_t val;
125 errno_t rc = audio_pcm_query_cap(dev->sess, AUDIO_CAP_CAPTURE, &val);
126 if (rc == EOK && val)
127 return &dev->source;
128 return NULL;
129}
130
131/**
132 * Get device provided audio sink.
133 * @param dev Th device.
134 * @return pointer to aa audio source structure, NULL if the device is not
135 * capable of audio playback.
136 */
137audio_sink_t *audio_device_get_sink(audio_device_t *dev)
138{
139 assert(dev);
140 sysarg_t val;
141 errno_t rc = audio_pcm_query_cap(dev->sess, AUDIO_CAP_PLAYBACK, &val);
142 if (rc == EOK && val)
143 return &dev->sink;
144 return NULL;
145}
146
147/**
148 * Handle connection addition and removal.
149 * @param sink audio sink that is connected or disconnected.
150 * @param new True of a connection was added, false otherwise.
151 * @return Error code.
152 *
153 * Starts playback on first connection. Stops playback when there are no
154 * connections.
155 */
156static errno_t device_sink_connection_callback(audio_sink_t *sink, bool new)
157{
158 assert(sink);
159 audio_device_t *dev = sink->private_data;
160 if (new && list_count(&sink->connections) == 1) {
161 log_verbose("First connection on device sink '%s'", sink->name);
162
163 errno_t ret = get_buffer(dev);
164 if (ret != EOK) {
165 log_error("Failed to get device buffer: %s",
166 str_error(ret));
167 return ret;
168 }
169 audio_pcm_register_event_callback(dev->sess,
170 device_event_callback, dev);
171
172 /*
173 * Fill the buffer first. Fill the first two fragments,
174 * so that we stay one fragment ahead
175 */
176 pcm_format_silence(dev->buffer.base, dev->buffer.size,
177 &dev->sink.format);
178 //TODO add underrun detection.
179 const size_t size = dev->buffer.fragment_size * 2;
180 /* We never cross the end of the buffer here */
181 audio_sink_mix_inputs(&dev->sink, dev->buffer.position, size);
182 advance_buffer(dev, size);
183
184 const unsigned frames = dev->buffer.fragment_size /
185 pcm_format_frame_size(&dev->sink.format);
186 log_verbose("Fragment frame count %u", frames);
187 ret = audio_pcm_start_playback_fragment(dev->sess, frames,
188 dev->sink.format.channels, dev->sink.format.sampling_rate,
189 dev->sink.format.sample_format);
190 if (ret != EOK) {
191 log_error("Failed to start playback: %s",
192 str_error(ret));
193 release_buffer(dev);
194 return ret;
195 }
196 }
197 if (list_count(&sink->connections) == 0) {
198 assert(!new);
199 log_verbose("Removed last connection on device sink '%s'",
200 sink->name);
201 errno_t ret = audio_pcm_stop_playback(dev->sess);
202 if (ret != EOK) {
203 log_error("Failed to stop playback: %s",
204 str_error(ret));
205 return ret;
206 }
207 }
208 return EOK;
209}
210
211/**
212 * Handle connection addition and removal.
213 * @param source audio source that is connected or disconnected.
214 * @param new True of a connection was added, false otherwise.
215 * @return Error code.
216 *
217 * Starts capture on first connection. Stops capture when there are no
218 * connections.
219 */
220static errno_t device_source_connection_callback(audio_source_t *source, bool new)
221{
222 assert(source);
223 audio_device_t *dev = source->private_data;
224 if (new && list_count(&source->connections) == 1) {
225 errno_t ret = get_buffer(dev);
226 if (ret != EOK) {
227 log_error("Failed to get device buffer: %s",
228 str_error(ret));
229 return ret;
230 }
231
232 //TODO set and test format
233
234 const unsigned frames = dev->buffer.fragment_size /
235 pcm_format_frame_size(&dev->sink.format);
236 ret = audio_pcm_start_capture_fragment(dev->sess, frames,
237 dev->source.format.channels,
238 dev->source.format.sampling_rate,
239 dev->source.format.sample_format);
240 if (ret != EOK) {
241 log_error("Failed to start recording: %s",
242 str_error(ret));
243 release_buffer(dev);
244 return ret;
245 }
246 }
247 if (list_count(&source->connections) == 0) { /* Disconnected */
248 assert(!new);
249 errno_t ret = audio_pcm_stop_capture_immediate(dev->sess);
250 if (ret != EOK) {
251 log_error("Failed to start recording: %s",
252 str_error(ret));
253 return ret;
254 }
255 }
256
257 return EOK;
258}
259
260/** Audio device event handler.
261 *
262 * @param icall Initial call structure.
263 * @param arg (unused)
264 *
265 */
266static void device_event_callback(ipc_call_t *icall, void *arg)
267{
268 struct timeval time1;
269 errno_t ret;
270
271 /* Answer initial request */
272 async_answer_0(icall, EOK);
273 audio_device_t *dev = arg;
274 assert(dev);
275 while (true) {
276 ipc_call_t call;
277 async_get_call(&call);
278 async_answer_0(&call, EOK);
279
280 switch (IPC_GET_IMETHOD(call)) {
281 case PCM_EVENT_FRAMES_PLAYED:
282 getuptime(&time1);
283 //TODO add underrun detection.
284 /* We never cross the end of the buffer here */
285 audio_sink_mix_inputs(&dev->sink, dev->buffer.position,
286 dev->buffer.fragment_size);
287 advance_buffer(dev, dev->buffer.fragment_size);
288 struct timeval time2;
289 getuptime(&time2);
290 log_verbose("Time to mix sources: %li\n",
291 tv_sub_diff(&time2, &time1));
292 break;
293 case PCM_EVENT_CAPTURE_TERMINATED:
294 log_verbose("Capture terminated");
295 dev->source.format = AUDIO_FORMAT_ANY;
296 ret = release_buffer(dev);
297 if (ret != EOK) {
298 log_error("Failed to release buffer: %s",
299 str_error(ret));
300 }
301 audio_pcm_unregister_event_callback(dev->sess);
302 break;
303 case PCM_EVENT_PLAYBACK_TERMINATED:
304 log_verbose("Playback Terminated");
305 dev->sink.format = AUDIO_FORMAT_ANY;
306 ret = release_buffer(dev);
307 if (ret != EOK) {
308 log_error("Failed to release buffer: %s",
309 str_error(ret));
310 }
311 audio_pcm_unregister_event_callback(dev->sess);
312 break;
313 case PCM_EVENT_FRAMES_CAPTURED:
314 ret = audio_source_push_data(&dev->source,
315 dev->buffer.position, dev->buffer.fragment_size);
316 advance_buffer(dev, dev->buffer.fragment_size);
317 if (ret != EOK)
318 log_warning("Failed to push recorded data");
319 break;
320 case 0:
321 log_info("Device event callback hangup");
322 return;
323 }
324 }
325}
326
327/**
328 * Test format against hardware limits.
329 * @param sink audio playback device.
330 * @return Error code.
331 */
332static errno_t device_check_format(audio_sink_t *sink)
333{
334 assert(sink);
335 audio_device_t *dev = sink->private_data;
336 assert(dev);
337 /* Check whether we are running */
338 if (is_running(dev))
339 return EBUSY;
340 log_verbose("Checking format on sink %s", sink->name);
341 return audio_pcm_test_format(dev->sess, &sink->format.channels,
342 &sink->format.sampling_rate, &sink->format.sample_format);
343}
344
345/**
346 * Get access to device buffer.
347 * @param dev Audio device.
348 * @return Error code.
349 */
350static errno_t get_buffer(audio_device_t *dev)
351{
352 assert(dev);
353 if (!dev->sess) {
354 log_debug("No connection to device");
355 return EIO;
356 }
357 if (dev->buffer.base) {
358 log_debug("We already have a buffer");
359 return EBUSY;
360 }
361
362 /* Ask for largest buffer possible */
363 size_t preferred_size = 0;
364
365 const errno_t ret = audio_pcm_get_buffer(dev->sess, &dev->buffer.base,
366 &preferred_size);
367 if (ret == EOK) {
368 dev->buffer.size = preferred_size;
369 dev->buffer.fragment_size = dev->buffer.size / BUFFER_PARTS;
370 dev->buffer.position = dev->buffer.base;
371 }
372 return ret;
373
374}
375
376/**
377 * Surrender access to device buffer.
378 * @param dev Audio device.
379 * @return Error code.
380 */
381static errno_t release_buffer(audio_device_t *dev)
382{
383 assert(dev);
384 assert(dev->buffer.base);
385
386 const errno_t ret = audio_pcm_release_buffer(dev->sess);
387 if (ret == EOK) {
388 as_area_destroy(dev->buffer.base);
389 dev->buffer.base = NULL;
390 dev->buffer.size = 0;
391 dev->buffer.position = NULL;
392 } else {
393 log_warning("Failed to release buffer: %s", str_error(ret));
394 }
395 return ret;
396}
397
398/**
399 * Move buffer position pointer.
400 * @param dev Audio device.
401 * @param size number of bytes to move forward
402 */
403static void advance_buffer(audio_device_t *dev, size_t size)
404{
405 assert(dev);
406 assert(dev->buffer.position >= dev->buffer.base);
407 assert(dev->buffer.position < (dev->buffer.base + dev->buffer.size));
408 dev->buffer.position += size;
409 if (dev->buffer.position == (dev->buffer.base + dev->buffer.size))
410 dev->buffer.position = dev->buffer.base;
411}
412/**
413 * @}
414 */
Note: See TracBrowser for help on using the repository browser.