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
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 <loc.h>
41#include <str.h>
42#include <str_error.h>
43
44
45#include "audio_device.h"
46#include "log.h"
47
48/* hardwired to provide ~21ms per fragment */
49#define BUFFER_PARTS 16
50
51static int device_sink_connection_callback(audio_sink_t *sink, bool new);
52static int device_source_connection_callback(audio_source_t *source, bool new);
53static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void *arg);
54static int device_check_format(audio_sink_t* sink);
55static int get_buffer(audio_device_t *dev);
56static int release_buffer(audio_device_t *dev);
57static void advance_buffer(audio_device_t *dev, size_t size);
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}
64
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 */
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);
78 dev->sess = audio_pcm_open_service(id);
79 if (!dev->sess) {
80 log_debug("Failed to connect to device \"%s\"", name);
81 return ENOMEM;
82 }
83
84 audio_sink_init(&dev->sink, name, dev, device_sink_connection_callback,
85 device_check_format, NULL, &AUDIO_FORMAT_ANY);
86 audio_source_init(&dev->source, name, dev,
87 device_source_connection_callback, NULL, &AUDIO_FORMAT_ANY);
88
89 /* Init buffer members */
90 dev->buffer.base = NULL;
91 dev->buffer.position = NULL;
92 dev->buffer.size = 0;
93 dev->buffer.fragment_size = 0;
94
95 log_verbose("Initialized device (%p) '%s' with id %u.",
96 dev, dev->name, dev->id);
97
98 return EOK;
99}
100
101/**
102 * Restore resource cplaimed during initialization.
103 * @param dev The device to release.
104 *
105 * NOT IMPLEMENTED
106 */
107void audio_device_fini(audio_device_t *dev)
108{
109 //TODO implement;
110}
111
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 */
149static int device_sink_connection_callback(audio_sink_t* sink, bool new)
150{
151 assert(sink);
152 audio_device_t *dev = sink->private_data;
153 if (new && list_count(&sink->connections) == 1) {
154 log_verbose("First connection on device sink '%s'", sink->name);
155
156 int ret = get_buffer(dev);
157 if (ret != EOK) {
158 log_error("Failed to get device buffer: %s",
159 str_error(ret));
160 return ret;
161 }
162 audio_pcm_register_event_callback(dev->sess,
163 device_event_callback, dev);\
164
165 /* Fill the buffer first. Fill the first two fragments,
166 * so that we stay one fragment ahead */
167 pcm_format_silence(dev->buffer.base, dev->buffer.size,
168 &dev->sink.format);
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);
174
175 const unsigned frames = dev->buffer.fragment_size /
176 pcm_format_frame_size(&dev->sink.format);
177 log_verbose("Fragment frame count %u", frames);
178 ret = audio_pcm_start_playback_fragment(dev->sess, frames,
179 dev->sink.format.channels, dev->sink.format.sampling_rate,
180 dev->sink.format.sample_format);
181 if (ret != EOK) {
182 log_error("Failed to start playback: %s",
183 str_error(ret));
184 release_buffer(dev);
185 return ret;
186 }
187 }
188 if (list_count(&sink->connections) == 0) {
189 assert(!new);
190 log_verbose("Removed last connection on device sink '%s'",
191 sink->name);
192 int ret = audio_pcm_stop_playback(dev->sess);
193 if (ret != EOK) {
194 log_error("Failed to stop playback: %s",
195 str_error(ret));
196 return ret;
197 }
198 dev->sink.format = AUDIO_FORMAT_ANY;
199 ret = release_buffer(dev);
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
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 int 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 int 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 int ret = audio_pcm_stop_capture(dev->sess);
248 if (ret != EOK) {
249 log_error("Failed to start recording: %s",
250 str_error(ret));
251 return ret;
252 }
253 source->format = AUDIO_FORMAT_ANY;
254 ret = release_buffer(dev);
255 if (ret != EOK) {
256 log_error("Failed to release buffer: %s",
257 str_error(ret));
258 return ret;
259 }
260 audio_pcm_unregister_event_callback(dev->sess);
261 }
262
263 return EOK;
264}
265
266/**
267 * Audio device event handler.
268 * @param iid initial call id.
269 * @param icall initial call structure.
270 * @param arg (unused)
271 */
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);
282 switch(IPC_GET_IMETHOD(call)) {
283 case PCM_EVENT_FRAMES_PLAYED: {
284 struct timeval time1;
285 getuptime(&time1);
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);
291 struct timeval time2;
292 getuptime(&time2);
293 log_verbose("Time to mix sources: %li\n",
294 tv_sub(&time2, &time1));
295 break;
296 }
297 case PCM_EVENT_PLAYBACK_TERMINATED:
298 log_verbose("Playback terminated!");
299 return;
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");
306 break;
307 }
308 case PCM_EVENT_CAPTURE_TERMINATED:
309 log_verbose("Recording terminated!");
310 return;
311 }
312
313 }
314}
315
316/**
317 * Test format against hardware limits.
318 * @param sink audio playback device.
319 * @return Error code.
320 */
321static int device_check_format(audio_sink_t* sink)
322{
323 assert(sink);
324 audio_device_t *dev = sink->private_data;
325 assert(dev);
326 /* Check whether we are running */
327 if (is_running(dev))
328 return EBUSY;
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}
333
334/**
335 * Get access to device buffer.
336 * @param dev Audio device.
337 * @return Error code.
338 */
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
351 /* Ask for largest buffer possible */
352 size_t preferred_size = 0;
353
354 const int ret = audio_pcm_get_buffer(dev->sess, &dev->buffer.base,
355 &preferred_size);
356 if (ret == EOK) {
357 dev->buffer.size = preferred_size;
358 dev->buffer.fragment_size = dev->buffer.size / BUFFER_PARTS;
359 dev->buffer.position = dev->buffer.base;
360 }
361 return ret;
362
363}
364
365/**
366 * Surrender access to device buffer.
367 * @param dev Audio device.
368 * @return Error code.
369 */
370static int release_buffer(audio_device_t *dev)
371{
372 assert(dev);
373 assert(dev->buffer.base);
374
375 const int ret = audio_pcm_release_buffer(dev->sess);
376 if (ret == EOK) {
377 dev->buffer.base = NULL;
378 dev->buffer.size = 0;
379 dev->buffer.position = NULL;
380 } else {
381 log_debug("Failed to release buffer: %s", str_error(ret));
382 }
383 return ret;
384}
385
386/**
387 * Move buffer position pointer.
388 * @param dev Audio device.
389 * @param size number of bytes to move forward
390 */
391static void advance_buffer(audio_device_t *dev, size_t size)
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}
400/**
401 * @}
402 */
Note: See TracBrowser for help on using the repository browser.