/*
OSS output plugin for DeaDBeeF Player
Copyright (C) 2009-2014 Alexey Yakovenko and contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifdef HAVE_CONFIG_H
# include "../../config.h"
#endif
#include
#include
#ifdef __linux__
#include
#endif
#include
#include
#if HAVE_SYS_SOUNDCARD_H
#include
#else
#include
#endif
#include
#include
#include
#include "../../deadbeef.h"
//#define trace(...) { fprintf(stderr, __VA_ARGS__); }
#define trace(fmt,...)
static DB_output_t plugin;
DB_functions_t *deadbeef;
static intptr_t oss_tid;
static int oss_terminate;
static int oss_rate = 44100;
static int state;
static int fd;
static uintptr_t mutex;
static char oss_device[100];
#define BLOCKSIZE 8192
static void
oss_thread (void *context);
static int
oss_callback (char *stream, int len);
int
oss_set_hwparams (ddb_waveformat_t *fmt) {
int samplefmt;
switch (fmt->bps) {
case 8:
samplefmt = AFMT_S8;
break;
case 16:
samplefmt = AFMT_S16_NE;
break;
default:
samplefmt = AFMT_S16_NE;
break;
}
if (ioctl (fd, SNDCTL_DSP_SETFMT, &samplefmt) == -1) {
fprintf (stderr, "oss: failed to set format\n");
perror ("SNDCTL_DSP_SETFMT");
return -1;
}
int channels = fmt->channels;
if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
if (channels != 2) {
fprintf (stderr, "oss: failed to set %d channels, trying fallback to stereo\n", fmt->channels);
channels = 2;
if (ioctl (fd, SNDCTL_DSP_CHANNELS, &channels) == -1) {
fprintf (stderr, "oss: stereo fallback failed\n");
perror ("SNDCTL_DSP_CHANNELS");
return -1;
}
}
else {
fprintf (stderr, "oss: failed to set %d channels\n", fmt->channels);
perror ("SNDCTL_DSP_CHANNELS");
return -1;
}
}
int rate = fmt->samplerate;
if (ioctl (fd, SNDCTL_DSP_SPEED, &rate) == -1) {
fprintf (stderr, "oss: can't switch to %d samplerate\n", rate);
perror ("SNDCTL_DSP_CHANNELS");
return -1;
}
plugin.fmt.samplerate = rate;
plugin.fmt.channels = channels;
plugin.fmt.is_float = 0;
switch (samplefmt) {
case AFMT_S8:
plugin.fmt.bps = 8;
break;
case AFMT_S16_LE:
case AFMT_S16_BE:
plugin.fmt.bps = 16;
break;
default:
fprintf (stderr, "oss: unsupported output format: 0x%X\n", samplefmt);
return -1;
}
plugin.fmt.channelmask = 0;
for (int i = 0; i < plugin.fmt.channels; i++) {
plugin.fmt.channelmask |= 1 << i;
}
return 0;
}
static int
oss_init (void) {
trace ("oss_init\n");
state = OUTPUT_STATE_STOPPED;
oss_terminate = 0;
mutex = 0;
// prepare oss for playback
fd = open (oss_device, O_WRONLY);
if (fd == -1) {
fprintf (stderr, "oss: failed to open file %s\n", oss_device);
perror (oss_device);
plugin.free ();
return -1;
}
oss_set_hwparams (&plugin.fmt);
mutex = deadbeef->mutex_create ();
oss_tid = deadbeef->thread_start (oss_thread, NULL);
return 0;
}
static int
oss_free (void) {
trace ("oss_free\n");
if (!oss_terminate) {
if (oss_tid) {
oss_terminate = 1;
deadbeef->thread_join (oss_tid);
}
oss_tid = 0;
state = OUTPUT_STATE_STOPPED;
oss_terminate = 0;
if (fd) {
close (fd);
fd = 0;
}
if (mutex) {
deadbeef->mutex_free (mutex);
mutex = 0;
}
}
return 0;
}
static int
oss_play (void) {
if (!oss_tid) {
if (oss_init () < 0) {
return -1;
}
}
state = OUTPUT_STATE_PLAYING;
return 0;
}
static int
oss_stop (void) {
state = OUTPUT_STATE_STOPPED;
deadbeef->streamer_reset (1);
return 0;
}
static int
oss_pause (void) {
if (state == OUTPUT_STATE_STOPPED) {
return -1;
}
state = OUTPUT_STATE_PAUSED;
return 0;
}
static int
oss_setformat (ddb_waveformat_t *fmt) {
trace ("oss_setformat\n");
if (!fd) {
memcpy (&plugin.fmt, fmt, sizeof (ddb_waveformat_t));
}
if (!memcmp (fmt, &plugin.fmt, sizeof (ddb_waveformat_t))) {
return 0;
}
int _state = state;
int v4workaround = deadbeef->conf_get_int ("oss.v4workaround", 0);
deadbeef->mutex_lock (mutex);
if (v4workaround) {
if (fd) {
close (fd);
fd = 0;
}
fd = open (oss_device, O_WRONLY);
memcpy (&plugin.fmt, fmt, sizeof (ddb_waveformat_t));
}
if (0 != oss_set_hwparams (fmt)) {
deadbeef->mutex_unlock (mutex);
return -1;
}
deadbeef->mutex_unlock (mutex);
switch (_state) {
case OUTPUT_STATE_STOPPED:
return oss_stop ();
case OUTPUT_STATE_PLAYING:
return oss_play ();
case OUTPUT_STATE_PAUSED:
if (0 != oss_play ()) {
return -1;
}
if (0 != oss_pause ()) {
return -1;
}
break;
}
return 0;
}
static int
oss_unpause (void) {
oss_play ();
return 0;
}
static int
oss_get_rate (void) {
return oss_rate;
}
static int
oss_get_bps (void) {
return 16;
}
static int
oss_get_channels (void) {
return 2;
}
static int
oss_get_endianness (void) {
#if WORDS_BIGENDIAN
return 1;
#else
return 0;
#endif
}
static void
oss_thread (void *context) {
#ifdef __linux__
prctl (PR_SET_NAME, "deadbeef-oss", 0, 0, 0, 0);
#endif
for (;;) {
if (oss_terminate) {
break;
}
if (state != OUTPUT_STATE_PLAYING || !deadbeef->streamer_ok_to_read (-1)) {
usleep (10000);
continue;
}
int res = 0;
int sample_size = plugin.fmt.channels * (plugin.fmt.bps / 8);
int bs = BLOCKSIZE;
int mod = bs % sample_size;
if (mod > 0) {
bs -= mod;
}
char buf[bs];
int write_size = oss_callback (buf, sizeof (buf));
deadbeef->mutex_lock (mutex);
if ( write_size > 0 ) {
res = write (fd, buf, write_size);
}
deadbeef->mutex_unlock (mutex);
// if (res != write_size) {
// perror ("oss write");
// fprintf (stderr, "oss: failed to write buffer\n");
// }
usleep (1000); // this must be here to prevent mutex deadlock
}
}
static int
oss_callback (char *stream, int len) {
return deadbeef->streamer_read (stream, len);
}
static int
oss_get_state (void) {
return state;
}
static int
oss_configchanged (void) {
deadbeef->conf_lock ();
const char *dev = deadbeef->conf_get_str_fast ("oss.device", "/dev/dsp");
if (strcmp (dev, oss_device)) {
strncpy (oss_device, dev, sizeof (oss_device)-1);
trace ("oss: config option changed, restarting\n");
deadbeef->sendmessage (DB_EV_REINIT_SOUND, 0, 0, 0);
}
deadbeef->conf_unlock ();
return 0;
}
static int
oss_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
switch (id) {
case DB_EV_CONFIGCHANGED:
oss_configchanged ();
break;
}
return 0;
}
static int
oss_plugin_start (void) {
deadbeef->conf_get_str ("oss.device", "/dev/dsp", oss_device, sizeof (oss_device));
return 0;
}
static int
oss_plugin_stop (void) {
return 0;
}
DB_plugin_t *
oss_load (DB_functions_t *api) {
deadbeef = api;
return DB_PLUGIN (&plugin);
}
static const char settings_dlg[] =
"property \"Device file\" entry oss.device /dev/dsp;\n"
"property \"OSS4 samplerate bug workaround\" checkbox oss.v4workaround 0;\n";
// define plugin interface
static DB_output_t plugin = {
.plugin.api_vmajor = 1,
.plugin.api_vminor = 0,
.plugin.version_major = 1,
.plugin.version_minor = 0,
.plugin.type = DB_PLUGIN_OUTPUT,
.plugin.id = "oss",
.plugin.name = "OSS output plugin",
.plugin.descr = "plays sound via OSS API",
.plugin.copyright =
"OSS output plugin for DeaDBeeF Player\n"
"Copyright (C) 2009-2014 Alexey Yakovenko and contributors\n"
"\n"
"This program is free software; you can redistribute it and/or\n"
"modify it under the terms of the GNU General Public License\n"
"as published by the Free Software Foundation; either version 2\n"
"of the License, or (at your option) any later version.\n"
"\n"
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n"
"\n"
"You should have received a copy of the GNU General Public License\n"
"along with this program; if not, write to the Free Software\n"
"Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
,
.plugin.website = "http://deadbeef.sf.net",
.plugin.start = oss_plugin_start,
.plugin.stop = oss_plugin_stop,
.plugin.configdialog = settings_dlg,
.plugin.message = oss_message,
.init = oss_init,
.free = oss_free,
.setformat = oss_setformat,
.play = oss_play,
.stop = oss_stop,
.pause = oss_pause,
.unpause = oss_unpause,
.state = oss_get_state,
.fmt = {-1},
};