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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since a99cbc1e was 7f9d97f3, checked in by Jakub Jermar <jakub@…>, 10 years ago

Make gettimeofday() return the actual microseconds.

Enhance struct tm to also have a field to hold microseconds and make
sure that this information propagates from the RTC driver.

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