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

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

hound: implement device source events

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