source: mainline/uspace/srv/audio/hound/audio_device.c@ 924786bf

Last change on this file since 924786bf was 76d0981d, checked in by Jiri Svoboda <jiri@…>, 8 years ago

Use proper boolean constant in while loops.

  • Property mode set to 100644
File size: 11.6 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(cap_call_handle_t icall_handle, 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 /* Fill the buffer first. Fill the first two fragments,
173 * so that we stay one fragment ahead */
174 pcm_format_silence(dev->buffer.base, dev->buffer.size,
175 &dev->sink.format);
176 //TODO add underrun detection.
177 const size_t size = dev->buffer.fragment_size * 2;
178 /* We never cross the end of the buffer here */
179 audio_sink_mix_inputs(&dev->sink, dev->buffer.position, size);
180 advance_buffer(dev, size);
181
182 const unsigned frames = dev->buffer.fragment_size /
183 pcm_format_frame_size(&dev->sink.format);
184 log_verbose("Fragment frame count %u", frames);
185 ret = audio_pcm_start_playback_fragment(dev->sess, frames,
186 dev->sink.format.channels, dev->sink.format.sampling_rate,
187 dev->sink.format.sample_format);
188 if (ret != EOK) {
189 log_error("Failed to start playback: %s",
190 str_error(ret));
191 release_buffer(dev);
192 return ret;
193 }
194 }
195 if (list_count(&sink->connections) == 0) {
196 assert(!new);
197 log_verbose("Removed last connection on device sink '%s'",
198 sink->name);
199 errno_t ret = audio_pcm_stop_playback(dev->sess);
200 if (ret != EOK) {
201 log_error("Failed to stop playback: %s",
202 str_error(ret));
203 return ret;
204 }
205 }
206 return EOK;
207}
208
209/**
210 * Handle connection addition and removal.
211 * @param source audio source that is connected or disconnected.
212 * @param new True of a connection was added, false otherwise.
213 * @return Error code.
214 *
215 * Starts capture on first connection. Stops capture when there are no
216 * connections.
217 */
218static errno_t device_source_connection_callback(audio_source_t *source, bool new)
219{
220 assert(source);
221 audio_device_t *dev = source->private_data;
222 if (new && list_count(&source->connections) == 1) {
223 errno_t ret = get_buffer(dev);
224 if (ret != EOK) {
225 log_error("Failed to get device buffer: %s",
226 str_error(ret));
227 return ret;
228 }
229
230 //TODO set and test format
231
232 const unsigned frames = dev->buffer.fragment_size /
233 pcm_format_frame_size(&dev->sink.format);
234 ret = audio_pcm_start_capture_fragment(dev->sess, frames,
235 dev->source.format.channels,
236 dev->source.format.sampling_rate,
237 dev->source.format.sample_format);
238 if (ret != EOK) {
239 log_error("Failed to start recording: %s",
240 str_error(ret));
241 release_buffer(dev);
242 return ret;
243 }
244 }
245 if (list_count(&source->connections) == 0) { /* Disconnected */
246 assert(!new);
247 errno_t ret = audio_pcm_stop_capture_immediate(dev->sess);
248 if (ret != EOK) {
249 log_error("Failed to start recording: %s",
250 str_error(ret));
251 return ret;
252 }
253 }
254
255 return EOK;
256}
257
258/** Audio device event handler.
259 *
260 * @param icall_handle Initial call handle.
261 * @param icall Initial call structure.
262 * @param arg (unused)
263 */
264static void device_event_callback(cap_call_handle_t icall_handle,
265 ipc_call_t *icall, void *arg)
266{
267 struct timeval time1;
268 errno_t ret;
269
270 /* Answer initial request */
271 async_answer_0(icall_handle, EOK);
272 audio_device_t *dev = arg;
273 assert(dev);
274 while (true) {
275 ipc_call_t call;
276 cap_call_handle_t chandle = async_get_call(&call);
277 async_answer_0(chandle, EOK);
278 switch (IPC_GET_IMETHOD(call)) {
279 case PCM_EVENT_FRAMES_PLAYED:
280 getuptime(&time1);
281 //TODO add underrun detection.
282 /* We never cross the end of the buffer here */
283 audio_sink_mix_inputs(&dev->sink, dev->buffer.position,
284 dev->buffer.fragment_size);
285 advance_buffer(dev, dev->buffer.fragment_size);
286 struct timeval time2;
287 getuptime(&time2);
288 log_verbose("Time to mix sources: %li\n",
289 tv_sub_diff(&time2, &time1));
290 break;
291 case PCM_EVENT_CAPTURE_TERMINATED:
292 log_verbose("Capture terminated");
293 dev->source.format = AUDIO_FORMAT_ANY;
294 ret = release_buffer(dev);
295 if (ret != EOK) {
296 log_error("Failed to release buffer: %s",
297 str_error(ret));
298 }
299 audio_pcm_unregister_event_callback(dev->sess);
300 break;
301 case PCM_EVENT_PLAYBACK_TERMINATED:
302 log_verbose("Playback Terminated");
303 dev->sink.format = AUDIO_FORMAT_ANY;
304 ret = release_buffer(dev);
305 if (ret != EOK) {
306 log_error("Failed to release buffer: %s",
307 str_error(ret));
308 }
309 audio_pcm_unregister_event_callback(dev->sess);
310 break;
311 case PCM_EVENT_FRAMES_CAPTURED:
312 ret = audio_source_push_data(&dev->source,
313 dev->buffer.position, dev->buffer.fragment_size);
314 advance_buffer(dev, dev->buffer.fragment_size);
315 if (ret != EOK)
316 log_warning("Failed to push recorded data");
317 break;
318 case 0:
319 log_info("Device event callback hangup");
320 return;
321 }
322 }
323}
324
325/**
326 * Test format against hardware limits.
327 * @param sink audio playback device.
328 * @return Error code.
329 */
330static errno_t device_check_format(audio_sink_t *sink)
331{
332 assert(sink);
333 audio_device_t *dev = sink->private_data;
334 assert(dev);
335 /* Check whether we are running */
336 if (is_running(dev))
337 return EBUSY;
338 log_verbose("Checking format on sink %s", sink->name);
339 return audio_pcm_test_format(dev->sess, &sink->format.channels,
340 &sink->format.sampling_rate, &sink->format.sample_format);
341}
342
343/**
344 * Get access to device buffer.
345 * @param dev Audio device.
346 * @return Error code.
347 */
348static errno_t get_buffer(audio_device_t *dev)
349{
350 assert(dev);
351 if (!dev->sess) {
352 log_debug("No connection to device");
353 return EIO;
354 }
355 if (dev->buffer.base) {
356 log_debug("We already have a buffer");
357 return EBUSY;
358 }
359
360 /* Ask for largest buffer possible */
361 size_t preferred_size = 0;
362
363 const errno_t ret = audio_pcm_get_buffer(dev->sess, &dev->buffer.base,
364 &preferred_size);
365 if (ret == EOK) {
366 dev->buffer.size = preferred_size;
367 dev->buffer.fragment_size = dev->buffer.size / BUFFER_PARTS;
368 dev->buffer.position = dev->buffer.base;
369 }
370 return ret;
371
372}
373
374/**
375 * Surrender access to device buffer.
376 * @param dev Audio device.
377 * @return Error code.
378 */
379static errno_t release_buffer(audio_device_t *dev)
380{
381 assert(dev);
382 assert(dev->buffer.base);
383
384 const errno_t ret = audio_pcm_release_buffer(dev->sess);
385 if (ret == EOK) {
386 as_area_destroy(dev->buffer.base);
387 dev->buffer.base = NULL;
388 dev->buffer.size = 0;
389 dev->buffer.position = NULL;
390 } else {
391 log_warning("Failed to release buffer: %s", str_error(ret));
392 }
393 return ret;
394}
395
396/**
397 * Move buffer position pointer.
398 * @param dev Audio device.
399 * @param size number of bytes to move forward
400 */
401static void advance_buffer(audio_device_t *dev, size_t size)
402{
403 assert(dev);
404 assert(dev->buffer.position >= dev->buffer.base);
405 assert(dev->buffer.position < (dev->buffer.base + dev->buffer.size));
406 dev->buffer.position += size;
407 if (dev->buffer.position == (dev->buffer.base + dev->buffer.size))
408 dev->buffer.position = dev->buffer.base;
409}
410/**
411 * @}
412 */
Note: See TracBrowser for help on using the repository browser.