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