1 /*
2     OSS output plugin for DeaDBeeF Player
3     Copyright (C) 2009-2014 Alexey Yakovenko and contributors
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #ifdef HAVE_CONFIG_H
19 #  include "../../config.h"
20 #endif
21 #include <stdint.h>
22 #include <unistd.h>
23 #ifdef __linux__
24 #include <sys/prctl.h>
25 #endif
26 #include <stdio.h>
27 #include <string.h>
28 #if HAVE_SYS_SOUNDCARD_H
29 #include <sys/soundcard.h>
30 #else
31 #include <soundcard.h>
32 #endif
33 #include <fcntl.h>
34 #include <sys/ioctl.h>
35 #include <stdlib.h>
36 #include "../../deadbeef.h"
37 
38 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
39 #define trace(fmt,...)
40 
41 static DB_output_t plugin;
42 DB_functions_t *deadbeef;
43 
44 static intptr_t oss_tid;
45 static int oss_terminate;
46 static int oss_rate = 44100;
47 static int state;
48 static int fd;
49 static uintptr_t mutex;
50 
51 static char oss_device[100];
52 
53 #define BLOCKSIZE 8192
54 
55 static void
56 oss_thread (void *context);
57 
58 static int
59 oss_callback (char *stream, int len);
60 
61 int
oss_set_hwparams(ddb_waveformat_t * fmt)62 oss_set_hwparams (ddb_waveformat_t *fmt) {
63     int samplefmt;
64     switch (fmt->bps) {
65     case 8:
66         samplefmt = AFMT_S8;
67         break;
68     case 16:
69         samplefmt = AFMT_S16_NE;
70         break;
71     default:
72         samplefmt = AFMT_S16_NE;
73         break;
74     }
75     if (ioctl (fd, SNDCTL_DSP_SETFMT, &samplefmt) == -1) {
76         fprintf (stderr, "oss: failed to set format (return: %d)\n", samplefmt);
77         perror ("SNDCTL_DSP_SETFMT");
78         return -1;
79     }
80 
81     int channels = fmt->channels;
82     if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
83         if (channels != 2) {
84             fprintf (stderr, "oss: failed to set %d channels, trying fallback to stereo\n", fmt->channels);
85             channels = 2;
86             if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
87                 fprintf (stderr, "oss: stereo fallback failed\n");
88                 perror ("SNDCTL_DSP_CHANNELS");
89                 return -1;
90             }
91         }
92         else {
93                 fprintf (stderr, "oss: failed to set %d channels\n", fmt->channels);
94                 perror ("SNDCTL_DSP_CHANNELS");
95                 return -1;
96         }
97     }
98     int rate = fmt->samplerate;
99     if (ioctl (fd, SNDCTL_DSP_SPEED, &rate) == -1) {
100         fprintf (stderr, "oss: can't switch to %d samplerate\n", rate);
101         perror ("SNDCTL_DSP_CHANNELS");
102         return -1;
103     }
104 
105     plugin.fmt.samplerate = rate;
106     plugin.fmt.channels = channels;
107     plugin.fmt.is_float = 0;
108     switch (samplefmt) {
109     case AFMT_S8:
110         plugin.fmt.bps = 8;
111         break;
112     case AFMT_S16_LE:
113     case AFMT_S16_BE:
114         plugin.fmt.bps = 16;
115         break;
116     default:
117         fprintf (stderr, "oss: unsupported output format: 0x%X\n", samplefmt);
118         return -1;
119     }
120     plugin.fmt.channelmask = 0;
121     for (int i = 0; i < plugin.fmt.channels; i++) {
122         plugin.fmt.channelmask |= 1 << i;
123     }
124 
125     return 0;
126 }
127 
128 static int
oss_init(void)129 oss_init (void) {
130     trace ("oss_init\n");
131     state = OUTPUT_STATE_STOPPED;
132     oss_terminate = 0;
133     mutex = 0;
134 
135     // prepare oss for playback
136     fd = open (oss_device, O_WRONLY);
137     if (fd == -1) {
138         fprintf (stderr, "oss: failed to open file %s\n", oss_device);
139         perror (oss_device);
140         plugin.free ();
141         return -1;
142     }
143 
144     oss_set_hwparams (&plugin.fmt);
145 
146     mutex = deadbeef->mutex_create ();
147 
148     oss_tid = deadbeef->thread_start (oss_thread, NULL);
149     return 0;
150 }
151 
152 static int
oss_free(void)153 oss_free (void) {
154     trace ("oss_free\n");
155     if (!oss_terminate) {
156         if (oss_tid) {
157             oss_terminate = 1;
158             deadbeef->thread_join (oss_tid);
159         }
160         oss_tid = 0;
161         state = OUTPUT_STATE_STOPPED;
162         oss_terminate = 0;
163         if (fd) {
164             close (fd);
165             fd = 0;
166         }
167         if (mutex) {
168             deadbeef->mutex_free (mutex);
169             mutex = 0;
170         }
171     }
172     return 0;
173 }
174 
175 static int
oss_play(void)176 oss_play (void) {
177     if (!oss_tid) {
178         if (oss_init () < 0) {
179             return -1;
180         }
181     }
182     state = OUTPUT_STATE_PLAYING;
183     return 0;
184 }
185 
186 static int
oss_stop(void)187 oss_stop (void) {
188     state = OUTPUT_STATE_STOPPED;
189     deadbeef->streamer_reset (1);
190     return 0;
191 }
192 
193 static int
oss_pause(void)194 oss_pause (void) {
195     if (state == OUTPUT_STATE_STOPPED) {
196         return -1;
197     }
198     state = OUTPUT_STATE_PAUSED;
199     return 0;
200 }
201 
202 
203 static int
oss_setformat(ddb_waveformat_t * fmt)204 oss_setformat (ddb_waveformat_t *fmt) {
205     trace ("oss_setformat\n");
206     if (!fd) {
207         memcpy (&plugin.fmt, fmt, sizeof (ddb_waveformat_t));
208     }
209     if (!memcmp (fmt, &plugin.fmt, sizeof (ddb_waveformat_t))) {
210         return 0;
211     }
212 
213     int _state = state;
214 
215     deadbeef->mutex_lock (mutex);
216 
217     if (fd) {
218         close (fd);
219         fd = 0;
220     }
221     fd = open (oss_device, O_WRONLY);
222     memcpy (&plugin.fmt, fmt, sizeof (ddb_waveformat_t));
223     if (0 != oss_set_hwparams (fmt)) {
224         deadbeef->mutex_unlock (mutex);
225         return -1;
226     }
227 
228     deadbeef->mutex_unlock (mutex);
229 
230     switch (_state) {
231     case OUTPUT_STATE_STOPPED:
232         return oss_stop ();
233     case OUTPUT_STATE_PLAYING:
234         return oss_play ();
235     case OUTPUT_STATE_PAUSED:
236         if (0 != oss_play ()) {
237             return -1;
238         }
239         if (0 != oss_pause ()) {
240             return -1;
241         }
242         break;
243     }
244     return 0;
245 }
246 
247 static int
oss_unpause(void)248 oss_unpause (void) {
249     oss_play ();
250     return 0;
251 }
252 
253 static int
oss_get_rate(void)254 oss_get_rate (void) {
255     return oss_rate;
256 }
257 
258 static int
oss_get_bps(void)259 oss_get_bps (void) {
260     return 16;
261 }
262 
263 static int
oss_get_channels(void)264 oss_get_channels (void) {
265     return 2;
266 }
267 
268 static int
oss_get_endianness(void)269 oss_get_endianness (void) {
270 #if WORDS_BIGENDIAN
271     return 1;
272 #else
273     return 0;
274 #endif
275 }
276 
277 static void
oss_thread(void * context)278 oss_thread (void *context) {
279 #ifdef __linux__
280     prctl (PR_SET_NAME, "deadbeef-oss", 0, 0, 0, 0);
281 #endif
282     for (;;) {
283         if (oss_terminate) {
284             break;
285         }
286         if (state != OUTPUT_STATE_PLAYING || !deadbeef->streamer_ok_to_read (-1)) {
287             usleep (10000);
288             continue;
289         }
290 
291         int res = 0;
292 
293         int sample_size = plugin.fmt.channels * (plugin.fmt.bps / 8);
294         int bs = BLOCKSIZE;
295         int mod = bs % sample_size;
296         if (mod > 0) {
297             bs -= mod;
298         }
299         char buf[bs];
300 
301         int write_size = oss_callback (buf, sizeof (buf));
302         deadbeef->mutex_lock (mutex);
303         if ( write_size > 0 ) {
304             res = write (fd, buf, write_size);
305         }
306 
307         deadbeef->mutex_unlock (mutex);
308 //        if (res != write_size) {
309 //            perror ("oss write");
310 //            fprintf (stderr, "oss: failed to write buffer\n");
311 //        }
312         usleep (1000); // this must be here to prevent mutex deadlock
313     }
314 }
315 
316 static int
oss_callback(char * stream,int len)317 oss_callback (char *stream, int len) {
318     return deadbeef->streamer_read (stream, len);
319 }
320 
321 static int
oss_get_state(void)322 oss_get_state (void) {
323     return state;
324 }
325 
326 static int
oss_configchanged(void)327 oss_configchanged (void) {
328     deadbeef->conf_lock ();
329     const char *dev = deadbeef->conf_get_str_fast ("oss.device", "/dev/dsp");
330     if (strcmp (dev, oss_device)) {
331         strncpy (oss_device, dev, sizeof (oss_device)-1);
332         trace ("oss: config option changed, restarting\n");
333         deadbeef->sendmessage (DB_EV_REINIT_SOUND, 0, 0, 0);
334     }
335     deadbeef->conf_unlock ();
336     return 0;
337 }
338 
339 static int
oss_message(uint32_t id,uintptr_t ctx,uint32_t p1,uint32_t p2)340 oss_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
341     switch (id) {
342     case DB_EV_CONFIGCHANGED:
343         oss_configchanged ();
344         break;
345     }
346     return 0;
347 }
348 
349 static int
oss_plugin_start(void)350 oss_plugin_start (void) {
351     deadbeef->conf_get_str ("oss.device", "/dev/dsp", oss_device, sizeof (oss_device));
352     return 0;
353 }
354 
355 static int
oss_plugin_stop(void)356 oss_plugin_stop (void) {
357     return 0;
358 }
359 
360 DB_plugin_t *
oss_load(DB_functions_t * api)361 oss_load (DB_functions_t *api) {
362     deadbeef = api;
363     return DB_PLUGIN (&plugin);
364 }
365 
366 static const char settings_dlg[] =
367     "property \"Device file\" entry oss.device /dev/dsp;\n";
368 
369 // define plugin interface
370 static DB_output_t plugin = {
371     .plugin.api_vmajor = 1,
372     .plugin.api_vminor = 0,
373     .plugin.version_major = 1,
374     .plugin.version_minor = 0,
375     .plugin.type = DB_PLUGIN_OUTPUT,
376     .plugin.id = "oss",
377     .plugin.name = "OSS output plugin",
378     .plugin.descr = "plays sound via OSS API",
379     .plugin.copyright =
380         "OSS output plugin for DeaDBeeF Player\n"
381         "Copyright (C) 2009-2014 Alexey Yakovenko and contributors\n"
382         "\n"
383         "This program is free software; you can redistribute it and/or\n"
384         "modify it under the terms of the GNU General Public License\n"
385         "as published by the Free Software Foundation; either version 2\n"
386         "of the License, or (at your option) any later version.\n"
387         "\n"
388         "This program is distributed in the hope that it will be useful,\n"
389         "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
390         "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
391         "GNU General Public License for more details.\n"
392         "\n"
393         "You should have received a copy of the GNU General Public License\n"
394         "along with this program; if not, write to the Free Software\n"
395         "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n"
396     ,
397     .plugin.website = "http://deadbeef.sf.net",
398     .plugin.start = oss_plugin_start,
399     .plugin.stop = oss_plugin_stop,
400     .plugin.configdialog = settings_dlg,
401     .plugin.message = oss_message,
402     .init = oss_init,
403     .free = oss_free,
404     .setformat = oss_setformat,
405     .play = oss_play,
406     .stop = oss_stop,
407     .pause = oss_pause,
408     .unpause = oss_unpause,
409     .state = oss_get_state,
410     .fmt = {-1},
411 };
412