1 /*-*- Mode: C; c-basic-offset: 8 -*-*/
2
3 /***
4 This file is part of libcanberra.
5
6 Copyright 2008 Lennart Poettering
7 Joe Marcus Clarke
8
9 libcanberra is free software; you can redistribute it and/or modify
10 it under the terms of the GNU Lesser General Public License as
11 published by the Free Software Foundation, either version 2.1 of the
12 License, or (at your option) any later version.
13
14 libcanberra is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public
20 License along with libcanberra. If not, see
21 <http://www.gnu.org/licenses/>.
22 ***/
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <sys/types.h>
29 #include <sys/ioctl.h>
30 #include <sys/param.h>
31 #include <sys/uio.h>
32 #include <math.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <stdlib.h>
37 #include <poll.h>
38 #include <pthread.h>
39 #include <semaphore.h>
40
41 #ifdef HAVE_MACHINE_SOUNDCARD_H
42 # include <machine/soundcard.h>
43 #else
44 # ifdef HAVE_SOUNDCARD_H
45 # include <soundcard.h>
46 # else
47 # include <sys/soundcard.h>
48 # endif
49 #endif
50
51 #include "canberra.h"
52 #include "common.h"
53 #include "driver.h"
54 #include "llist.h"
55 #include "read-sound-file.h"
56 #include "sound-theme-spec.h"
57 #include "malloc.h"
58
59 struct private;
60
61 struct outstanding {
62 CA_LLIST_FIELDS(struct outstanding);
63 ca_bool_t dead;
64 uint32_t id;
65 ca_finish_callback_t callback;
66 void *userdata;
67 ca_sound_file *file;
68 int pcm;
69 int pipe_fd[2];
70 ca_context *context;
71 };
72
73 struct private {
74 ca_theme_data *theme;
75 ca_mutex *outstanding_mutex;
76 ca_bool_t signal_semaphore;
77 sem_t semaphore;
78 ca_bool_t semaphore_allocated;
79 CA_LLIST_HEAD(struct outstanding, outstanding);
80 };
81
82 #define PRIVATE(c) ((struct private *) ((c)->private))
83
outstanding_free(struct outstanding * o)84 static void outstanding_free(struct outstanding *o) {
85 ca_assert(o);
86
87 if (o->pipe_fd[1] >= 0)
88 close(o->pipe_fd[1]);
89
90 if (o->pipe_fd[0] >= 0)
91 close(o->pipe_fd[0]);
92
93 if (o->file)
94 ca_sound_file_close(o->file);
95
96 if (o->pcm >= 0) {
97 close(o->pcm);
98 o->pcm = -1;
99 }
100
101 ca_free(o);
102 }
103
driver_open(ca_context * c)104 int driver_open(ca_context *c) {
105 struct private *p;
106
107 ca_return_val_if_fail(c, CA_ERROR_INVALID);
108 ca_return_val_if_fail(!c->driver || ca_streq(c->driver, "oss"), CA_ERROR_NODRIVER);
109 ca_return_val_if_fail(!PRIVATE(c), CA_ERROR_STATE);
110
111 if (!(c->private = p = ca_new0(struct private, 1)))
112 return CA_ERROR_OOM;
113
114 if (!(p->outstanding_mutex = ca_mutex_new())) {
115 driver_destroy(c);
116 return CA_ERROR_OOM;
117 }
118
119 if (sem_init(&p->semaphore, 0, 0) < 0) {
120 driver_destroy(c);
121 return CA_ERROR_OOM;
122 }
123
124 p->semaphore_allocated = TRUE;
125
126 return CA_SUCCESS;
127 }
128
driver_destroy(ca_context * c)129 int driver_destroy(ca_context *c) {
130 struct private *p;
131 struct outstanding *out;
132
133 ca_return_val_if_fail(c, CA_ERROR_INVALID);
134 ca_return_val_if_fail(c->private, CA_ERROR_STATE);
135
136 p = PRIVATE(c);
137
138 if (p->outstanding_mutex) {
139 ca_mutex_lock(p->outstanding_mutex);
140
141 /* Tell all player threads to terminate */
142 for (out = p->outstanding; out; out = out->next) {
143
144 if (out->dead)
145 continue;
146
147 out->dead = TRUE;
148
149 if (out->callback)
150 out->callback(c, out->id, CA_ERROR_DESTROYED, out->userdata);
151
152 /* This will cause the thread to wakeup and terminate */
153 if (out->pipe_fd[1] >= 0) {
154 close(out->pipe_fd[1]);
155 out->pipe_fd[1] = -1;
156 }
157 }
158
159 if (p->semaphore_allocated) {
160 /* Now wait until all players are destroyed */
161 p->signal_semaphore = TRUE;
162 while (p->outstanding) {
163 ca_mutex_unlock(p->outstanding_mutex);
164 sem_wait(&p->semaphore);
165 ca_mutex_lock(p->outstanding_mutex);
166 }
167 }
168
169 ca_mutex_unlock(p->outstanding_mutex);
170 ca_mutex_free(p->outstanding_mutex);
171 }
172
173 if (p->theme)
174 ca_theme_data_free(p->theme);
175
176 if (p->semaphore_allocated)
177 sem_destroy(&p->semaphore);
178
179 ca_free(p);
180
181 c->private = NULL;
182
183 return CA_SUCCESS;
184 }
185
driver_change_device(ca_context * c,const char * device)186 int driver_change_device(ca_context *c, const char *device) {
187 ca_return_val_if_fail(c, CA_ERROR_INVALID);
188 ca_return_val_if_fail(c->private, CA_ERROR_STATE);
189
190 return CA_SUCCESS;
191 }
192
driver_change_props(ca_context * c,ca_proplist * changed,ca_proplist * merged)193 int driver_change_props(ca_context *c, ca_proplist *changed, ca_proplist *merged) {
194 ca_return_val_if_fail(c, CA_ERROR_INVALID);
195 ca_return_val_if_fail(changed, CA_ERROR_INVALID);
196 ca_return_val_if_fail(merged, CA_ERROR_INVALID);
197
198 return CA_SUCCESS;
199 }
200
driver_cache(ca_context * c,ca_proplist * proplist)201 int driver_cache(ca_context *c, ca_proplist *proplist) {
202 ca_return_val_if_fail(c, CA_ERROR_INVALID);
203 ca_return_val_if_fail(proplist, CA_ERROR_INVALID);
204
205 return CA_ERROR_NOTSUPPORTED;
206 }
207
translate_error(int error)208 static int translate_error(int error) {
209
210 switch (error) {
211 case ENODEV:
212 case ENOENT:
213 return CA_ERROR_NOTFOUND;
214 case EACCES:
215 case EPERM:
216 return CA_ERROR_ACCESS;
217 case ENOMEM:
218 return CA_ERROR_OOM;
219 case EBUSY:
220 return CA_ERROR_NOTAVAILABLE;
221 case EINVAL:
222 return CA_ERROR_INVALID;
223 case ENOSYS:
224 return CA_ERROR_NOTSUPPORTED;
225 default:
226 if (ca_debug())
227 fprintf(stderr, "Got unhandled error from OSS: %s\n", strerror(error));
228 return CA_ERROR_IO;
229 }
230 }
231
open_oss(ca_context * c,struct outstanding * out)232 static int open_oss(ca_context *c, struct outstanding *out) {
233 int mode, val, test, ret;
234
235 ca_return_val_if_fail(c, CA_ERROR_INVALID);
236 ca_return_val_if_fail(c->private, CA_ERROR_STATE);
237 ca_return_val_if_fail(out, CA_ERROR_INVALID);
238
239 /* In OSS we have no way to configure a channel mapping for
240 * multichannel streams. We cannot support those files hence */
241 ca_return_val_if_fail(ca_sound_file_get_nchannels(out->file) <= 2, CA_ERROR_NOTSUPPORTED);
242
243 if ((out->pcm = open(c->device ? c->device : "/dev/dsp", O_WRONLY | O_NONBLOCK, 0)) < 0)
244 goto finish_errno;
245
246 if ((mode = fcntl(out->pcm, F_GETFL)) < 0)
247 goto finish_errno;
248
249 mode &= ~O_NONBLOCK;
250
251 if (fcntl(out->pcm, F_SETFL, mode) < 0)
252 goto finish_errno;
253
254 switch (ca_sound_file_get_sample_type(out->file)) {
255 case CA_SAMPLE_U8:
256 val = AFMT_U8;
257 break;
258 case CA_SAMPLE_S16NE:
259 val = AFMT_S16_NE;
260 break;
261 case CA_SAMPLE_S16RE:
262 #if _BYTE_ORDER == _LITTLE_ENDIAN
263 val = AFMT_S16_BE;
264 #else
265 val = AFMT_S16_LE;
266 #endif
267 break;
268 }
269
270 test = val;
271 if (ioctl(out->pcm, SNDCTL_DSP_SETFMT, &val) < 0)
272 goto finish_errno;
273
274 if (val != test) {
275 ret = CA_ERROR_NOTSUPPORTED;
276 goto finish_ret;
277 }
278
279 test = val = (int) ca_sound_file_get_nchannels(out->file);
280 if (ioctl(out->pcm, SNDCTL_DSP_CHANNELS, &val) < 0)
281 goto finish_errno;
282
283 if (val != test) {
284 ret = CA_ERROR_NOTSUPPORTED;
285 goto finish_ret;
286 }
287
288 test = val = (int) ca_sound_file_get_rate(out->file);
289 if (ioctl(out->pcm, SNDCTL_DSP_SPEED, &val) < 0)
290 goto finish_errno;
291
292 /* Check to make sure the configured rate is close enough to the
293 * requested rate. */
294 if (fabs((double) (val - test)) > test * 0.05) {
295 ret = CA_ERROR_NOTSUPPORTED;
296 goto finish_ret;
297 }
298
299 return CA_SUCCESS;
300
301 finish_errno:
302 return translate_error(errno);
303
304 finish_ret:
305 return ret;
306 }
307
308 #define BUFSIZE (4*1024)
309
thread_func(void * userdata)310 static void* thread_func(void *userdata) {
311 struct outstanding *out = userdata;
312 int ret;
313 void *data, *d = NULL;
314 size_t fs, data_size;
315 size_t nbytes = 0;
316 struct pollfd pfd[2];
317 nfds_t n_pfd = 2;
318 struct private *p;
319
320 p = PRIVATE(out->context);
321
322 pthread_detach(pthread_self());
323
324 fs = ca_sound_file_frame_size(out->file);
325 data_size = (BUFSIZE/fs)*fs;
326
327 if (!(data = ca_malloc(data_size))) {
328 ret = CA_ERROR_OOM;
329 goto finish;
330 }
331
332 pfd[0].fd = out->pipe_fd[0];
333 pfd[0].events = POLLIN;
334 pfd[0].revents = 0;
335 pfd[1].fd = out->pcm;
336 pfd[1].events = POLLOUT;
337 pfd[1].revents = 0;
338
339 for (;;) {
340 ssize_t bytes_written;
341
342 if (out->dead)
343 break;
344
345 if (poll(pfd, n_pfd, -1) < 0) {
346 ret = CA_ERROR_SYSTEM;
347 goto finish;
348 }
349
350 /* We have been asked to shut down */
351 if (pfd[0].revents)
352 break;
353
354 if (pfd[1].revents != POLLOUT) {
355 ret = CA_ERROR_IO;
356 goto finish;
357 }
358
359 if (nbytes <= 0) {
360 nbytes = data_size;
361
362 if ((ret = ca_sound_file_read_arbitrary(out->file, data, &nbytes)) < 0)
363 goto finish;
364
365 d = data;
366 }
367
368 if (nbytes <= 0)
369 break;
370
371 if ((bytes_written = write(out->pcm, d, nbytes)) <= 0) {
372 ret = translate_error(errno);
373 goto finish;
374 }
375
376 nbytes -= (size_t) bytes_written;
377 d = (uint8_t*) d + (size_t) bytes_written;
378 }
379
380 ret = CA_SUCCESS;
381
382 finish:
383
384 ca_free(data);
385
386 if (!out->dead)
387 if (out->callback)
388 out->callback(out->context, out->id, ret, out->userdata);
389
390 ca_mutex_lock(p->outstanding_mutex);
391
392 CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
393
394 if (!p->outstanding && p->signal_semaphore)
395 sem_post(&p->semaphore);
396
397 outstanding_free(out);
398
399 ca_mutex_unlock(p->outstanding_mutex);
400
401 return NULL;
402 }
403
driver_play(ca_context * c,uint32_t id,ca_proplist * proplist,ca_finish_callback_t cb,void * userdata)404 int driver_play(ca_context *c, uint32_t id, ca_proplist *proplist, ca_finish_callback_t cb, void *userdata) {
405 struct private *p;
406 struct outstanding *out = NULL;
407 int ret;
408 pthread_t thread;
409
410 ca_return_val_if_fail(c, CA_ERROR_INVALID);
411 ca_return_val_if_fail(proplist, CA_ERROR_INVALID);
412 ca_return_val_if_fail(!userdata || cb, CA_ERROR_INVALID);
413 ca_return_val_if_fail(c->private, CA_ERROR_STATE);
414
415 p = PRIVATE(c);
416
417 if (!(out = ca_new0(struct outstanding, 1))) {
418 ret = CA_ERROR_OOM;
419 goto finish;
420 }
421
422 out->context = c;
423 out->id = id;
424 out->callback = cb;
425 out->userdata = userdata;
426 out->pipe_fd[0] = out->pipe_fd[1] = -1;
427 out->pcm = -1;
428
429 if (pipe(out->pipe_fd) < 0) {
430 ret = CA_ERROR_SYSTEM;
431 goto finish;
432 }
433
434 if ((ret = ca_lookup_sound(&out->file, NULL, &p->theme, c->props, proplist)) < 0)
435 goto finish;
436
437 if ((ret = open_oss(c, out)) < 0)
438 goto finish;
439
440 /* OK, we're ready to go, so let's add this to our list */
441 ca_mutex_lock(p->outstanding_mutex);
442 CA_LLIST_PREPEND(struct outstanding, p->outstanding, out);
443 ca_mutex_unlock(p->outstanding_mutex);
444
445 if (pthread_create(&thread, NULL, thread_func, out) < 0) {
446 ret = CA_ERROR_OOM;
447
448 ca_mutex_lock(p->outstanding_mutex);
449 CA_LLIST_REMOVE(struct outstanding, p->outstanding, out);
450 ca_mutex_unlock(p->outstanding_mutex);
451
452 goto finish;
453 }
454
455 ret = CA_SUCCESS;
456
457 finish:
458
459 /* We keep the outstanding struct around if we need clean up later to */
460 if (ret != CA_SUCCESS)
461 outstanding_free(out);
462
463 return ret;
464 }
465
driver_cancel(ca_context * c,uint32_t id)466 int driver_cancel(ca_context *c, uint32_t id) {
467 struct private *p;
468 struct outstanding *out;
469
470 ca_return_val_if_fail(c, CA_ERROR_INVALID);
471 ca_return_val_if_fail(c->private, CA_ERROR_STATE);
472
473 p = PRIVATE(c);
474
475 ca_mutex_lock(p->outstanding_mutex);
476
477 for (out = p->outstanding; out; out = out->next) {
478
479 if (out->id != id)
480 continue;
481
482 if (out->dead)
483 continue;
484
485 out->dead = TRUE;
486
487 if (out->callback)
488 out->callback(c, out->id, CA_ERROR_CANCELED, out->userdata);
489
490 /* This will cause the thread to wakeup and terminate */
491 if (out->pipe_fd[1] >= 0) {
492 close(out->pipe_fd[1]);
493 out->pipe_fd[1] = -1;
494 }
495 }
496
497 ca_mutex_unlock(p->outstanding_mutex);
498
499 return CA_SUCCESS;
500 }
501
driver_playing(ca_context * c,uint32_t id,int * playing)502 int driver_playing(ca_context *c, uint32_t id, int *playing) {
503 struct private *p;
504 struct outstanding *out;
505
506 ca_return_val_if_fail(c, CA_ERROR_INVALID);
507 ca_return_val_if_fail(c->private, CA_ERROR_STATE);
508 ca_return_val_if_fail(playing, CA_ERROR_INVALID);
509
510 p = PRIVATE(c);
511
512 *playing = 0;
513
514 ca_mutex_lock(p->outstanding_mutex);
515
516 for (out = p->outstanding; out; out = out->next) {
517
518 if (out->dead ||
519 out->id != id)
520 continue;
521
522 *playing = 1;
523 break;
524 }
525
526 ca_mutex_unlock(p->outstanding_mutex);
527
528 return CA_SUCCESS;
529 }
530