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