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

Last change on this file was fafb8e5, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 6 years ago

Mechanically lowercase IPC_SET_*/IPC_GET_*

  • Property mode set to 100644
File size: 11.4 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>
[76d0981d]42#include <stdbool.h>
[737b4c0]43#include <str.h>
44#include <str_error.h>
[b6e481b]45#include <as.h>
[737b4c0]46
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
[b7fd2a0]53static errno_t device_sink_connection_callback(audio_sink_t *sink, bool new);
54static errno_t device_source_connection_callback(audio_source_t *source, bool new);
[984a9ba]55static void device_event_callback(ipc_call_t *icall, void *arg);
[1433ecda]56static errno_t device_check_format(audio_sink_t *sink);
[b7fd2a0]57static errno_t get_buffer(audio_device_t *dev);
58static errno_t 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 */
[c81132d]64 return dev->buffer.base != NULL;
[d1f144a]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 */
[b7fd2a0]74errno_t audio_device_init(audio_device_t *dev, service_id_t id, const char *name)
[737b4c0]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 */
[1433ecda]120audio_source_t *audio_device_get_source(audio_device_t *dev)
[c799138]121{
122 assert(dev);
[e172429]123 sysarg_t val;
[b7fd2a0]124 errno_t rc = audio_pcm_query_cap(dev->sess, AUDIO_CAP_CAPTURE, &val);
[e172429]125 if (rc == EOK && val)
[c799138]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 */
[1433ecda]136audio_sink_t *audio_device_get_sink(audio_device_t *dev)
[c799138]137{
138 assert(dev);
[e172429]139 sysarg_t val;
[b7fd2a0]140 errno_t rc = audio_pcm_query_cap(dev->sess, AUDIO_CAP_PLAYBACK, &val);
[e172429]141 if (rc == EOK && val)
[c799138]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 */
[1433ecda]155static errno_t device_sink_connection_callback(audio_sink_t *sink, bool new)
[737b4c0]156{
[1df3018a]157 assert(sink);
158 audio_device_t *dev = sink->private_data;
[fa60cd69]159 if (new && list_count(&sink->connections) == 1) {
[f3fced0]160 log_verbose("First connection on device sink '%s'", sink->name);
161
[b7fd2a0]162 errno_t ret = get_buffer(dev);
[737b4c0]163 if (ret != EOK) {
164 log_error("Failed to get device buffer: %s",
165 str_error(ret));
166 return ret;
167 }
[018ab50]168 audio_pcm_register_event_callback(dev->sess,
[7f84430]169 device_event_callback, dev);
[f3fced0]170
[7c3fb9b]171 /*
172 * Fill the buffer first. Fill the first two fragments,
173 * so that we stay one fragment ahead
174 */
[7e706a3]175 pcm_format_silence(dev->buffer.base, dev->buffer.size,
176 &dev->sink.format);
[4eff63c]177 //TODO add underrun detection.
178 const size_t size = dev->buffer.fragment_size * 2;
179 /* We never cross the end of the buffer here */
180 audio_sink_mix_inputs(&dev->sink, dev->buffer.position, size);
181 advance_buffer(dev, size);
[2cc5c835]182
[5c98bb28]183 const unsigned frames = dev->buffer.fragment_size /
184 pcm_format_frame_size(&dev->sink.format);
185 log_verbose("Fragment frame count %u", frames);
[92b638c]186 ret = audio_pcm_start_playback_fragment(dev->sess, frames,
[2cc5c835]187 dev->sink.format.channels, dev->sink.format.sampling_rate,
188 dev->sink.format.sample_format);
[737b4c0]189 if (ret != EOK) {
190 log_error("Failed to start playback: %s",
191 str_error(ret));
[992ef56]192 release_buffer(dev);
[737b4c0]193 return ret;
194 }
195 }
[fa60cd69]196 if (list_count(&sink->connections) == 0) {
[13df13c8]197 assert(!new);
[6da3baec]198 log_verbose("Removed last connection on device sink '%s'",
199 sink->name);
[b7fd2a0]200 errno_t ret = audio_pcm_stop_playback(dev->sess);
[737b4c0]201 if (ret != EOK) {
[842eecdd]202 log_error("Failed to stop playback: %s",
[737b4c0]203 str_error(ret));
204 return ret;
205 }
206 }
207 return EOK;
208}
209
[c799138]210/**
211 * Handle connection addition and removal.
212 * @param source audio source that is connected or disconnected.
213 * @param new True of a connection was added, false otherwise.
214 * @return Error code.
215 *
216 * Starts capture on first connection. Stops capture when there are no
217 * connections.
218 */
[b7fd2a0]219static errno_t device_source_connection_callback(audio_source_t *source, bool new)
[737b4c0]220{
[1df3018a]221 assert(source);
222 audio_device_t *dev = source->private_data;
[5c98bb28]223 if (new && list_count(&source->connections) == 1) {
[b7fd2a0]224 errno_t ret = get_buffer(dev);
[737b4c0]225 if (ret != EOK) {
226 log_error("Failed to get device buffer: %s",
227 str_error(ret));
228 return ret;
229 }
[c799138]230
231 //TODO set and test format
232
[5c98bb28]233 const unsigned frames = dev->buffer.fragment_size /
234 pcm_format_frame_size(&dev->sink.format);
[92b638c]235 ret = audio_pcm_start_capture_fragment(dev->sess, frames,
[c799138]236 dev->source.format.channels,
237 dev->source.format.sampling_rate,
238 dev->source.format.sample_format);
[737b4c0]239 if (ret != EOK) {
240 log_error("Failed to start recording: %s",
241 str_error(ret));
[992ef56]242 release_buffer(dev);
[737b4c0]243 return ret;
244 }
[fa60cd69]245 }
246 if (list_count(&source->connections) == 0) { /* Disconnected */
247 assert(!new);
[b7fd2a0]248 errno_t ret = audio_pcm_stop_capture_immediate(dev->sess);
[737b4c0]249 if (ret != EOK) {
250 log_error("Failed to start recording: %s",
251 str_error(ret));
252 return ret;
253 }
254 }
255
256 return EOK;
257}
258
[eed4139]259/** Audio device event handler.
260 *
[984a9ba]261 * @param icall Initial call structure.
262 * @param arg (unused)
263 *
[c799138]264 */
[984a9ba]265static void device_event_callback(ipc_call_t *icall, void *arg)
[992ef56]266{
[bd41ac52]267 struct timespec time1;
[338d54a7]268 errno_t ret;
269
[992ef56]270 audio_device_t *dev = arg;
271 assert(dev);
[76d0981d]272 while (true) {
[992ef56]273 ipc_call_t call;
[984a9ba]274 async_get_call(&call);
275 async_answer_0(&call, EOK);
276
[fafb8e5]277 switch (ipc_get_imethod(&call)) {
[338d54a7]278 case PCM_EVENT_FRAMES_PLAYED:
[7f6d84b]279 getuptime(&time1);
[4eff63c]280 //TODO add underrun detection.
281 /* We never cross the end of the buffer here */
282 audio_sink_mix_inputs(&dev->sink, dev->buffer.position,
283 dev->buffer.fragment_size);
284 advance_buffer(dev, dev->buffer.fragment_size);
[bd41ac52]285 struct timespec time2;
[7f6d84b]286 getuptime(&time2);
[bd41ac52]287 log_verbose("Time to mix sources: %lld\n",
288 NSEC2USEC(ts_sub_diff(&time2, &time1)));
[fd5c0b7]289 break;
[338d54a7]290 case PCM_EVENT_CAPTURE_TERMINATED:
[8e77b60e]291 log_verbose("Capture terminated");
292 dev->source.format = AUDIO_FORMAT_ANY;
[338d54a7]293 ret = release_buffer(dev);
[8e77b60e]294 if (ret != EOK) {
295 log_error("Failed to release buffer: %s",
296 str_error(ret));
297 }
298 audio_pcm_unregister_event_callback(dev->sess);
299 break;
[338d54a7]300 case PCM_EVENT_PLAYBACK_TERMINATED:
[8e77b60e]301 log_verbose("Playback Terminated");
302 dev->sink.format = AUDIO_FORMAT_ANY;
[338d54a7]303 ret = release_buffer(dev);
[8e77b60e]304 if (ret != EOK) {
305 log_error("Failed to release buffer: %s",
306 str_error(ret));
307 }
308 audio_pcm_unregister_event_callback(dev->sess);
[3efaeb6]309 break;
[338d54a7]310 case PCM_EVENT_FRAMES_CAPTURED:
311 ret = audio_source_push_data(&dev->source,
[4eff63c]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");
[fd5c0b7]316 break;
[3efaeb6]317 case 0:
[8e77b60e]318 log_info("Device event callback hangup");
[ab07cf0]319 return;
[992ef56]320 }
321 }
322}
[eca79ff]323
[c799138]324/**
325 * Test format against hardware limits.
326 * @param sink audio playback device.
327 * @return Error code.
328 */
[1433ecda]329static errno_t device_check_format(audio_sink_t *sink)
[389ef25]330{
331 assert(sink);
332 audio_device_t *dev = sink->private_data;
333 assert(dev);
[d1f144a]334 /* Check whether we are running */
335 if (is_running(dev))
336 return EBUSY;
[389ef25]337 log_verbose("Checking format on sink %s", sink->name);
338 return audio_pcm_test_format(dev->sess, &sink->format.channels,
339 &sink->format.sampling_rate, &sink->format.sample_format);
340}
[992ef56]341
[c799138]342/**
343 * Get access to device buffer.
344 * @param dev Audio device.
345 * @return Error code.
346 */
[b7fd2a0]347static errno_t get_buffer(audio_device_t *dev)
[992ef56]348{
349 assert(dev);
350 if (!dev->sess) {
351 log_debug("No connection to device");
352 return EIO;
353 }
354 if (dev->buffer.base) {
355 log_debug("We already have a buffer");
356 return EBUSY;
357 }
358
[eca79ff]359 /* Ask for largest buffer possible */
360 size_t preferred_size = 0;
[992ef56]361
[b7fd2a0]362 const errno_t ret = audio_pcm_get_buffer(dev->sess, &dev->buffer.base,
[eca79ff]363 &preferred_size);
364 if (ret == EOK) {
365 dev->buffer.size = preferred_size;
[5c98bb28]366 dev->buffer.fragment_size = dev->buffer.size / BUFFER_PARTS;
[eca79ff]367 dev->buffer.position = dev->buffer.base;
368 }
[5c98bb28]369 return ret;
370
[992ef56]371}
372
[c799138]373/**
374 * Surrender access to device buffer.
375 * @param dev Audio device.
376 * @return Error code.
377 */
[b7fd2a0]378static errno_t release_buffer(audio_device_t *dev)
[992ef56]379{
[2cc5c835]380 assert(dev);
381 assert(dev->buffer.base);
[992ef56]382
[b7fd2a0]383 const errno_t ret = audio_pcm_release_buffer(dev->sess);
[992ef56]384 if (ret == EOK) {
[b6e481b]385 as_area_destroy(dev->buffer.base);
[992ef56]386 dev->buffer.base = NULL;
387 dev->buffer.size = 0;
388 dev->buffer.position = NULL;
[2cc5c835]389 } else {
[7373117]390 log_warning("Failed to release buffer: %s", str_error(ret));
[992ef56]391 }
392 return ret;
393}
[eca79ff]394
[c799138]395/**
[4eff63c]396 * Move buffer position pointer.
[c799138]397 * @param dev Audio device.
[4eff63c]398 * @param size number of bytes to move forward
[c799138]399 */
[4eff63c]400static void advance_buffer(audio_device_t *dev, size_t size)
[eca79ff]401{
402 assert(dev);
403 assert(dev->buffer.position >= dev->buffer.base);
404 assert(dev->buffer.position < (dev->buffer.base + dev->buffer.size));
405 dev->buffer.position += size;
406 if (dev->buffer.position == (dev->buffer.base + dev->buffer.size))
407 dev->buffer.position = dev->buffer.base;
408}
[737b4c0]409/**
410 * @}
411 */
Note: See TracBrowser for help on using the repository browser.