DeaDBeeF (as in 0xDEADBEEF) is an audio player for GNU/Linux, BSD, OpenSolaris and probably other UNIX-like systems. ok dcoppa@
361 lines
7.9 KiB
C
361 lines
7.9 KiB
C
/*
|
|
* sndio - Output plugin using OpenBSD sndio API
|
|
* Copyright (c) 2012 Alexandr Shadchin <shadchin@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sndio.h>
|
|
#include "../../deadbeef.h"
|
|
|
|
static DB_output_t plugin;
|
|
DB_functions_t *deadbeef;
|
|
|
|
static int state = OUTPUT_STATE_STOPPED;
|
|
static int sndio_terminate;
|
|
static intptr_t sndio_tid;
|
|
static uintptr_t sndio_mutex;
|
|
|
|
static struct sio_hdl *hdl;
|
|
static size_t bufsz;
|
|
static char *buf = NULL;
|
|
static float min_db;
|
|
static float vol_db;
|
|
|
|
static int sndio_init(void);
|
|
static int sndio_free(void);
|
|
static int sndio_setformat(ddb_waveformat_t *);
|
|
static int sndio_play(void);
|
|
static int sndio_stop(void);
|
|
static int sndio_pause(void);
|
|
static int sndio_unpause(void);
|
|
static void sndio_thread(void *context);
|
|
static int sndio_callback(char *stream, int len);
|
|
|
|
static void
|
|
vol_cb(void *unused, unsigned int newvol)
|
|
{
|
|
float newvol_db;
|
|
|
|
newvol_db = min_db * (1 - (float)newvol / SIO_MAXVOL);
|
|
if (newvol_db == vol_db)
|
|
return;
|
|
vol_db = newvol_db;
|
|
deadbeef->volume_set_db(vol_db);
|
|
}
|
|
|
|
static void
|
|
pause_do(void)
|
|
{
|
|
if (!hdl)
|
|
return;
|
|
|
|
deadbeef->mutex_lock(sndio_mutex);
|
|
sio_stop(hdl);
|
|
sio_start(hdl);
|
|
deadbeef->mutex_unlock(sndio_mutex);
|
|
}
|
|
|
|
static int
|
|
sndio_init(void)
|
|
{
|
|
struct sio_par par, askpar;
|
|
int buffer_ms = deadbeef->conf_get_int("sndio.buffer", 250);
|
|
const char *device = deadbeef->conf_get_str_fast("sndio.device", SIO_DEVANY);
|
|
|
|
if (plugin.fmt.is_float) {
|
|
fprintf(stderr, "sndio: float format is not supported\n");
|
|
goto error;
|
|
}
|
|
|
|
hdl = sio_open(device, SIO_PLAY, 0);
|
|
if (!hdl) {
|
|
fprintf(stderr, "sndio: failed to open audio device\n");
|
|
goto error;
|
|
}
|
|
|
|
sio_initpar(&par);
|
|
par.pchan = plugin.fmt.channels;
|
|
par.rate = plugin.fmt.samplerate;
|
|
par.bits = plugin.fmt.bps;
|
|
par.bps = SIO_BPS(plugin.fmt.bps);
|
|
par.sig = 1;
|
|
par.le = SIO_LE_NATIVE;
|
|
par.appbufsz = par.rate * buffer_ms / 1000;
|
|
|
|
/* fprintf(stderr, "sndio: %dbit %s %dch %dHz %dbps\n", par.bits, plugin.fmt.is_float ? "float" : "int", par.pchan, par.rate, par.bps); */
|
|
|
|
askpar = par;
|
|
if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) {
|
|
fprintf(stderr, "sndio: failed to set parameters\n");
|
|
goto error;
|
|
}
|
|
|
|
if ((par.bps > 1 && par.le != askpar.le) ||
|
|
(par.bits < par.bps * 8 && !par.msb) ||
|
|
par.bps != askpar.bps ||
|
|
par.sig != askpar.sig ||
|
|
par.pchan != askpar.pchan ||
|
|
par.rate != askpar.rate) {
|
|
fprintf(stderr, "sndio: parameters not supported\n");
|
|
goto error;
|
|
}
|
|
|
|
bufsz = par.bps * par.pchan * par.round;
|
|
buf = malloc(bufsz);
|
|
if (!buf) {
|
|
fprintf(stderr, "sndio: failed malloc buf\n");
|
|
goto error;
|
|
}
|
|
|
|
min_db = deadbeef->volume_get_min_db();
|
|
sio_onvol(hdl, vol_cb, NULL);
|
|
if (!sio_start(hdl)) {
|
|
fprintf(stderr, "sndio: failed to start audio device\n");
|
|
goto error;
|
|
}
|
|
|
|
sndio_tid = deadbeef->thread_start(sndio_thread, NULL);
|
|
|
|
return 0;
|
|
|
|
error:
|
|
sndio_free();
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
sndio_free(void)
|
|
{
|
|
if (sndio_tid) {
|
|
sndio_terminate = 1;
|
|
deadbeef->thread_join(sndio_tid);
|
|
sndio_tid = 0;
|
|
}
|
|
if (hdl) {
|
|
sio_close(hdl);
|
|
hdl = NULL;
|
|
}
|
|
if (buf) {
|
|
free(buf);
|
|
buf = NULL;
|
|
}
|
|
|
|
state = OUTPUT_STATE_STOPPED;
|
|
sndio_terminate = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_setformat(ddb_waveformat_t *fmt)
|
|
{
|
|
if (!memcmp(&plugin.fmt, fmt, sizeof(ddb_waveformat_t)))
|
|
return 0;
|
|
|
|
memcpy(&plugin.fmt, fmt, sizeof(ddb_waveformat_t));
|
|
|
|
if (sndio_tid)
|
|
sndio_free();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_play(void)
|
|
{
|
|
if (!sndio_tid && sndio_init() < 0)
|
|
return -1;
|
|
|
|
state = OUTPUT_STATE_PLAYING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_stop(void)
|
|
{
|
|
if (state != OUTPUT_STATE_STOPPED) {
|
|
state = OUTPUT_STATE_STOPPED;
|
|
pause_do();
|
|
}
|
|
deadbeef->streamer_reset(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_pause(void)
|
|
{
|
|
if (state == OUTPUT_STATE_STOPPED)
|
|
return -1;
|
|
else if (state != OUTPUT_STATE_PAUSED) {
|
|
state = OUTPUT_STATE_PAUSED;
|
|
pause_do();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_unpause(void)
|
|
{
|
|
state = OUTPUT_STATE_PLAYING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
sndio_thread(void *context)
|
|
{
|
|
float newvol_db;
|
|
size_t size;
|
|
|
|
while (!sndio_terminate) {
|
|
if (state != OUTPUT_STATE_PLAYING) {
|
|
usleep(10000);
|
|
continue;
|
|
}
|
|
|
|
deadbeef->mutex_lock(sndio_mutex);
|
|
newvol_db = deadbeef->volume_get_db();
|
|
if (newvol_db != vol_db) {
|
|
vol_db = newvol_db;
|
|
sio_setvol(hdl, (1 - vol_db / min_db) * SIO_MAXVOL);
|
|
}
|
|
size = sndio_callback(buf, bufsz);
|
|
memset(buf + size, 0, bufsz - size);
|
|
size = sio_write(hdl, buf, bufsz);
|
|
deadbeef->mutex_unlock(sndio_mutex);
|
|
if (size != bufsz) {
|
|
fprintf(stderr, "sndio: failed to write buffer\n");
|
|
sndio_free();
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
sndio_callback(char *stream, int len)
|
|
{
|
|
return deadbeef->streamer_read(stream, len);
|
|
}
|
|
|
|
static int
|
|
sndio_get_state(void)
|
|
{
|
|
return state;
|
|
}
|
|
|
|
static int
|
|
sndio_configchanged(void)
|
|
{
|
|
deadbeef->conf_lock();
|
|
deadbeef->sendmessage(DB_EV_REINIT_SOUND, 0, 0, 0);
|
|
deadbeef->conf_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_plugin_message(uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2)
|
|
{
|
|
switch (id) {
|
|
case DB_EV_CONFIGCHANGED:
|
|
sndio_configchanged();
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_plugin_start(void)
|
|
{
|
|
sndio_mutex = deadbeef->mutex_create();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
sndio_plugin_stop(void)
|
|
{
|
|
if (sndio_mutex) {
|
|
deadbeef->mutex_free(sndio_mutex);
|
|
sndio_mutex = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DB_plugin_t *
|
|
sndio_load(DB_functions_t *api)
|
|
{
|
|
deadbeef = api;
|
|
|
|
return DB_PLUGIN(&plugin);
|
|
}
|
|
|
|
static const char settings_dlg[] =
|
|
"property \"Device\" entry sndio.device " SIO_DEVANY ";\n"
|
|
"property \"Buffer size (ms)\" entry sndio.buffer 250;\n";
|
|
|
|
/* define plugin interface */
|
|
static DB_output_t plugin = {
|
|
DB_PLUGIN_SET_API_VERSION
|
|
.plugin.version_major = 1,
|
|
.plugin.version_minor = 0,
|
|
.plugin.type = DB_PLUGIN_OUTPUT,
|
|
.plugin.id = "sndio",
|
|
.plugin.name = "sndio output plugin",
|
|
.plugin.descr = "plays sound through sndio library",
|
|
.plugin.copyright =
|
|
"Copyright (c) 2012 Alexandr Shadchin <shadchin@openbsd.org>\n"
|
|
"\n"
|
|
"Permission to use, copy, modify, and distribute this software for any\n"
|
|
"purpose with or without fee is hereby granted, provided that the above\n"
|
|
"copyright notice and this permission notice appear in all copies.\n"
|
|
"\n"
|
|
"THE SOFTWARE IS PROVIDED 'AS IS' AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n"
|
|
"WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n"
|
|
"MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n"
|
|
"ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n"
|
|
"WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n"
|
|
"ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n"
|
|
"OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n",
|
|
.plugin.website = "http://www.openbsd.org/",
|
|
.plugin.start = sndio_plugin_start,
|
|
.plugin.stop = sndio_plugin_stop,
|
|
.plugin.configdialog = settings_dlg,
|
|
.plugin.message = sndio_plugin_message,
|
|
.init = sndio_init,
|
|
.free = sndio_free,
|
|
.setformat = sndio_setformat,
|
|
.play = sndio_play,
|
|
.stop = sndio_stop,
|
|
.pause = sndio_pause,
|
|
.unpause = sndio_unpause,
|
|
.state = sndio_get_state,
|
|
.has_volume = 1,
|
|
.fmt.samplerate = 48000,
|
|
.fmt.channels = 2,
|
|
.fmt.bps = 16,
|
|
.fmt.is_float = 0,
|
|
.fmt.channelmask = DDB_SPEAKER_FRONT_LEFT | DDB_SPEAKER_FRONT_RIGHT
|
|
};
|