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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 80d9d83 was ff03f79, checked in by Jan Vesely <jano.vesely@…>, 12 years ago

hound: Don't use immediate stop on event playback.

Otherwise we might skip the last 20ms of data.

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