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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since e172429 was e172429, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 8 years ago

Let audio_pcm_query_cap() return value separately from error code.

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