renamed src/audioOutputs/ to src/output/

Again, no CamelCase in the directory name.
This commit is contained in:
Max Kellermann
2008-10-26 11:29:44 +01:00
parent e11355f47d
commit ece8c1347c
14 changed files with 18 additions and 19 deletions

444
src/output/alsa_plugin.c Normal file
View File

@@ -0,0 +1,444 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_ALSA
#define ALSA_PCM_NEW_HW_PARAMS_API
#define ALSA_PCM_NEW_SW_PARAMS_API
static const char default_device[] = "default";
#define MPD_ALSA_RETRY_NR 5
#include "../utils.h"
#include "../log.h"
#include <alsa/asoundlib.h>
typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer,
snd_pcm_uframes_t size);
typedef struct _AlsaData {
const char *device;
/** the mode flags passed to snd_pcm_open */
int mode;
snd_pcm_t *pcmHandle;
alsa_writei_t *writei;
unsigned int buffer_time;
unsigned int period_time;
int sampleSize;
int useMmap;
} AlsaData;
static AlsaData *newAlsaData(void)
{
AlsaData *ret = xmalloc(sizeof(AlsaData));
ret->device = default_device;
ret->mode = 0;
ret->pcmHandle = NULL;
ret->writei = snd_pcm_writei;
ret->useMmap = 0;
ret->buffer_time = 0;
ret->period_time = 0;
return ret;
}
static void freeAlsaData(AlsaData * ad)
{
if (ad->device && ad->device != default_device)
xfree(ad->device);
free(ad);
}
static void
alsa_configure(AlsaData *ad, ConfigParam *param)
{
BlockParam *bp;
if ((bp = getBlockParam(param, "device")))
ad->device = xstrdup(bp->value);
ad->useMmap = getBoolBlockParam(param, "use_mmap", 1);
if (ad->useMmap == CONF_BOOL_UNSET)
ad->useMmap = 0;
if ((bp = getBlockParam(param, "buffer_time")))
ad->buffer_time = atoi(bp->value);
if ((bp = getBlockParam(param, "period_time")))
ad->period_time = atoi(bp->value);
#ifdef SND_PCM_NO_AUTO_RESAMPLE
if (!getBoolBlockParam(param, "auto_resample", true))
ad->mode |= SND_PCM_NO_AUTO_RESAMPLE;
#endif
#ifdef SND_PCM_NO_AUTO_CHANNELS
if (!getBoolBlockParam(param, "auto_channels", true))
ad->mode |= SND_PCM_NO_AUTO_CHANNELS;
#endif
#ifdef SND_PCM_NO_AUTO_FORMAT
if (!getBoolBlockParam(param, "auto_format", true))
ad->mode |= SND_PCM_NO_AUTO_FORMAT;
#endif
}
static void *alsa_initDriver(mpd_unused struct audio_output *ao,
mpd_unused const struct audio_format *audio_format,
ConfigParam * param)
{
/* no need for pthread_once thread-safety when reading config */
static int free_global_registered;
AlsaData *ad = newAlsaData();
if (!free_global_registered) {
atexit((void(*)(void))snd_config_update_free_global);
free_global_registered = 1;
}
if (param)
alsa_configure(ad, param);
return ad;
}
static void alsa_finishDriver(void *data)
{
AlsaData *ad = data;
freeAlsaData(ad);
}
static int alsa_testDefault(void)
{
snd_pcm_t *handle;
int ret = snd_pcm_open(&handle, default_device,
SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (ret) {
WARNING("Error opening default ALSA device: %s\n",
snd_strerror(-ret));
return -1;
} else
snd_pcm_close(handle);
return 0;
}
static snd_pcm_format_t get_bitformat(const struct audio_format *af)
{
switch (af->bits) {
case 8: return SND_PCM_FORMAT_S8;
case 16: return SND_PCM_FORMAT_S16;
case 24: return SND_PCM_FORMAT_S24;
case 32: return SND_PCM_FORMAT_S32;
}
return SND_PCM_FORMAT_UNKNOWN;
}
static int alsa_openDevice(void *data, struct audio_format *audioFormat)
{
AlsaData *ad = data;
snd_pcm_format_t bitformat;
snd_pcm_hw_params_t *hwparams;
snd_pcm_sw_params_t *swparams;
unsigned int sample_rate = audioFormat->sample_rate;
unsigned int channels = audioFormat->channels;
snd_pcm_uframes_t alsa_buffer_size;
snd_pcm_uframes_t alsa_period_size;
int err;
const char *cmd = NULL;
int retry = MPD_ALSA_RETRY_NR;
unsigned int period_time, period_time_ro;
unsigned int buffer_time;
if ((bitformat = get_bitformat(audioFormat)) == SND_PCM_FORMAT_UNKNOWN)
ERROR("ALSA device \"%s\" doesn't support %u bit audio\n",
ad->device, audioFormat->bits);
err = snd_pcm_open(&ad->pcmHandle, ad->device,
SND_PCM_STREAM_PLAYBACK, ad->mode);
if (err < 0) {
ad->pcmHandle = NULL;
goto error;
}
period_time_ro = period_time = ad->period_time;
configure_hw:
/* configure HW params */
snd_pcm_hw_params_alloca(&hwparams);
cmd = "snd_pcm_hw_params_any";
err = snd_pcm_hw_params_any(ad->pcmHandle, hwparams);
if (err < 0)
goto error;
if (ad->useMmap) {
err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams,
SND_PCM_ACCESS_MMAP_INTERLEAVED);
if (err < 0) {
ERROR("Cannot set mmap'ed mode on ALSA device \"%s\": "
" %s\n", ad->device, snd_strerror(-err));
ERROR("Falling back to direct write mode\n");
ad->useMmap = 0;
} else
ad->writei = snd_pcm_mmap_writei;
}
if (!ad->useMmap) {
cmd = "snd_pcm_hw_params_set_access";
err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (err < 0)
goto error;
ad->writei = snd_pcm_writei;
}
err = snd_pcm_hw_params_set_format(ad->pcmHandle, hwparams, bitformat);
if (err == -EINVAL && audioFormat->bits != 16) {
/* fall back to 16 bit, let pcm_utils.c do the conversion */
err = snd_pcm_hw_params_set_format(ad->pcmHandle, hwparams,
SND_PCM_FORMAT_S16);
if (err == 0) {
DEBUG("ALSA device \"%s\": converting %u bit to 16 bit\n",
ad->device, audioFormat->bits);
audioFormat->bits = 16;
}
}
if (err < 0) {
ERROR("ALSA device \"%s\" does not support %u bit audio: "
"%s\n", ad->device, audioFormat->bits, snd_strerror(-err));
goto fail;
}
err = snd_pcm_hw_params_set_channels_near(ad->pcmHandle, hwparams,
&channels);
if (err < 0) {
ERROR("ALSA device \"%s\" does not support %i channels: "
"%s\n", ad->device, (int)audioFormat->channels,
snd_strerror(-err));
goto fail;
}
audioFormat->channels = (int8_t)channels;
err = snd_pcm_hw_params_set_rate_near(ad->pcmHandle, hwparams,
&sample_rate, NULL);
if (err < 0 || sample_rate == 0) {
ERROR("ALSA device \"%s\" does not support %u Hz audio\n",
ad->device, audioFormat->sample_rate);
goto fail;
}
audioFormat->sample_rate = sample_rate;
if (ad->buffer_time > 0) {
buffer_time = ad->buffer_time;
cmd = "snd_pcm_hw_params_set_buffer_time_near";
err = snd_pcm_hw_params_set_buffer_time_near(ad->pcmHandle, hwparams,
&buffer_time, NULL);
if (err < 0)
goto error;
}
if (period_time_ro > 0) {
period_time = period_time_ro;
cmd = "snd_pcm_hw_params_set_period_time_near";
err = snd_pcm_hw_params_set_period_time_near(ad->pcmHandle, hwparams,
&period_time, NULL);
if (err < 0)
goto error;
}
cmd = "snd_pcm_hw_params";
err = snd_pcm_hw_params(ad->pcmHandle, hwparams);
if (err == -EPIPE && --retry > 0 && period_time_ro > 0) {
period_time_ro = period_time_ro >> 1;
goto configure_hw;
} else if (err < 0)
goto error;
if (retry != MPD_ALSA_RETRY_NR)
DEBUG("ALSA period_time set to %d\n", period_time);
cmd = "snd_pcm_hw_params_get_buffer_size";
err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size);
if (err < 0)
goto error;
cmd = "snd_pcm_hw_params_get_period_size";
err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size,
NULL);
if (err < 0)
goto error;
/* configure SW params */
snd_pcm_sw_params_alloca(&swparams);
cmd = "snd_pcm_sw_params_current";
err = snd_pcm_sw_params_current(ad->pcmHandle, swparams);
if (err < 0)
goto error;
cmd = "snd_pcm_sw_params_set_start_threshold";
err = snd_pcm_sw_params_set_start_threshold(ad->pcmHandle, swparams,
alsa_buffer_size -
alsa_period_size);
if (err < 0)
goto error;
cmd = "snd_pcm_sw_params_set_avail_min";
err = snd_pcm_sw_params_set_avail_min(ad->pcmHandle, swparams,
alsa_period_size);
if (err < 0)
goto error;
cmd = "snd_pcm_sw_params";
err = snd_pcm_sw_params(ad->pcmHandle, swparams);
if (err < 0)
goto error;
ad->sampleSize = audio_format_frame_size(audioFormat);
DEBUG("ALSA device \"%s\" will be playing %i bit, %u channel audio at "
"%u Hz\n", ad->device, audioFormat->bits,
channels, sample_rate);
return 0;
error:
if (cmd) {
ERROR("Error opening ALSA device \"%s\" (%s): %s\n",
ad->device, cmd, snd_strerror(-err));
} else {
ERROR("Error opening ALSA device \"%s\": %s\n", ad->device,
snd_strerror(-err));
}
fail:
if (ad->pcmHandle)
snd_pcm_close(ad->pcmHandle);
ad->pcmHandle = NULL;
return -1;
}
static int alsa_errorRecovery(AlsaData * ad, int err)
{
if (err == -EPIPE) {
DEBUG("Underrun on ALSA device \"%s\"\n", ad->device);
} else if (err == -ESTRPIPE) {
DEBUG("ALSA device \"%s\" was suspended\n", ad->device);
}
switch (snd_pcm_state(ad->pcmHandle)) {
case SND_PCM_STATE_PAUSED:
err = snd_pcm_pause(ad->pcmHandle, /* disable */ 0);
break;
case SND_PCM_STATE_SUSPENDED:
err = snd_pcm_resume(ad->pcmHandle);
if (err == -EAGAIN)
return 0;
/* fall-through to snd_pcm_prepare: */
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
err = snd_pcm_prepare(ad->pcmHandle);
break;
case SND_PCM_STATE_DISCONNECTED:
/* so alsa_closeDevice won't try to drain: */
snd_pcm_close(ad->pcmHandle);
ad->pcmHandle = NULL;
break;
/* this is no error, so just keep running */
case SND_PCM_STATE_RUNNING:
err = 0;
break;
default:
/* unknown state, do nothing */
break;
}
return err;
}
static void alsa_dropBufferedAudio(void *data)
{
AlsaData *ad = data;
alsa_errorRecovery(ad, snd_pcm_drop(ad->pcmHandle));
}
static void alsa_closeDevice(void *data)
{
AlsaData *ad = data;
if (ad->pcmHandle) {
if (snd_pcm_state(ad->pcmHandle) == SND_PCM_STATE_RUNNING) {
snd_pcm_drain(ad->pcmHandle);
}
snd_pcm_close(ad->pcmHandle);
ad->pcmHandle = NULL;
}
}
static int alsa_playAudio(void *data, const char *playChunk, size_t size)
{
AlsaData *ad = data;
int ret;
size /= ad->sampleSize;
while (size > 0) {
ret = ad->writei(ad->pcmHandle, playChunk, size);
if (ret == -EAGAIN || ret == -EINTR)
continue;
if (ret < 0) {
if (alsa_errorRecovery(ad, ret) < 0) {
ERROR("closing ALSA device \"%s\" due to write "
"error: %s\n", ad->device,
snd_strerror(-errno));
alsa_closeDevice(ad);
return -1;
}
continue;
}
playChunk += ret * ad->sampleSize;
size -= ret;
}
return 0;
}
const struct audio_output_plugin alsaPlugin = {
.name = "alsa",
.test_default_device = alsa_testDefault,
.init = alsa_initDriver,
.finish = alsa_finishDriver,
.open = alsa_openDevice,
.play = alsa_playAudio,
.cancel = alsa_dropBufferedAudio,
.close = alsa_closeDevice,
};
#else /* HAVE ALSA */
DISABLED_AUDIO_OUTPUT_PLUGIN(alsaPlugin)
#endif /* HAVE_ALSA */

253
src/output/ao_plugin.c Normal file
View File

@@ -0,0 +1,253 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_AO
#include "../utils.h"
#include "../log.h"
#include <ao/ao.h>
static int driverInitCount;
typedef struct _AoData {
int writeSize;
int driverId;
ao_option *options;
ao_device *device;
} AoData;
static AoData *newAoData(void)
{
AoData *ret = xmalloc(sizeof(AoData));
ret->device = NULL;
ret->options = NULL;
return ret;
}
static void audioOutputAo_error(void)
{
if (errno == AO_ENOTLIVE) {
ERROR("not a live ao device\n");
} else if (errno == AO_EOPENDEVICE) {
ERROR("not able to open audio device\n");
} else if (errno == AO_EBADOPTION) {
ERROR("bad driver option\n");
}
}
static void *audioOutputAo_initDriver(struct audio_output *ao,
mpd_unused const struct audio_format *audio_format,
ConfigParam * param)
{
ao_info *ai;
char *duplicated;
char *stk1;
char *stk2;
char *n1;
char *key;
char *value;
char *test;
AoData *ad = newAoData();
BlockParam *blockParam;
if ((blockParam = getBlockParam(param, "write_size"))) {
ad->writeSize = strtol(blockParam->value, &test, 10);
if (*test != '\0') {
FATAL("\"%s\" is not a valid write size at line %i\n",
blockParam->value, blockParam->line);
}
} else
ad->writeSize = 1024;
if (driverInitCount == 0) {
ao_initialize();
}
driverInitCount++;
blockParam = getBlockParam(param, "driver");
if (!blockParam || 0 == strcmp(blockParam->value, "default")) {
ad->driverId = ao_default_driver_id();
} else if ((ad->driverId = ao_driver_id(blockParam->value)) < 0) {
FATAL("\"%s\" is not a valid ao driver at line %i\n",
blockParam->value, blockParam->line);
}
if ((ai = ao_driver_info(ad->driverId)) == NULL) {
FATAL("problems getting driver info for device defined at line %i\n"
"you may not have permission to the audio device\n", param->line);
}
DEBUG("using ao driver \"%s\" for \"%s\"\n", ai->short_name,
audio_output_get_name(ao));
blockParam = getBlockParam(param, "options");
if (blockParam) {
duplicated = xstrdup(blockParam->value);
} else
duplicated = xstrdup("");
if (strlen(duplicated)) {
stk1 = NULL;
n1 = strtok_r(duplicated, ";", &stk1);
while (n1) {
stk2 = NULL;
key = strtok_r(n1, "=", &stk2);
if (!key)
FATAL("problems parsing options \"%s\"\n", n1);
/*found = 0;
for(i=0;i<ai->option_count;i++) {
if(strcmp(ai->options[i],key)==0) {
found = 1;
break;
}
}
if(!found) {
FATAL("\"%s\" is not an option for "
"\"%s\" ao driver\n",key,
ai->short_name);
} */
value = strtok_r(NULL, "", &stk2);
if (!value)
FATAL("problems parsing options \"%s\"\n", n1);
ao_append_option(&ad->options, key, value);
n1 = strtok_r(NULL, ";", &stk1);
}
}
free(duplicated);
return ad;
}
static void freeAoData(AoData * ad)
{
ao_free_options(ad->options);
free(ad);
}
static void audioOutputAo_finishDriver(void *data)
{
AoData *ad = (AoData *)data;
freeAoData(ad);
driverInitCount--;
if (driverInitCount == 0)
ao_shutdown();
}
static void audioOutputAo_dropBufferedAudio(mpd_unused void *data)
{
/* not supported by libao */
}
static void audioOutputAo_closeDevice(void *data)
{
AoData *ad = (AoData *)data;
if (ad->device) {
ao_close(ad->device);
ad->device = NULL;
}
}
static int audioOutputAo_openDevice(void *data,
struct audio_format *audio_format)
{
ao_sample_format format;
AoData *ad = (AoData *)data;
if (ad->device) {
audioOutputAo_closeDevice(ad);
}
format.bits = audio_format->bits;
format.rate = audio_format->sample_rate;
format.byte_format = AO_FMT_NATIVE;
format.channels = audio_format->channels;
ad->device = ao_open_live(ad->driverId, &format, ad->options);
if (ad->device == NULL)
return -1;
return 0;
}
/**
* For whatever reason, libao wants a non-const pointer. Let's hope
* it does not write to the buffer, and use the union deconst hack to
* work around this API misdesign.
*/
static int ao_play_deconst(ao_device *device, const void *output_samples,
uint_32 num_bytes)
{
union {
const void *in;
void *out;
} u;
u.in = output_samples;
return ao_play(device, u.out, num_bytes);
}
static int audioOutputAo_play(void *data, const char *playChunk, size_t size)
{
AoData *ad = (AoData *)data;
size_t chunk_size;
if (ad->device == NULL)
return -1;
while (size > 0) {
chunk_size = (size_t)ad->writeSize > size
? size : (size_t)ad->writeSize;
if (ao_play_deconst(ad->device, playChunk, chunk_size) == 0) {
audioOutputAo_error();
ERROR("closing audio device due to write error\n");
audioOutputAo_closeDevice(ad);
return -1;
}
playChunk += chunk_size;
size -= chunk_size;
}
return 0;
}
const struct audio_output_plugin aoPlugin = {
.name = "ao",
.init = audioOutputAo_initDriver,
.finish = audioOutputAo_finishDriver,
.open = audioOutputAo_openDevice,
.play = audioOutputAo_play,
.cancel = audioOutputAo_dropBufferedAudio,
.close = audioOutputAo_closeDevice,
};
#else
DISABLED_AUDIO_OUTPUT_PLUGIN(aoPlugin)
#endif

290
src/output/fifo_plugin.c Normal file
View File

@@ -0,0 +1,290 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_FIFO
#include "../log.h"
#include "../utils.h"
#include "../timer.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
typedef struct _FifoData {
char *path;
int input;
int output;
int created;
Timer *timer;
} FifoData;
static FifoData *newFifoData(void)
{
FifoData *ret;
ret = xmalloc(sizeof(FifoData));
ret->path = NULL;
ret->input = -1;
ret->output = -1;
ret->created = 0;
ret->timer = NULL;
return ret;
}
static void freeFifoData(FifoData *fd)
{
if (fd->path)
free(fd->path);
if (fd->timer)
timer_free(fd->timer);
free(fd);
}
static void removeFifo(FifoData *fd)
{
DEBUG("Removing FIFO \"%s\"\n", fd->path);
if (unlink(fd->path) < 0) {
ERROR("Could not remove FIFO \"%s\": %s\n",
fd->path, strerror(errno));
return;
}
fd->created = 0;
}
static void closeFifo(FifoData *fd)
{
struct stat st;
if (fd->input >= 0) {
close(fd->input);
fd->input = -1;
}
if (fd->output >= 0) {
close(fd->output);
fd->output = -1;
}
if (fd->created && (stat(fd->path, &st) == 0))
removeFifo(fd);
}
static int makeFifo(FifoData *fd)
{
if (mkfifo(fd->path, 0666) < 0) {
ERROR("Couldn't create FIFO \"%s\": %s\n",
fd->path, strerror(errno));
return -1;
}
fd->created = 1;
return 0;
}
static int checkFifo(FifoData *fd)
{
struct stat st;
if (stat(fd->path, &st) < 0) {
if (errno == ENOENT) {
/* Path doesn't exist */
return makeFifo(fd);
}
ERROR("Failed to stat FIFO \"%s\": %s\n",
fd->path, strerror(errno));
return -1;
}
if (!S_ISFIFO(st.st_mode)) {
ERROR("\"%s\" already exists, but is not a FIFO\n", fd->path);
return -1;
}
return 0;
}
static int openFifo(FifoData *fd)
{
if (checkFifo(fd) < 0)
return -1;
fd->input = open(fd->path, O_RDONLY|O_NONBLOCK);
if (fd->input < 0) {
ERROR("Could not open FIFO \"%s\" for reading: %s\n",
fd->path, strerror(errno));
closeFifo(fd);
return -1;
}
fd->output = open(fd->path, O_WRONLY|O_NONBLOCK);
if (fd->output < 0) {
ERROR("Could not open FIFO \"%s\" for writing: %s\n",
fd->path, strerror(errno));
closeFifo(fd);
return -1;
}
return 0;
}
static void *fifo_initDriver(mpd_unused struct audio_output *ao,
mpd_unused const struct audio_format *audio_format,
ConfigParam *param)
{
FifoData *fd;
BlockParam *blockParam;
char *path;
blockParam = getBlockParam(param, "path");
if (!blockParam) {
FATAL("No \"path\" parameter specified for fifo output "
"defined at line %i\n", param->line);
}
path = parsePath(blockParam->value);
if (!path) {
FATAL("Could not parse \"path\" parameter for fifo output "
"at line %i\n", blockParam->line);
}
fd = newFifoData();
fd->path = path;
if (openFifo(fd) < 0) {
freeFifoData(fd);
return NULL;
}
return fd;
}
static void fifo_finishDriver(void *data)
{
FifoData *fd = (FifoData *)data;
closeFifo(fd);
freeFifoData(fd);
}
static int fifo_openDevice(void *data,
struct audio_format *audio_format)
{
FifoData *fd = (FifoData *)data;
if (fd->timer)
timer_free(fd->timer);
fd->timer = timer_new(audio_format);
return 0;
}
static void fifo_closeDevice(void *data)
{
FifoData *fd = (FifoData *)data;
if (fd->timer) {
timer_free(fd->timer);
fd->timer = NULL;
}
}
static void fifo_dropBufferedAudio(void *data)
{
FifoData *fd = (FifoData *)data;
char buf[FIFO_BUFFER_SIZE];
int bytes = 1;
timer_reset(fd->timer);
while (bytes > 0 && errno != EINTR)
bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
if (bytes < 0 && errno != EAGAIN) {
WARNING("Flush of FIFO \"%s\" failed: %s\n",
fd->path, strerror(errno));
}
}
static int fifo_playAudio(void *data,
const char *playChunk, size_t size)
{
FifoData *fd = (FifoData *)data;
size_t offset = 0;
ssize_t bytes;
if (!fd->timer->started)
timer_start(fd->timer);
else
timer_sync(fd->timer);
timer_add(fd->timer, size);
while (size) {
bytes = write(fd->output, playChunk + offset, size);
if (bytes < 0) {
switch (errno) {
case EAGAIN:
/* The pipe is full, so empty it */
fifo_dropBufferedAudio(fd);
continue;
case EINTR:
continue;
}
ERROR("Closing FIFO output \"%s\" due to write error: "
"%s\n", fd->path, strerror(errno));
fifo_closeDevice(fd);
return -1;
}
size -= bytes;
offset += bytes;
}
return 0;
}
const struct audio_output_plugin fifoPlugin = {
.name = "fifo",
.init = fifo_initDriver,
.finish = fifo_finishDriver,
.open = fifo_openDevice,
.play = fifo_playAudio,
.cancel = fifo_dropBufferedAudio,
.close = fifo_closeDevice,
};
#else /* HAVE_FIFO */
DISABLED_AUDIO_OUTPUT_PLUGIN(fifoPlugin)
#endif /* !HAVE_FIFO */

486
src/output/jack_plugin.c Normal file
View File

@@ -0,0 +1,486 @@
/* jack plug in for the Music Player Daemon (MPD)
* (c)2006 by anarch(anarchsss@gmail.com)
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_JACK
#include "../utils.h"
#include "../log.h"
#include <assert.h>
#include <jack/jack.h>
#include <jack/types.h>
#include <jack/ringbuffer.h>
static const size_t sample_size = sizeof(jack_default_audio_sample_t);
struct jack_data {
struct audio_output *ao;
/* configuration */
const char *name;
const char *output_ports[2];
int ringbuffer_size;
/* for srate() only */
struct audio_format *audio_format;
/* jack library stuff */
jack_port_t *ports[2];
jack_client_t *client;
jack_ringbuffer_t *ringbuffer[2];
int bps;
int shutdown;
};
static struct jack_data *
mpd_jack_new(void)
{
struct jack_data *ret;
ret = xcalloc(sizeof(*ret), 1);
ret->name = "mpd";
ret->ringbuffer_size = 32768;
return ret;
}
static void
mpd_jack_client_free(struct jack_data *jd)
{
assert(jd != NULL);
if (jd->client != NULL) {
jack_deactivate(jd->client);
jack_client_close(jd->client);
jd->client = NULL;
}
if (jd->ringbuffer[0] != NULL) {
jack_ringbuffer_free(jd->ringbuffer[0]);
jd->ringbuffer[0] = NULL;
}
if (jd->ringbuffer[1] != NULL) {
jack_ringbuffer_free(jd->ringbuffer[1]);
jd->ringbuffer[1] = NULL;
}
}
static void
mpd_jack_free(struct jack_data *jd)
{
int i;
assert(jd != NULL);
mpd_jack_client_free(jd);
if (strcmp(jd->name, "mpd") != 0)
xfree(jd->name);
for ( i = ARRAY_SIZE(jd->output_ports); --i >= 0; ) {
if (!jd->output_ports[i])
continue;
xfree(jd->output_ports[i]);
}
free(jd);
}
static void
mpd_jack_finish(void *data)
{
struct jack_data *jd = data;
mpd_jack_free(jd);
}
static int
mpd_jack_srate(mpd_unused jack_nframes_t rate, void *data)
{
struct jack_data *jd = (struct jack_data *)data;
struct audio_format *audioFormat = jd->audio_format;
audioFormat->sample_rate = (int)jack_get_sample_rate(jd->client);
return 0;
}
static int
mpd_jack_process(jack_nframes_t nframes, void *arg)
{
struct jack_data *jd = (struct jack_data *) arg;
jack_default_audio_sample_t *out;
size_t available;
if (nframes <= 0)
return 0;
for (unsigned i = 0; i < 2; ++i) {
available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
assert(available % sample_size == 0);
available /= sample_size;
if (available > nframes)
available = nframes;
out = jack_port_get_buffer(jd->ports[i], nframes);
jack_ringbuffer_read(jd->ringbuffer[i],
(char *)out, available * sample_size);
while (available < nframes)
/* ringbuffer underrun, fill with silence */
out[available++] = 0.0;
}
return 0;
}
static void
mpd_jack_shutdown(void *arg)
{
struct jack_data *jd = (struct jack_data *) arg;
jd->shutdown = 1;
}
static void
set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
{
audio_format->sample_rate = jack_get_sample_rate(jd->client);
DEBUG("samplerate = %u\n", audio_format->sample_rate);
audio_format->channels = 2;
if (audio_format->bits != 16 && audio_format->bits != 24)
audio_format->bits = 24;
jd->bps = audio_format->channels
* sizeof(jack_default_audio_sample_t)
* audio_format->sample_rate;
}
static void
mpd_jack_error(const char *msg)
{
ERROR("jack: %s\n", msg);
}
static void *
mpd_jack_init(struct audio_output *ao,
mpd_unused const struct audio_format *audio_format,
ConfigParam *param)
{
struct jack_data *jd;
BlockParam *bp;
char *endptr;
int val;
char *cp = NULL;
jd = mpd_jack_new();
jd->ao = ao;
DEBUG("mpd_jack_init (pid=%d)\n", getpid());
if (param == NULL)
return jd;
if ( (bp = getBlockParam(param, "ports")) ) {
DEBUG("output_ports=%s\n", bp->value);
if (!(cp = strchr(bp->value, ',')))
FATAL("expected comma and a second value for '%s' "
"at line %d: %s\n",
bp->name, bp->line, bp->value);
*cp = '\0';
jd->output_ports[0] = xstrdup(bp->value);
*cp++ = ',';
if (!*cp)
FATAL("expected a second value for '%s' at line %d: "
"%s\n", bp->name, bp->line, bp->value);
jd->output_ports[1] = xstrdup(cp);
if (strchr(cp,','))
FATAL("Only %d values are supported for '%s' "
"at line %d\n",
(int)ARRAY_SIZE(jd->output_ports),
bp->name, bp->line);
}
if ( (bp = getBlockParam(param, "ringbuffer_size")) ) {
errno = 0;
val = strtol(bp->value, &endptr, 10);
if ( errno == 0 && endptr != bp->value) {
jd->ringbuffer_size = val < 32768 ? 32768 : val;
DEBUG("ringbuffer_size=%d\n", jd->ringbuffer_size);
} else {
FATAL("%s is not a number; ringbuf_size=%d\n",
bp->value, jd->ringbuffer_size);
}
}
if ( (bp = getBlockParam(param, "name"))
&& (strcmp(bp->value, "mpd") != 0) ) {
jd->name = xstrdup(bp->value);
DEBUG("name=%s\n", jd->name);
}
return jd;
}
static int
mpd_jack_test_default_device(void)
{
return 0;
}
static int
mpd_jack_connect(struct jack_data *jd, struct audio_format *audio_format)
{
const char **jports;
char *port_name;
jd->audio_format = audio_format;
if ( (jd->client = jack_client_new(jd->name)) == NULL ) {
ERROR("jack server not running?\n");
return -1;
}
jack_set_error_function(mpd_jack_error);
jack_set_process_callback(jd->client, mpd_jack_process, jd);
jack_set_sample_rate_callback(jd->client, mpd_jack_srate, jd);
jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
if ( jack_activate(jd->client) ) {
ERROR("cannot activate client\n");
return -1;
}
jd->ports[0] = jack_port_register(jd->client, "left",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if ( !jd->ports[0] ) {
ERROR("Cannot register left output port.\n");
return -1;
}
jd->ports[1] = jack_port_register(jd->client, "right",
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if ( !jd->ports[1] ) {
ERROR("Cannot register right output port.\n");
return -1;
}
/* hay que buscar que hay */
if (!jd->output_ports[1] &&
(jports = jack_get_ports(jd->client, NULL, NULL,
JackPortIsPhysical | JackPortIsInput))) {
jd->output_ports[0] = jports[0];
jd->output_ports[1] = jports[1] ? jports[1] : jports[0];
DEBUG("output_ports: %s %s\n",
jd->output_ports[0], jd->output_ports[1]);
free(jports);
}
if ( jd->output_ports[1] ) {
jd->ringbuffer[0] = jack_ringbuffer_create(jd->ringbuffer_size);
jd->ringbuffer[1] = jack_ringbuffer_create(jd->ringbuffer_size);
memset(jd->ringbuffer[0]->buf, 0, jd->ringbuffer[0]->size);
memset(jd->ringbuffer[1]->buf, 0, jd->ringbuffer[1]->size);
port_name = xmalloc(sizeof(char)*(7+strlen(jd->name)));
sprintf(port_name, "%s:left", jd->name);
if ( (jack_connect(jd->client, port_name,
jd->output_ports[0])) != 0 ) {
ERROR("%s is not a valid Jack Client / Port\n",
jd->output_ports[0]);
free(port_name);
return -1;
}
sprintf(port_name, "%s:right", jd->name);
if ( (jack_connect(jd->client, port_name,
jd->output_ports[1])) != 0 ) {
ERROR("%s is not a valid Jack Client / Port\n",
jd->output_ports[1]);
free(port_name);
return -1;
}
free(port_name);
}
return 1;
}
static int
mpd_jack_open(void *data, struct audio_format *audio_format)
{
struct jack_data *jd = data;
assert(jd != NULL);
if (jd->client == NULL && mpd_jack_connect(jd, audio_format) < 0) {
mpd_jack_client_free(jd);
return -1;
}
set_audioformat(jd, audio_format);
return 0;
}
static void
mpd_jack_close(mpd_unused void *data)
{
/*mpd_jack_finish(audioOutput);*/
}
static void
mpd_jack_cancel (mpd_unused void *data)
{
}
static inline jack_default_audio_sample_t
sample_16_to_jack(int16_t sample)
{
return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
}
static void
mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
unsigned num_samples)
{
jack_default_audio_sample_t sample;
while (num_samples-- > 0) {
sample = sample_16_to_jack(*src++);
jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
sizeof(sample));
sample = sample_16_to_jack(*src++);
jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
sizeof(sample));
}
}
static inline jack_default_audio_sample_t
sample_24_to_jack(int32_t sample)
{
return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
}
static void
mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
unsigned num_samples)
{
jack_default_audio_sample_t sample;
while (num_samples-- > 0) {
sample = sample_24_to_jack(*src++);
jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
sizeof(sample));
sample = sample_24_to_jack(*src++);
jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
sizeof(sample));
}
}
static void
mpd_jack_write_samples(struct jack_data *jd, const void *src,
unsigned num_samples)
{
switch (jd->audio_format->bits) {
case 16:
mpd_jack_write_samples_16(jd, (const int16_t*)src,
num_samples);
break;
case 24:
mpd_jack_write_samples_24(jd, (const int32_t*)src,
num_samples);
break;
default:
assert(false);
}
}
static int
mpd_jack_play(void *data, const char *buff, size_t size)
{
struct jack_data *jd = data;
const size_t frame_size = audio_format_frame_size(jd->audio_format);
size_t space, space1;
if (jd->shutdown) {
ERROR("Refusing to play, because there is no client thread.\n");
mpd_jack_client_free(jd);
audio_output_closed(jd->ao);
return 0;
}
assert(size % frame_size == 0);
size /= frame_size;
while (size > 0 && !jd->shutdown) {
space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]);
if (space > space1)
/* send data symmetrically */
space = space1;
space /= sample_size;
if (space > 0) {
if (space > size)
space = size;
mpd_jack_write_samples(jd, buff, space);
buff += space * frame_size;
size -= space;
} else {
/* XXX do something more intelligent to
synchronize */
my_usleep(10000);
}
}
return 0;
}
const struct audio_output_plugin jackPlugin = {
.name = "jack",
.test_default_device = mpd_jack_test_default_device,
.init = mpd_jack_init,
.finish = mpd_jack_finish,
.open = mpd_jack_open,
.play = mpd_jack_play,
.cancel = mpd_jack_cancel,
.close = mpd_jack_close,
};
#else /* HAVE JACK */
DISABLED_AUDIO_OUTPUT_PLUGIN(jackPlugin)
#endif /* HAVE_JACK */

280
src/output/mvp_plugin.c Normal file
View File

@@ -0,0 +1,280 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* Media MVP audio output based on code from MVPMC project:
* http://mvpmc.sourceforge.net/
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_MVP
#include "../utils.h"
#include "../log.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
typedef struct {
unsigned long dsp_status;
unsigned long stream_decode_type;
unsigned long sample_rate;
unsigned long bit_rate;
unsigned long raw[64 / sizeof(unsigned long)];
} aud_status_t;
#define MVP_SET_AUD_STOP _IOW('a',1,int)
#define MVP_SET_AUD_PLAY _IOW('a',2,int)
#define MVP_SET_AUD_PAUSE _IOW('a',3,int)
#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int)
#define MVP_SET_AUD_SRC _IOW('a',5,int)
#define MVP_SET_AUD_MUTE _IOW('a',6,int)
#define MVP_SET_AUD_BYPASS _IOW('a',8,int)
#define MVP_SET_AUD_CHANNEL _IOW('a',9,int)
#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t)
#define MVP_SET_AUD_VOLUME _IOW('a',13,int)
#define MVP_GET_AUD_VOLUME _IOR('a',14,int)
#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int)
#define MVP_SET_AUD_FORMAT _IOW('a',16,int)
#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*)
#define MVP_SET_AUD_STC _IOW('a',22,long long int *)
#define MVP_SET_AUD_SYNC _IOW('a',23,int)
#define MVP_SET_AUD_END_STREAM _IOW('a',25,int)
#define MVP_SET_AUD_RESET _IOW('a',26,int)
#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int)
#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*)
typedef struct _MvpData {
struct audio_output *audio_output;
struct audio_format audio_format;
int fd;
} MvpData;
static unsigned pcmfrequencies[][3] = {
{9, 8000, 32000},
{10, 11025, 44100},
{11, 12000, 48000},
{1, 16000, 32000},
{2, 22050, 44100},
{3, 24000, 48000},
{5, 32000, 32000},
{0, 44100, 44100},
{7, 48000, 48000},
{13, 64000, 32000},
{14, 88200, 44100},
{15, 96000, 48000}
};
static const unsigned numfrequencies =
sizeof(pcmfrequencies) / sizeof(pcmfrequencies[0]);
static int mvp_testDefault(void)
{
int fd;
fd = open("/dev/adec_pcm", O_WRONLY);
if (fd) {
close(fd);
return 0;
}
WARNING("Error opening PCM device \"/dev/adec_pcm\": %s\n",
strerror(errno));
return -1;
}
static void *mvp_initDriver(mpd_unused struct audio_output *audio_output,
mpd_unused const struct audio_format *audio_format,
mpd_unused ConfigParam *param)
{
MvpData *md = xmalloc(sizeof(MvpData));
md->audio_output = audio_output;
md->fd = -1;
return md;
}
static void mvp_finishDriver(void *data)
{
MvpData *md = data;
free(md);
}
static int mvp_setPcmParams(MvpData * md, unsigned long rate, int channels,
int big_endian, unsigned bits)
{
unsigned iloop;
unsigned mix[5];
if (channels == 1)
mix[0] = 1;
else if (channels == 2)
mix[0] = 0;
else
return -1;
/* 0,1=24bit(24) , 2,3=16bit */
if (bits == 16)
mix[1] = 2;
else if (bits == 24)
mix[1] = 0;
else
return -1;
mix[3] = 0; /* stream type? */
if (big_endian == 1)
mix[4] = 1;
else if (big_endian == 0)
mix[4] = 0;
else
return -1;
/*
* if there is an exact match for the frequency, use it.
*/
for (iloop = 0; iloop < numfrequencies; iloop++) {
if (rate == pcmfrequencies[iloop][1]) {
mix[2] = pcmfrequencies[iloop][0];
break;
}
}
if (iloop >= numfrequencies) {
ERROR("Can not find suitable output frequency for %ld\n", rate);
return -1;
}
if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
ERROR("Can not set audio format\n");
return -1;
}
if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) {
ERROR("Can not set audio sync\n");
return -1;
}
if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) {
ERROR("Can not set audio play mode\n");
return -1;
}
return 0;
}
static int mvp_openDevice(void *data, struct audio_format *audioFormat)
{
MvpData *md = data;
long long int stc = 0;
int mix[5] = { 0, 2, 7, 1, 0 };
if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) {
ERROR("Error opening /dev/adec_pcm: %s\n", strerror(errno));
return -1;
}
if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) {
ERROR("Error setting audio source: %s\n", strerror(errno));
return -1;
}
if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) {
ERROR("Error setting audio streamtype: %s\n", strerror(errno));
return -1;
}
if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) {
ERROR("Error setting audio format: %s\n", strerror(errno));
return -1;
}
ioctl(md->fd, MVP_SET_AUD_STC, &stc);
if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) {
ERROR("Error setting audio streamtype: %s\n", strerror(errno));
return -1;
}
#ifdef WORDS_BIGENDIAN
mvp_setPcmParams(md, audioFormat->sample_rate, audioFormat->channels,
0, audioFormat->bits);
#else
mvp_setPcmParams(md, audioFormat->sample_rate, audioFormat->channels,
1, audioFormat->bits);
#endif
md->audio_format = *audioFormat;
return 0;
}
static void mvp_closeDevice(void *data)
{
MvpData *md = data;
if (md->fd >= 0)
close(md->fd);
md->fd = -1;
}
static void mvp_dropBufferedAudio(void *data)
{
MvpData *md = data;
if (md->fd >= 0) {
ioctl(md->fd, MVP_SET_AUD_RESET, 0x11);
close(md->fd);
md->fd = -1;
audio_output_closed(md->audio_output);
}
}
static int mvp_playAudio(void *data, const char *playChunk, size_t size)
{
MvpData *md = data;
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
if (md->fd < 0)
mvp_openDevice(md, &md->audio_format);
while (size > 0) {
ret = write(md->fd, playChunk, size);
if (ret < 0) {
if (errno == EINTR)
continue;
ERROR("closing mvp PCM device due to write error: "
"%s\n", strerror(errno));
mvp_closeDevice(md);
return -1;
}
playChunk += ret;
size -= ret;
}
return 0;
}
const struct audio_output_plugin mvpPlugin = {
.name = "mvp",
.test_default_device = mvp_testDefault,
.init = mvp_initDriver,
.finish = mvp_finishDriver,
.open = mvp_openDevice,
.play = mvp_playAudio,
.cancel = mvp_dropBufferedAudio,
.close = mvp_closeDevice,
};
#else /* HAVE_MVP */
DISABLED_AUDIO_OUTPUT_PLUGIN(mvpPlugin)
#endif /* HAVE_MVP */

85
src/output/null_plugin.c Normal file
View File

@@ -0,0 +1,85 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#include "../timer.h"
#include "../utils.h"
struct null_data {
Timer *timer;
};
static void *null_initDriver(mpd_unused struct audio_output *audioOutput,
mpd_unused const struct audio_format *audio_format,
mpd_unused ConfigParam *param)
{
struct null_data *nd = xmalloc(sizeof(*nd));
nd->timer = NULL;
return nd;
}
static int null_openDevice(void *data,
struct audio_format *audio_format)
{
struct null_data *nd = data;
nd->timer = timer_new(audio_format);
return 0;
}
static void null_closeDevice(void *data)
{
struct null_data *nd = data;
if (nd->timer != NULL) {
timer_free(nd->timer);
nd->timer = NULL;
}
}
static int null_playAudio(void *data,
mpd_unused const char *playChunk, size_t size)
{
struct null_data *nd = data;
Timer *timer = nd->timer;
if (!timer->started)
timer_start(timer);
else
timer_sync(timer);
timer_add(timer, size);
return 0;
}
static void null_dropBufferedAudio(void *data)
{
struct null_data *nd = data;
timer_reset(nd->timer);
}
const struct audio_output_plugin nullPlugin = {
.name = "null",
.init = null_initDriver,
.open = null_openDevice,
.play = null_playAudio,
.cancel = null_dropBufferedAudio,
.close = null_closeDevice,
};

571
src/output/oss_plugin.c Normal file
View File

@@ -0,0 +1,571 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* OSS audio output (c) 2004, 2005, 2006, 2007 by Eric Wong <eric@petta-tech.com>
* and Warren Dukes <warren.dukes@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_OSS
#include "../utils.h"
#include "../log.h"
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#if defined(__OpenBSD__) || defined(__NetBSD__)
# include <soundcard.h>
#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
# include <sys/soundcard.h>
#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
#ifdef WORDS_BIGENDIAN
# define AFMT_S16_MPD AFMT_S16_BE
#else
# define AFMT_S16_MPD AFMT_S16_LE
#endif /* WORDS_BIGENDIAN */
typedef struct _OssData {
int fd;
const char *device;
struct audio_format audio_format;
int bitFormat;
int *supported[3];
int numSupported[3];
int *unsupported[3];
int numUnsupported[3];
} OssData;
enum oss_support {
OSS_SUPPORTED = 1,
OSS_UNSUPPORTED = 0,
OSS_UNKNOWN = -1,
};
enum oss_param {
OSS_RATE = 0,
OSS_CHANNELS = 1,
OSS_BITS = 2,
};
static enum oss_param
getIndexForParam(unsigned param)
{
enum oss_param idx = OSS_RATE;
switch (param) {
case SNDCTL_DSP_SPEED:
idx = OSS_RATE;
break;
case SNDCTL_DSP_CHANNELS:
idx = OSS_CHANNELS;
break;
case SNDCTL_DSP_SAMPLESIZE:
idx = OSS_BITS;
break;
}
return idx;
}
static int findSupportedParam(OssData * od, unsigned param, int val)
{
int i;
enum oss_param idx = getIndexForParam(param);
for (i = 0; i < od->numSupported[idx]; i++) {
if (od->supported[idx][i] == val)
return 1;
}
return 0;
}
static int canConvert(int idx, int val)
{
switch (idx) {
case OSS_BITS:
if (val != 16)
return 0;
break;
case OSS_CHANNELS:
if (val != 2)
return 0;
break;
}
return 1;
}
static int getSupportedParam(OssData * od, unsigned param, int val)
{
int i;
enum oss_param idx = getIndexForParam(param);
int ret = -1;
int least = val;
int diff;
for (i = 0; i < od->numSupported[idx]; i++) {
diff = od->supported[idx][i] - val;
if (diff < 0)
diff = -diff;
if (diff < least) {
if (!canConvert(idx, od->supported[idx][i])) {
continue;
}
least = diff;
ret = od->supported[idx][i];
}
}
return ret;
}
static int findUnsupportedParam(OssData * od, unsigned param, int val)
{
int i;
enum oss_param idx = getIndexForParam(param);
for (i = 0; i < od->numUnsupported[idx]; i++) {
if (od->unsupported[idx][i] == val)
return 1;
}
return 0;
}
static void addSupportedParam(OssData * od, unsigned param, int val)
{
enum oss_param idx = getIndexForParam(param);
od->numSupported[idx]++;
od->supported[idx] = xrealloc(od->supported[idx],
od->numSupported[idx] * sizeof(int));
od->supported[idx][od->numSupported[idx] - 1] = val;
}
static void addUnsupportedParam(OssData * od, unsigned param, int val)
{
enum oss_param idx = getIndexForParam(param);
od->numUnsupported[idx]++;
od->unsupported[idx] = xrealloc(od->unsupported[idx],
od->numUnsupported[idx] *
sizeof(int));
od->unsupported[idx][od->numUnsupported[idx] - 1] = val;
}
static void removeSupportedParam(OssData * od, unsigned param, int val)
{
int i;
int j = 0;
enum oss_param idx = getIndexForParam(param);
for (i = 0; i < od->numSupported[idx] - 1; i++) {
if (od->supported[idx][i] == val)
j = 1;
od->supported[idx][i] = od->supported[idx][i + j];
}
od->numSupported[idx]--;
od->supported[idx] = xrealloc(od->supported[idx],
od->numSupported[idx] * sizeof(int));
}
static void removeUnsupportedParam(OssData * od, unsigned param, int val)
{
int i;
int j = 0;
enum oss_param idx = getIndexForParam(param);
for (i = 0; i < od->numUnsupported[idx] - 1; i++) {
if (od->unsupported[idx][i] == val)
j = 1;
od->unsupported[idx][i] = od->unsupported[idx][i + j];
}
od->numUnsupported[idx]--;
od->unsupported[idx] = xrealloc(od->unsupported[idx],
od->numUnsupported[idx] *
sizeof(int));
}
static enum oss_support
isSupportedParam(OssData * od, unsigned param, int val)
{
if (findSupportedParam(od, param, val))
return OSS_SUPPORTED;
if (findUnsupportedParam(od, param, val))
return OSS_UNSUPPORTED;
return OSS_UNKNOWN;
}
static void supportParam(OssData * od, unsigned param, int val)
{
enum oss_support supported = isSupportedParam(od, param, val);
if (supported == OSS_SUPPORTED)
return;
if (supported == OSS_UNSUPPORTED) {
removeUnsupportedParam(od, param, val);
}
addSupportedParam(od, param, val);
}
static void unsupportParam(OssData * od, unsigned param, int val)
{
enum oss_support supported = isSupportedParam(od, param, val);
if (supported == OSS_UNSUPPORTED)
return;
if (supported == OSS_SUPPORTED) {
removeSupportedParam(od, param, val);
}
addUnsupportedParam(od, param, val);
}
static OssData *newOssData(void)
{
OssData *ret = xmalloc(sizeof(OssData));
ret->device = NULL;
ret->fd = -1;
ret->supported[OSS_RATE] = NULL;
ret->supported[OSS_CHANNELS] = NULL;
ret->supported[OSS_BITS] = NULL;
ret->unsupported[OSS_RATE] = NULL;
ret->unsupported[OSS_CHANNELS] = NULL;
ret->unsupported[OSS_BITS] = NULL;
ret->numSupported[OSS_RATE] = 0;
ret->numSupported[OSS_CHANNELS] = 0;
ret->numSupported[OSS_BITS] = 0;
ret->numUnsupported[OSS_RATE] = 0;
ret->numUnsupported[OSS_CHANNELS] = 0;
ret->numUnsupported[OSS_BITS] = 0;
supportParam(ret, SNDCTL_DSP_SPEED, 48000);
supportParam(ret, SNDCTL_DSP_SPEED, 44100);
supportParam(ret, SNDCTL_DSP_CHANNELS, 2);
supportParam(ret, SNDCTL_DSP_SAMPLESIZE, 16);
return ret;
}
static void freeOssData(OssData * od)
{
if (od->supported[OSS_RATE])
free(od->supported[OSS_RATE]);
if (od->supported[OSS_CHANNELS])
free(od->supported[OSS_CHANNELS]);
if (od->supported[OSS_BITS])
free(od->supported[OSS_BITS]);
if (od->unsupported[OSS_RATE])
free(od->unsupported[OSS_RATE]);
if (od->unsupported[OSS_CHANNELS])
free(od->unsupported[OSS_CHANNELS]);
if (od->unsupported[OSS_BITS])
free(od->unsupported[OSS_BITS]);
free(od);
}
#define OSS_STAT_NO_ERROR 0
#define OSS_STAT_NOT_CHAR_DEV -1
#define OSS_STAT_NO_PERMS -2
#define OSS_STAT_DOESN_T_EXIST -3
#define OSS_STAT_OTHER -4
static int oss_statDevice(const char *device, int *stErrno)
{
struct stat st;
if (0 == stat(device, &st)) {
if (!S_ISCHR(st.st_mode)) {
return OSS_STAT_NOT_CHAR_DEV;
}
} else {
*stErrno = errno;
switch (errno) {
case ENOENT:
case ENOTDIR:
return OSS_STAT_DOESN_T_EXIST;
case EACCES:
return OSS_STAT_NO_PERMS;
default:
return OSS_STAT_OTHER;
}
}
return 0;
}
static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" };
static int oss_testDefault(void)
{
int fd, i;
for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
if ((fd = open(default_devices[i], O_WRONLY)) >= 0) {
xclose(fd);
return 0;
}
WARNING("Error opening OSS device \"%s\": %s\n",
default_devices[i], strerror(errno));
}
return -1;
}
static void *oss_open_default(ConfigParam *param)
{
int i;
int err[ARRAY_SIZE(default_devices)];
int ret[ARRAY_SIZE(default_devices)];
for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
ret[i] = oss_statDevice(default_devices[i], &err[i]);
if (ret[i] == 0) {
OssData *od = newOssData();
od->device = default_devices[i];
return od;
}
}
if (param)
ERROR("error trying to open specified OSS device"
" at line %i\n", param->line);
else
ERROR("error trying to open default OSS device\n");
for (i = ARRAY_SIZE(default_devices); --i >= 0; ) {
const char *dev = default_devices[i];
switch(ret[i]) {
case OSS_STAT_DOESN_T_EXIST:
ERROR("%s not found\n", dev);
break;
case OSS_STAT_NOT_CHAR_DEV:
ERROR("%s is not a character device\n", dev);
break;
case OSS_STAT_NO_PERMS:
ERROR("%s: permission denied\n", dev);
break;
default:
ERROR("Error accessing %s: %s\n", dev, strerror(err[i]));
}
}
exit(EXIT_FAILURE);
return NULL; /* some compilers can be dumb... */
}
static void *oss_initDriver(mpd_unused struct audio_output *audioOutput,
mpd_unused const struct audio_format *audio_format,
ConfigParam * param)
{
if (param) {
BlockParam *bp = getBlockParam(param, "device");
if (bp) {
OssData *od = newOssData();
od->device = bp->value;
return od;
}
}
return oss_open_default(param);
}
static void oss_finishDriver(void *data)
{
OssData *od = data;
freeOssData(od);
}
static int setParam(OssData * od, unsigned param, int *value)
{
int val = *value;
int copy;
enum oss_support supported = isSupportedParam(od, param, val);
do {
if (supported == OSS_UNSUPPORTED) {
val = getSupportedParam(od, param, val);
if (copy < 0)
return -1;
}
copy = val;
if (ioctl(od->fd, param, &copy)) {
unsupportParam(od, param, val);
supported = OSS_UNSUPPORTED;
} else {
if (supported == OSS_UNKNOWN) {
supportParam(od, param, val);
supported = OSS_SUPPORTED;
}
val = copy;
}
} while (supported == OSS_UNSUPPORTED);
*value = val;
return 0;
}
static void oss_close(OssData * od)
{
if (od->fd >= 0)
while (close(od->fd) && errno == EINTR) ;
od->fd = -1;
}
static int oss_open(OssData *od)
{
int tmp;
if ((od->fd = open(od->device, O_WRONLY)) < 0) {
ERROR("Error opening OSS device \"%s\": %s\n", od->device,
strerror(errno));
goto fail;
}
tmp = od->audio_format.channels;
if (setParam(od, SNDCTL_DSP_CHANNELS, &tmp)) {
ERROR("OSS device \"%s\" does not support %u channels: %s\n",
od->device, od->audio_format.channels, strerror(errno));
goto fail;
}
od->audio_format.channels = tmp;
tmp = od->audio_format.sample_rate;
if (setParam(od, SNDCTL_DSP_SPEED, &tmp)) {
ERROR("OSS device \"%s\" does not support %u Hz audio: %s\n",
od->device, od->audio_format.sample_rate,
strerror(errno));
goto fail;
}
od->audio_format.sample_rate = tmp;
switch (od->audio_format.bits) {
case 8:
tmp = AFMT_S8;
break;
case 16:
tmp = AFMT_S16_MPD;
}
if (setParam(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) {
ERROR("OSS device \"%s\" does not support %u bit audio: %s\n",
od->device, tmp, strerror(errno));
goto fail;
}
return 0;
fail:
oss_close(od);
return -1;
}
static int oss_openDevice(void *data,
struct audio_format *audioFormat)
{
int ret;
OssData *od = data;
od->audio_format = *audioFormat;
if ((ret = oss_open(od)) < 0)
return ret;
*audioFormat = od->audio_format;
DEBUG("oss device \"%s\" will be playing %u bit %u channel audio at "
"%u Hz\n", od->device,
od->audio_format.bits, od->audio_format.channels,
od->audio_format.sample_rate);
return ret;
}
static void oss_closeDevice(void *data)
{
OssData *od = data;
oss_close(od);
}
static void oss_dropBufferedAudio(void *data)
{
OssData *od = data;
if (od->fd >= 0) {
ioctl(od->fd, SNDCTL_DSP_RESET, 0);
oss_close(od);
}
}
static int oss_playAudio(void *data,
const char *playChunk, size_t size)
{
OssData *od = data;
ssize_t ret;
/* reopen the device since it was closed by dropBufferedAudio */
if (od->fd < 0 && oss_open(od) < 0)
return -1;
while (size > 0) {
ret = write(od->fd, playChunk, size);
if (ret < 0) {
if (errno == EINTR)
continue;
ERROR("closing oss device \"%s\" due to write error: "
"%s\n", od->device, strerror(errno));
oss_closeDevice(od);
return -1;
}
playChunk += ret;
size -= ret;
}
return 0;
}
const struct audio_output_plugin ossPlugin = {
.name = "oss",
.test_default_device = oss_testDefault,
.init = oss_initDriver,
.finish = oss_finishDriver,
.open = oss_openDevice,
.play = oss_playAudio,
.cancel = oss_dropBufferedAudio,
.close = oss_closeDevice,
};
#else /* HAVE OSS */
DISABLED_AUDIO_OUTPUT_PLUGIN(ossPlugin)
#endif /* HAVE_OSS */

368
src/output/osx_plugin.c Normal file
View File

@@ -0,0 +1,368 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_OSX
#include <AudioUnit/AudioUnit.h>
#include "../utils.h"
#include "../log.h"
typedef struct _OsxData {
AudioUnit au;
pthread_mutex_t mutex;
pthread_cond_t condition;
char *buffer;
size_t bufferSize;
size_t pos;
size_t len;
int started;
} OsxData;
static OsxData *newOsxData()
{
OsxData *ret = xmalloc(sizeof(OsxData));
pthread_mutex_init(&ret->mutex, NULL);
pthread_cond_init(&ret->condition, NULL);
ret->pos = 0;
ret->len = 0;
ret->started = 0;
ret->buffer = NULL;
ret->bufferSize = 0;
return ret;
}
static int osx_testDefault()
{
/*AudioUnit au;
ComponentDescription desc;
Component comp;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_Output;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
comp = FindNextComponent(NULL, &desc);
if(!comp) {
ERROR("Unable to open default OS X defice\n");
return -1;
}
if(OpenAComponent(comp, &au) != noErr) {
ERROR("Unable to open default OS X defice\n");
return -1;
}
CloseComponent(au); */
return 0;
}
static int osx_initDriver(struct audio_output *audioOutput,
mpd_unused const struct audio_format *audio_format,
ConfigParam * param)
{
OsxData *od = newOsxData();
audioOutput->data = od;
return 0;
}
static void freeOsxData(OsxData * od)
{
if (od->buffer)
free(od->buffer);
pthread_mutex_destroy(&od->mutex);
pthread_cond_destroy(&od->condition);
free(od);
}
static void osx_finishDriver(struct audio_output *audioOutput)
{
OsxData *od = (OsxData *) audioOutput->data;
freeOsxData(od);
}
static void osx_dropBufferedAudio(struct audio_output *audioOutput)
{
OsxData *od = (OsxData *) audioOutput->data;
pthread_mutex_lock(&od->mutex);
od->len = 0;
pthread_mutex_unlock(&od->mutex);
}
static void osx_closeDevice(struct audio_output *audioOutput)
{
OsxData *od = (OsxData *) audioOutput->data;
pthread_mutex_lock(&od->mutex);
while (od->len) {
pthread_cond_wait(&od->condition, &od->mutex);
}
pthread_mutex_unlock(&od->mutex);
if (od->started) {
AudioOutputUnitStop(od->au);
od->started = 0;
}
CloseComponent(od->au);
AudioUnitUninitialize(od->au);
}
static OSStatus osx_render(void *vdata,
AudioUnitRenderActionFlags * ioActionFlags,
const AudioTimeStamp * inTimeStamp,
UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList * bufferList)
{
OsxData *od = (OsxData *) vdata;
AudioBuffer *buffer = &bufferList->mBuffers[0];
size_t bufferSize = buffer->mDataByteSize;
size_t bytesToCopy;
int curpos = 0;
/*DEBUG("osx_render: enter : %i\n", (int)bufferList->mNumberBuffers);
DEBUG("osx_render: ioActionFlags: %p\n", ioActionFlags);
if(ioActionFlags) {
if(*ioActionFlags & kAudioUnitRenderAction_PreRender) {
DEBUG("prerender\n");
}
if(*ioActionFlags & kAudioUnitRenderAction_PostRender) {
DEBUG("post render\n");
}
if(*ioActionFlags & kAudioUnitRenderAction_OutputIsSilence) {
DEBUG("post render\n");
}
if(*ioActionFlags & kAudioOfflineUnitRenderAction_Preflight) {
DEBUG("prefilight\n");
}
if(*ioActionFlags & kAudioOfflineUnitRenderAction_Render) {
DEBUG("render\n");
}
if(*ioActionFlags & kAudioOfflineUnitRenderAction_Complete) {
DEBUG("complete\n");
}
} */
/* while(bufferSize) {
DEBUG("osx_render: lock\n"); */
pthread_mutex_lock(&od->mutex);
/*
DEBUG("%i:%i\n", bufferSize, od->len);
while(od->go && od->len < bufferSize &&
od->len < od->bufferSize)
{
DEBUG("osx_render: wait\n");
pthread_cond_wait(&od->condition, &od->mutex);
}
*/
bytesToCopy = od->len < bufferSize ? od->len : bufferSize;
bufferSize = bytesToCopy;
od->len -= bytesToCopy;
if (od->pos + bytesToCopy > od->bufferSize) {
size_t bytes = od->bufferSize - od->pos;
memcpy(buffer->mData + curpos, od->buffer + od->pos, bytes);
od->pos = 0;
curpos += bytes;
bytesToCopy -= bytes;
}
memcpy(buffer->mData + curpos, od->buffer + od->pos, bytesToCopy);
od->pos += bytesToCopy;
curpos += bytesToCopy;
if (od->pos >= od->bufferSize)
od->pos = 0;
/* DEBUG("osx_render: unlock\n"); */
pthread_mutex_unlock(&od->mutex);
pthread_cond_signal(&od->condition);
/* } */
buffer->mDataByteSize = bufferSize;
if (!bufferSize) {
my_usleep(1000);
}
/* DEBUG("osx_render: leave\n"); */
return 0;
}
static int osx_openDevice(struct audio_output *audioOutput,
struct audio_format *audioFormat)
{
OsxData *od = (OsxData *) audioOutput->data;
ComponentDescription desc;
Component comp;
AURenderCallbackStruct callback;
AudioStreamBasicDescription streamDesc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
comp = FindNextComponent(NULL, &desc);
if (comp == 0) {
ERROR("Error finding OS X component\n");
return -1;
}
if (OpenAComponent(comp, &od->au) != noErr) {
ERROR("Unable to open OS X component\n");
return -1;
}
if (AudioUnitInitialize(od->au) != 0) {
CloseComponent(od->au);
ERROR("Unable to initialize OS X audio unit\n");
return -1;
}
callback.inputProc = osx_render;
callback.inputProcRefCon = od;
if (AudioUnitSetProperty(od->au, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0,
&callback, sizeof(callback)) != 0) {
AudioUnitUninitialize(od->au);
CloseComponent(od->au);
ERROR("unable to set callback for OS X audio unit\n");
return -1;
}
streamDesc.mSampleRate = audioFormat->sample_rate;
streamDesc.mFormatID = kAudioFormatLinearPCM;
streamDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
#ifdef WORDS_BIGENDIAN
streamDesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
#endif
streamDesc.mBytesPerPacket = audio_format_frame_size(audioFormat);
streamDesc.mFramesPerPacket = 1;
streamDesc.mBytesPerFrame = streamDesc.mBytesPerPacket;
streamDesc.mChannelsPerFrame = audioFormat->channels;
streamDesc.mBitsPerChannel = audioFormat->bits;
if (AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
&streamDesc, sizeof(streamDesc)) != 0) {
AudioUnitUninitialize(od->au);
CloseComponent(od->au);
ERROR("Unable to set format on OS X device\n");
return -1;
}
/* create a buffer of 1s */
od->bufferSize = (audioFormat->sample_rate) *
audio_format_frame_size(audioFormat);
od->buffer = xrealloc(od->buffer, od->bufferSize);
od->pos = 0;
od->len = 0;
return 0;
}
static int osx_play(struct audio_output *audioOutput,
const char *playChunk, size_t size)
{
OsxData *od = (OsxData *) audioOutput->data;
size_t bytesToCopy;
size_t curpos;
/* DEBUG("osx_play: enter\n"); */
if (!od->started) {
int err;
od->started = 1;
err = AudioOutputUnitStart(od->au);
if (err) {
ERROR("unable to start audio output: %i\n", err);
return -1;
}
}
pthread_mutex_lock(&od->mutex);
while (size) {
/* DEBUG("osx_play: lock\n"); */
curpos = od->pos + od->len;
if (curpos >= od->bufferSize)
curpos -= od->bufferSize;
bytesToCopy = od->bufferSize < size ? od->bufferSize : size;
while (od->len > od->bufferSize - bytesToCopy) {
/* DEBUG("osx_play: wait\n"); */
pthread_cond_wait(&od->condition, &od->mutex);
}
bytesToCopy = od->bufferSize - od->len;
bytesToCopy = bytesToCopy < size ? bytesToCopy : size;
size -= bytesToCopy;
od->len += bytesToCopy;
if (curpos + bytesToCopy > od->bufferSize) {
size_t bytes = od->bufferSize - curpos;
memcpy(od->buffer + curpos, playChunk, bytes);
curpos = 0;
playChunk += bytes;
bytesToCopy -= bytes;
}
memcpy(od->buffer + curpos, playChunk, bytesToCopy);
curpos += bytesToCopy;
playChunk += bytesToCopy;
}
/* DEBUG("osx_play: unlock\n"); */
pthread_mutex_unlock(&od->mutex);
/* DEBUG("osx_play: leave\n"); */
return 0;
}
const struct audio_output_plugin osxPlugin = {
.name = "osx",
.test_default_device = osx_testDefault,
.init = osx_initDriver,
.finish = osx_finishDriver,
.open = osx_openDevice,
.play = osx_play,
.cancel = osx_dropBufferedAudio,
.close = osx_closeDevice,
};
#else
DISABLED_AUDIO_OUTPUT_PLUGIN(osxPlugin)
#endif

218
src/output/pulse_plugin.c Normal file
View File

@@ -0,0 +1,218 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../output_api.h"
#ifdef HAVE_PULSE
#include "../utils.h"
#include "../log.h"
#include <pulse/simple.h>
#include <pulse/error.h>
#define MPD_PULSE_NAME "mpd"
#define CONN_ATTEMPT_INTERVAL 60
typedef struct _PulseData {
struct audio_output *ao;
pa_simple *s;
char *server;
char *sink;
int connAttempts;
time_t lastAttempt;
} PulseData;
static PulseData *newPulseData(void)
{
PulseData *ret;
ret = xmalloc(sizeof(PulseData));
ret->s = NULL;
ret->server = NULL;
ret->sink = NULL;
ret->connAttempts = 0;
ret->lastAttempt = 0;
return ret;
}
static void freePulseData(PulseData * pd)
{
if (pd->server)
free(pd->server);
if (pd->sink)
free(pd->sink);
free(pd);
}
static void *pulse_initDriver(struct audio_output *ao,
mpd_unused const struct audio_format *audio_format,
ConfigParam * param)
{
BlockParam *server = NULL;
BlockParam *sink = NULL;
PulseData *pd;
if (param) {
server = getBlockParam(param, "server");
sink = getBlockParam(param, "sink");
}
pd = newPulseData();
pd->ao = ao;
pd->server = server ? xstrdup(server->value) : NULL;
pd->sink = sink ? xstrdup(sink->value) : NULL;
return pd;
}
static void pulse_finishDriver(void *data)
{
PulseData *pd = data;
freePulseData(pd);
}
static int pulse_testDefault(void)
{
pa_simple *s;
pa_sample_spec ss;
int error;
ss.format = PA_SAMPLE_S16NE;
ss.rate = 44100;
ss.channels = 2;
s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL,
MPD_PULSE_NAME, &ss, NULL, NULL, &error);
if (!s) {
WARNING("Cannot connect to default PulseAudio server: %s\n",
pa_strerror(error));
return -1;
}
pa_simple_free(s);
return 0;
}
static int pulse_openDevice(void *data,
struct audio_format *audioFormat)
{
PulseData *pd = data;
pa_sample_spec ss;
time_t t;
int error;
t = time(NULL);
if (pd->connAttempts != 0 &&
(t - pd->lastAttempt) < CONN_ATTEMPT_INTERVAL)
return -1;
pd->connAttempts++;
pd->lastAttempt = t;
/* MPD doesn't support the other pulseaudio sample formats, so
we just force MPD to send us everything as 16 bit */
audioFormat->bits = 16;
ss.format = PA_SAMPLE_S16NE;
ss.rate = audioFormat->sample_rate;
ss.channels = audioFormat->channels;
pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK,
pd->sink, audio_output_get_name(pd->ao),
&ss, NULL, NULL,
&error);
if (!pd->s) {
ERROR("Cannot connect to server in PulseAudio output "
"\"%s\" (attempt %i): %s\n",
audio_output_get_name(pd->ao),
pd->connAttempts, pa_strerror(error));
return -1;
}
pd->connAttempts = 0;
DEBUG("PulseAudio output \"%s\" connected and playing %i bit, %i "
"channel audio at %i Hz\n",
audio_output_get_name(pd->ao),
audioFormat->bits,
audioFormat->channels, audioFormat->sample_rate);
return 0;
}
static void pulse_dropBufferedAudio(void *data)
{
PulseData *pd = data;
int error;
if (pa_simple_flush(pd->s, &error) < 0)
WARNING("Flush failed in PulseAudio output \"%s\": %s\n",
audio_output_get_name(pd->ao),
pa_strerror(error));
}
static void pulse_closeDevice(void *data)
{
PulseData *pd = data;
if (pd->s) {
pa_simple_drain(pd->s, NULL);
pa_simple_free(pd->s);
}
}
static int pulse_playAudio(void *data,
const char *playChunk, size_t size)
{
PulseData *pd = data;
int error;
if (pa_simple_write(pd->s, playChunk, size, &error) < 0) {
ERROR("PulseAudio output \"%s\" disconnecting due to write "
"error: %s\n",
audio_output_get_name(pd->ao),
pa_strerror(error));
pulse_closeDevice(pd);
return -1;
}
return 0;
}
const struct audio_output_plugin pulsePlugin = {
.name = "pulse",
.test_default_device = pulse_testDefault,
.init = pulse_initDriver,
.finish = pulse_finishDriver,
.open = pulse_openDevice,
.play = pulse_playAudio,
.cancel = pulse_dropBufferedAudio,
.close = pulse_closeDevice,
};
#else /* HAVE_PULSE */
DISABLED_AUDIO_OUTPUT_PLUGIN(pulsePlugin)
#endif /* HAVE_PULSE */

188
src/output/shout_mp3.c Normal file
View File

@@ -0,0 +1,188 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "shout_plugin.h"
#ifdef HAVE_SHOUT_MP3
#include "../utils.h"
#include <lame/lame.h>
struct lame_data {
lame_global_flags *gfp;
};
static int shout_mp3_encoder_init(struct shout_data *sd)
{
struct lame_data *ld;
if (NULL == (ld = xmalloc(sizeof(*ld))))
FATAL("error initializing lame encoder data\n");
sd->encoder_data = ld;
return 0;
}
static int shout_mp3_encoder_clear_encoder(struct shout_data *sd)
{
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
struct shout_buffer *buf = &sd->buf;
int ret;
if ((ret = lame_encode_flush(ld->gfp, buf->data + buf->len,
buf->len)) < 0)
ERROR("error flushing lame buffers\n");
return (ret > 0);
}
static void shout_mp3_encoder_finish(struct shout_data *sd)
{
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
lame_close(ld->gfp);
ld->gfp = NULL;
}
static int shout_mp3_encoder_init_encoder(struct shout_data *sd)
{
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
if (NULL == (ld->gfp = lame_init())) {
ERROR("error initializing lame encoder for shout\n");
return -1;
}
if (sd->quality >= -1.0) {
if (0 != lame_set_VBR(ld->gfp, vbr_rh)) {
ERROR("error setting lame VBR mode\n");
return -1;
}
if (0 != lame_set_VBR_q(ld->gfp, sd->quality)) {
ERROR("error setting lame VBR quality\n");
return -1;
}
} else {
if (0 != lame_set_brate(ld->gfp, sd->bitrate)) {
ERROR("error setting lame bitrate\n");
return -1;
}
}
if (0 != lame_set_num_channels(ld->gfp,
sd->audio_format.channels)) {
ERROR("error setting lame num channels\n");
return -1;
}
if (0 != lame_set_in_samplerate(ld->gfp,
sd->audio_format.sample_rate)) {
ERROR("error setting lame sample rate\n");
return -1;
}
if (0 > lame_init_params(ld->gfp))
FATAL("error initializing lame params\n");
return 0;
}
static int shout_mp3_encoder_send_metadata(struct shout_data *sd,
char * song, size_t size)
{
char artist[size];
char title[size];
int i;
struct tag *tag = sd->tag;
strncpy(artist, "", size);
strncpy(title, "", size);
for (i = 0; i < tag->numOfItems; i++) {
switch (tag->items[i]->type) {
case TAG_ITEM_ARTIST:
strncpy(artist, tag->items[i]->value, size);
break;
case TAG_ITEM_TITLE:
strncpy(title, tag->items[i]->value, size);
break;
default:
break;
}
}
snprintf(song, size, "%s - %s", title, artist);
return 1;
}
static int shout_mp3_encoder_encode(struct shout_data *sd,
const char * chunk, size_t len)
{
unsigned int i;
int j;
float (*lamebuf)[2];
struct shout_buffer *buf = &(sd->buf);
unsigned int samples;
int bytes = audio_format_sample_size(&sd->audio_format);
struct lame_data *ld = (struct lame_data *)sd->encoder_data;
int bytes_out;
samples = len / (bytes * sd->audio_format.channels);
/* rough estimate, from lame.h */
lamebuf = xmalloc(sizeof(float) * (1.25 * samples + 7200));
/* this is for only 16-bit audio */
for (i = 0; i < samples; i++) {
for (j = 0; j < sd->audio_format.channels; j++) {
lamebuf[j][i] = *((const int16_t *) chunk);
chunk += bytes;
}
}
bytes_out = lame_encode_buffer_float(ld->gfp, lamebuf[0], lamebuf[1],
samples, buf->data,
sizeof(buf->data) - buf->len);
free(lamebuf);
if (0 > bytes_out) {
ERROR("error encoding lame buffer for shout\n");
lame_close(ld->gfp);
ld->gfp = NULL;
return -1;
} else
buf->len = bytes_out; /* signed to unsigned conversion */
return 0;
}
const struct shout_encoder_plugin shout_mp3_encoder = {
"mp3",
SHOUT_FORMAT_MP3,
shout_mp3_encoder_clear_encoder,
shout_mp3_encoder_encode,
shout_mp3_encoder_finish,
shout_mp3_encoder_init,
shout_mp3_encoder_init_encoder,
shout_mp3_encoder_send_metadata,
};
#endif

306
src/output/shout_ogg.c Normal file
View File

@@ -0,0 +1,306 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "shout_plugin.h"
#ifdef HAVE_SHOUT_OGG
#include "../utils.h"
#include <vorbis/vorbisenc.h>
struct ogg_vorbis_data {
ogg_stream_state os;
ogg_page og;
ogg_packet op;
ogg_packet header_main;
ogg_packet header_comments;
ogg_packet header_codebooks;
vorbis_dsp_state vd;
vorbis_block vb;
vorbis_info vi;
vorbis_comment vc;
};
static void add_tag(struct ogg_vorbis_data *od, const char *name, char *value)
{
if (value) {
union {
const char *in;
char *out;
} u = { .in = name };
vorbis_comment_add_tag(&od->vc, u.out, value);
}
}
static void copy_tag_to_vorbis_comment(struct shout_data *sd)
{
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
if (sd->tag) {
int i;
for (i = 0; i < sd->tag->numOfItems; i++) {
switch (sd->tag->items[i]->type) {
case TAG_ITEM_ARTIST:
add_tag(od, "ARTIST", sd->tag->items[i]->value);
break;
case TAG_ITEM_ALBUM:
add_tag(od, "ALBUM", sd->tag->items[i]->value);
break;
case TAG_ITEM_TITLE:
add_tag(od, "TITLE", sd->tag->items[i]->value);
break;
default:
break;
}
}
}
}
static int copy_ogg_buffer_to_shout_buffer(ogg_page *og,
struct shout_buffer *buf)
{
if (sizeof(buf->data) - buf->len >= (size_t)og->header_len) {
memcpy(buf->data + buf->len,
og->header, og->header_len);
buf->len += og->header_len;
} else {
ERROR("%s: not enough buffer space!\n", __func__);
return -1;
}
if (sizeof(buf->data) - buf->len >= (size_t)og->body_len) {
memcpy(buf->data + buf->len,
og->body, og->body_len);
buf->len += og->body_len;
} else {
ERROR("%s: not enough buffer space!\n", __func__);
return -1;
}
return 0;
}
static int flush_ogg_buffer(struct shout_data *sd)
{
struct shout_buffer *buf = &sd->buf;
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
int ret = 0;
if (ogg_stream_flush(&od->os, &od->og))
ret = copy_ogg_buffer_to_shout_buffer(&od->og, buf);
return ret;
}
static int send_ogg_vorbis_header(struct shout_data *sd)
{
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
vorbis_analysis_headerout(&od->vd, &od->vc,
&od->header_main,
&od->header_comments,
&od->header_codebooks);
ogg_stream_packetin(&od->os, &od->header_main);
ogg_stream_packetin(&od->os, &od->header_comments);
ogg_stream_packetin(&od->os, &od->header_codebooks);
return flush_ogg_buffer(sd);
}
static void finish_encoder(struct ogg_vorbis_data *od)
{
vorbis_analysis_wrote(&od->vd, 0);
while (vorbis_analysis_blockout(&od->vd, &od->vb) == 1) {
vorbis_analysis(&od->vb, NULL);
vorbis_bitrate_addblock(&od->vb);
while (vorbis_bitrate_flushpacket(&od->vd, &od->op)) {
ogg_stream_packetin(&od->os, &od->op);
}
}
}
static int shout_ogg_encoder_clear_encoder(struct shout_data *sd)
{
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
int ret;
finish_encoder(od);
if ((ret = ogg_stream_pageout(&od->os, &od->og)))
copy_ogg_buffer_to_shout_buffer(&od->og, &sd->buf);
vorbis_comment_clear(&od->vc);
ogg_stream_clear(&od->os);
vorbis_block_clear(&od->vb);
vorbis_dsp_clear(&od->vd);
vorbis_info_clear(&od->vi);
return ret;
}
static void shout_ogg_encoder_finish(struct shout_data *sd)
{
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
if (od) {
free(od);
sd->encoder_data = NULL;
}
}
static int shout_ogg_encoder_init(struct shout_data *sd)
{
struct ogg_vorbis_data *od;
if (NULL == (od = xmalloc(sizeof(*od))))
FATAL("error initializing ogg vorbis encoder data\n");
sd->encoder_data = od;
return 0;
}
static int reinit_encoder(struct shout_data *sd)
{
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
vorbis_info_init(&od->vi);
if (sd->quality >= -1.0) {
if (0 != vorbis_encode_init_vbr(&od->vi,
sd->audio_format.channels,
sd->audio_format.sample_rate,
sd->quality * 0.1)) {
ERROR("error initializing vorbis vbr\n");
vorbis_info_clear(&od->vi);
return -1;
}
} else {
if (0 != vorbis_encode_init(&od->vi,
sd->audio_format.channels,
sd->audio_format.sample_rate, -1.0,
sd->bitrate * 1000, -1.0)) {
ERROR("error initializing vorbis encoder\n");
vorbis_info_clear(&od->vi);
return -1;
}
}
vorbis_analysis_init(&od->vd, &od->vi);
vorbis_block_init(&od->vd, &od->vb);
ogg_stream_init(&od->os, rand());
vorbis_comment_init(&od->vc);
return 0;
}
static int shout_ogg_encoder_init_encoder(struct shout_data *sd)
{
if (reinit_encoder(sd))
return -1;
if (send_ogg_vorbis_header(sd)) {
ERROR("error sending ogg vorbis header for shout\n");
return -1;
}
return 0;
}
static int shout_ogg_encoder_send_metadata(struct shout_data *sd,
mpd_unused char * song,
mpd_unused size_t size)
{
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
shout_ogg_encoder_clear_encoder(sd);
if (reinit_encoder(sd))
return 0;
copy_tag_to_vorbis_comment(sd);
vorbis_analysis_headerout(&od->vd, &od->vc,
&od->header_main,
&od->header_comments,
&od->header_codebooks);
ogg_stream_packetin(&od->os, &od->header_main);
ogg_stream_packetin(&od->os, &od->header_comments);
ogg_stream_packetin(&od->os, &od->header_codebooks);
flush_ogg_buffer(sd);
return 0;
}
static int shout_ogg_encoder_encode(struct shout_data *sd,
const char *chunk, size_t size)
{
struct shout_buffer *buf = &sd->buf;
unsigned int i;
int j;
float **vorbbuf;
unsigned int samples;
int bytes = audio_format_sample_size(&sd->audio_format);
struct ogg_vorbis_data *od = (struct ogg_vorbis_data *)sd->encoder_data;
samples = size / (bytes * sd->audio_format.channels);
vorbbuf = vorbis_analysis_buffer(&od->vd, samples);
/* this is for only 16-bit audio */
for (i = 0; i < samples; i++) {
for (j = 0; j < sd->audio_format.channels; j++) {
vorbbuf[j][i] = (*((const int16_t *) chunk)) / 32768.0;
chunk += bytes;
}
}
vorbis_analysis_wrote(&od->vd, samples);
while (1 == vorbis_analysis_blockout(&od->vd, &od->vb)) {
vorbis_analysis(&od->vb, NULL);
vorbis_bitrate_addblock(&od->vb);
while (vorbis_bitrate_flushpacket(&od->vd, &od->op)) {
ogg_stream_packetin(&od->os, &od->op);
}
}
if (ogg_stream_pageout(&od->os, &od->og))
copy_ogg_buffer_to_shout_buffer(&od->og, buf);
return 0;
}
const struct shout_encoder_plugin shout_ogg_encoder = {
"ogg",
SHOUT_FORMAT_VORBIS,
shout_ogg_encoder_clear_encoder,
shout_ogg_encoder_encode,
shout_ogg_encoder_finish,
shout_ogg_encoder_init,
shout_ogg_encoder_init_encoder,
shout_ogg_encoder_send_metadata,
};
#endif

596
src/output/shout_plugin.c Normal file
View File

@@ -0,0 +1,596 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "shout_plugin.h"
#ifdef HAVE_SHOUT
#include "../utils.h"
#include <assert.h>
#define CONN_ATTEMPT_INTERVAL 60
#define DEFAULT_CONN_TIMEOUT 2
static int shout_init_count;
static const struct shout_encoder_plugin *const shout_encoder_plugins[] = {
#ifdef HAVE_SHOUT_MP3
&shout_mp3_encoder,
#endif
#ifdef HAVE_SHOUT_OGG
&shout_ogg_encoder,
#endif
NULL
};
static const struct shout_encoder_plugin *
shout_encoder_plugin_get(const char *name)
{
unsigned i;
for (i = 0; shout_encoder_plugins[i] != NULL; ++i)
if (strcmp(shout_encoder_plugins[i]->name, name) == 0)
return shout_encoder_plugins[i];
return NULL;
}
static struct shout_data *new_shout_data(void)
{
struct shout_data *ret = xmalloc(sizeof(*ret));
ret->shout_conn = shout_new();
ret->shout_meta = shout_metadata_new();
ret->opened = 0;
ret->tag = NULL;
ret->tag_to_send = 0;
ret->bitrate = -1;
ret->quality = -2.0;
ret->timeout = DEFAULT_CONN_TIMEOUT;
ret->conn_attempts = 0;
ret->last_attempt = 0;
ret->timer = NULL;
ret->buf.len = 0;
return ret;
}
static void free_shout_data(struct shout_data *sd)
{
if (sd->shout_meta)
shout_metadata_free(sd->shout_meta);
if (sd->shout_conn)
shout_free(sd->shout_conn);
if (sd->tag)
tag_free(sd->tag);
if (sd->timer)
timer_free(sd->timer);
free(sd);
}
#define check_block_param(name) { \
block_param = getBlockParam(param, name); \
if (!block_param) { \
FATAL("no \"%s\" defined for shout device defined at line " \
"%i\n", name, param->line); \
} \
}
static void *my_shout_init_driver(struct audio_output *audio_output,
const struct audio_format *audio_format,
ConfigParam *param)
{
struct shout_data *sd;
char *test;
int port;
char *host;
char *mount;
char *passwd;
const char *encoding;
unsigned protocol;
const char *user;
char *name;
BlockParam *block_param;
int public;
sd = new_shout_data();
sd->audio_output = audio_output;
if (shout_init_count == 0)
shout_init();
shout_init_count++;
check_block_param("host");
host = block_param->value;
check_block_param("mount");
mount = block_param->value;
check_block_param("port");
port = strtol(block_param->value, &test, 10);
if (*test != '\0' || port <= 0) {
FATAL("shout port \"%s\" is not a positive integer, line %i\n",
block_param->value, block_param->line);
}
check_block_param("password");
passwd = block_param->value;
check_block_param("name");
name = block_param->value;
public = getBoolBlockParam(param, "public", 1);
if (public == CONF_BOOL_UNSET)
public = 0;
block_param = getBlockParam(param, "user");
if (block_param)
user = block_param->value;
else
user = "source";
block_param = getBlockParam(param, "quality");
if (block_param) {
int line = block_param->line;
sd->quality = strtod(block_param->value, &test);
if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) {
FATAL("shout quality \"%s\" is not a number in the "
"range -1 to 10, line %i\n", block_param->value,
block_param->line);
}
block_param = getBlockParam(param, "bitrate");
if (block_param) {
FATAL("quality (line %i) and bitrate (line %i) are "
"both defined for shout output\n", line,
block_param->line);
}
} else {
block_param = getBlockParam(param, "bitrate");
if (!block_param) {
FATAL("neither bitrate nor quality defined for shout "
"output at line %i\n", param->line);
}
sd->bitrate = strtol(block_param->value, &test, 10);
if (*test != '\0' || sd->bitrate <= 0) {
FATAL("bitrate at line %i should be a positive integer "
"\n", block_param->line);
}
}
check_block_param("format");
assert(audio_format != NULL);
sd->audio_format = *audio_format;
block_param = getBlockParam(param, "encoding");
if (block_param) {
if (0 == strcmp(block_param->value, "mp3"))
encoding = block_param->value;
else if (0 == strcmp(block_param->value, "ogg"))
encoding = block_param->value;
else
FATAL("shout encoding \"%s\" is not \"ogg\" or "
"\"mp3\", line %i\n", block_param->value,
block_param->line);
} else {
encoding = "ogg";
}
sd->encoder = shout_encoder_plugin_get(encoding);
if (sd->encoder == NULL)
FATAL("couldn't find shout encoder plugin for \"%s\" "
"at line %i\n", encoding, block_param->line);
check_block_param("protocol");
block_param = getBlockParam(param, "protocol");
if (block_param) {
if (0 == strcmp(block_param->value, "shoutcast") &&
0 != strcmp(encoding, "mp3"))
FATAL("you cannot stream \"%s\" to shoutcast, use mp3\n",
encoding);
else if (0 == strcmp(block_param->value, "shoutcast"))
protocol = SHOUT_PROTOCOL_ICY;
else if (0 == strcmp(block_param->value, "icecast1"))
protocol = SHOUT_PROTOCOL_XAUDIOCAST;
else if (0 == strcmp(block_param->value, "icecast2"))
protocol = SHOUT_PROTOCOL_HTTP;
else
FATAL("shout protocol \"%s\" is not \"shoutcast\" or "
"\"icecast1\"or "
"\"icecast2\", line %i\n", block_param->value,
block_param->line);
} else {
protocol = SHOUT_PROTOCOL_HTTP;
}
if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS ||
shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS ||
shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS ||
shout_set_mount(sd->shout_conn, mount) != SHOUTERR_SUCCESS ||
shout_set_name(sd->shout_conn, name) != SHOUTERR_SUCCESS ||
shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS ||
shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS ||
shout_set_nonblocking(sd->shout_conn, 1) != SHOUTERR_SUCCESS ||
shout_set_format(sd->shout_conn, sd->encoder->shout_format)
!= SHOUTERR_SUCCESS ||
shout_set_protocol(sd->shout_conn, protocol) != SHOUTERR_SUCCESS ||
shout_set_agent(sd->shout_conn, "MPD") != SHOUTERR_SUCCESS) {
FATAL("error configuring shout defined at line %i: %s\n",
param->line, shout_get_error(sd->shout_conn));
}
/* optional paramters */
block_param = getBlockParam(param, "timeout");
if (block_param) {
sd->timeout = (int)strtol(block_param->value, &test, 10);
if (*test != '\0' || sd->timeout <= 0) {
FATAL("shout timeout is not a positive integer, "
"line %i\n", block_param->line);
}
}
block_param = getBlockParam(param, "genre");
if (block_param && shout_set_genre(sd->shout_conn, block_param->value)) {
FATAL("error configuring shout defined at line %i: %s\n",
param->line, shout_get_error(sd->shout_conn));
}
block_param = getBlockParam(param, "description");
if (block_param && shout_set_description(sd->shout_conn,
block_param->value)) {
FATAL("error configuring shout defined at line %i: %s\n",
param->line, shout_get_error(sd->shout_conn));
}
{
char temp[11];
memset(temp, 0, sizeof(temp));
snprintf(temp, sizeof(temp), "%u", sd->audio_format.channels);
shout_set_audio_info(sd->shout_conn, SHOUT_AI_CHANNELS, temp);
snprintf(temp, sizeof(temp), "%u", sd->audio_format.sample_rate);
shout_set_audio_info(sd->shout_conn, SHOUT_AI_SAMPLERATE, temp);
if (sd->quality >= -1.0) {
snprintf(temp, sizeof(temp), "%2.2f", sd->quality);
shout_set_audio_info(sd->shout_conn, SHOUT_AI_QUALITY,
temp);
} else {
snprintf(temp, sizeof(temp), "%d", sd->bitrate);
shout_set_audio_info(sd->shout_conn, SHOUT_AI_BITRATE,
temp);
}
}
if (sd->encoder->init_func(sd) != 0)
FATAL("shout: encoder plugin '%s' failed to initialize\n",
sd->encoder->name);
return sd;
}
static int handle_shout_error(struct shout_data *sd, int err)
{
switch (err) {
case SHOUTERR_SUCCESS:
break;
case SHOUTERR_UNCONNECTED:
case SHOUTERR_SOCKET:
ERROR("Lost shout connection to %s:%i: %s\n",
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
shout_get_error(sd->shout_conn));
sd->shout_error = 1;
return -1;
default:
ERROR("shout: connection to %s:%i error: %s\n",
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
shout_get_error(sd->shout_conn));
sd->shout_error = 1;
return -1;
}
return 0;
}
static int write_page(struct shout_data *sd)
{
int err;
if (sd->buf.len == 0)
return 0;
shout_sync(sd->shout_conn);
err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len);
if (handle_shout_error(sd, err) < 0)
return -1;
sd->buf.len = 0;
return 0;
}
static void close_shout_conn(struct shout_data * sd)
{
if (sd->opened) {
if (sd->encoder->clear_encoder_func(sd))
write_page(sd);
}
if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
ERROR("problem closing connection to shout server: %s\n",
shout_get_error(sd->shout_conn));
}
sd->opened = 0;
}
static void my_shout_finish_driver(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
close_shout_conn(sd);
sd->encoder->finish_func(sd);
free_shout_data(sd);
shout_init_count--;
if (shout_init_count == 0)
shout_shutdown();
}
static void my_shout_drop_buffered_audio(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
timer_reset(sd->timer);
/* needs to be implemented for shout */
}
static void my_shout_close_device(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
close_shout_conn(sd);
if (sd->timer) {
timer_free(sd->timer);
sd->timer = NULL;
}
}
static int shout_connect(struct shout_data *sd)
{
time_t t = time(NULL);
int state = shout_get_connected(sd->shout_conn);
/* already connected */
if (state == SHOUTERR_CONNECTED)
return 0;
/* waiting to connect */
if (state == SHOUTERR_BUSY && sd->conn_attempts != 0) {
/* timeout waiting to connect */
if ((t - sd->last_attempt) > sd->timeout) {
ERROR("timeout connecting to shout server %s:%i "
"(attempt %i)\n",
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
sd->conn_attempts);
return -1;
}
return 1;
}
/* we're in some funky state, so just reset it to unconnected */
if (state != SHOUTERR_UNCONNECTED)
shout_close(sd->shout_conn);
/* throttle new connection attempts */
if (sd->conn_attempts != 0 &&
(t - sd->last_attempt) <= CONN_ATTEMPT_INTERVAL) {
return -1;
}
/* initiate a new connection */
sd->conn_attempts++;
sd->last_attempt = t;
state = shout_open(sd->shout_conn);
switch (state) {
case SHOUTERR_SUCCESS:
case SHOUTERR_CONNECTED:
return 0;
case SHOUTERR_BUSY:
return 1;
default:
ERROR("problem opening connection to shout server %s:%i "
"(attempt %i): %s\n",
shout_get_host(sd->shout_conn),
shout_get_port(sd->shout_conn),
sd->conn_attempts, shout_get_error(sd->shout_conn));
return -1;
}
}
static int open_shout_conn(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
int status;
status = shout_connect(sd);
if (status != 0)
return status;
if (sd->encoder->init_encoder_func(sd) < 0) {
shout_close(sd->shout_conn);
return -1;
}
write_page(sd);
sd->shout_error = 0;
sd->opened = 1;
sd->tag_to_send = 1;
sd->conn_attempts = 0;
return 0;
}
static int my_shout_open_device(void *data,
struct audio_format *audio_format)
{
struct shout_data *sd = (struct shout_data *)data;
if (!sd->opened && open_shout_conn(sd) < 0)
return -1;
if (sd->timer)
timer_free(sd->timer);
sd->timer = timer_new(audio_format);
return 0;
}
static void send_metadata(struct shout_data * sd)
{
static const int size = 1024;
char song[size];
if (!sd->opened || !sd->tag)
return;
if (sd->encoder->send_metadata_func(sd, song, size)) {
shout_metadata_add(sd->shout_meta, "song", song);
if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
sd->shout_meta)) {
ERROR("error setting shout metadata\n");
return;
}
}
sd->tag_to_send = 0;
}
static int my_shout_play(void *data,
const char *chunk, size_t size)
{
struct shout_data *sd = (struct shout_data *)data;
int status;
if (!sd->timer->started)
timer_start(sd->timer);
timer_add(sd->timer, size);
if (sd->opened && sd->tag_to_send)
send_metadata(sd);
if (!sd->opened) {
status = open_shout_conn(sd);
if (status < 0) {
my_shout_close_device(sd);
return -1;
} else if (status > 0) {
timer_sync(sd->timer);
return 0;
}
}
if (sd->encoder->encode_func(sd, chunk, size)) {
my_shout_close_device(sd);
return -1;
}
if (write_page(sd) < 0) {
my_shout_close_device(sd);
return -1;
}
return 0;
}
static void my_shout_pause(void *data)
{
struct shout_data *sd = (struct shout_data *)data;
static const char silence[1020];
int ret;
/* play silence until the player thread sends us a command */
while (sd->opened && !audio_output_is_pending(sd->audio_output)) {
ret = my_shout_play(data, silence, sizeof(silence));
if (ret != 0)
break;
}
}
static void my_shout_set_tag(void *data,
const struct tag *tag)
{
struct shout_data *sd = (struct shout_data *)data;
if (sd->tag)
tag_free(sd->tag);
sd->tag = NULL;
sd->tag_to_send = 0;
if (!tag)
return;
sd->tag = tag_dup(tag);
sd->tag_to_send = 1;
}
const struct audio_output_plugin shoutPlugin = {
.name = "shout",
.init = my_shout_init_driver,
.finish = my_shout_finish_driver,
.open = my_shout_open_device,
.play = my_shout_play,
.pause = my_shout_pause,
.cancel = my_shout_drop_buffered_audio,
.close = my_shout_close_device,
.send_tag = my_shout_set_tag,
};
#else
DISABLED_AUDIO_OUTPUT_PLUGIN(shoutPlugin)
#endif

93
src/output/shout_plugin.h Normal file
View File

@@ -0,0 +1,93 @@
/* the Music Player Daemon (MPD)
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
* This project's homepage is: http://www.musicpd.org
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef AUDIO_OUTPUT_SHOUT_H
#define AUDIO_OUTPUT_SHOUT_H
#include "../output_api.h"
#ifdef HAVE_SHOUT
#include "../conf.h"
#include "../timer.h"
#include <shout/shout.h>
struct shout_data;
struct shout_encoder_plugin {
const char *name;
unsigned int shout_format;
int (*clear_encoder_func)(struct shout_data *sd);
int (*encode_func)(struct shout_data *sd,
const char *chunk, size_t len);
void (*finish_func)(struct shout_data *sd);
int (*init_func)(struct shout_data *sd);
int (*init_encoder_func) (struct shout_data *sd);
/* Called when there is a new MpdTag to encode into the
stream. If this function returns non-zero, then the
resulting song will be passed to the shout server as
metadata. This allows the Ogg encoder to send metadata via
Vorbis comments in the stream, while an MP3 encoder can use
the Shout Server's metadata API. */
int (*send_metadata_func)(struct shout_data *sd,
char *song, size_t size);
};
struct shout_buffer {
unsigned char data[8192];
size_t len;
};
struct shout_data {
struct audio_output *audio_output;
shout_t *shout_conn;
shout_metadata_t *shout_meta;
int shout_error;
const struct shout_encoder_plugin *encoder;
void *encoder_data;
float quality;
int bitrate;
int opened;
struct tag *tag;
int tag_to_send;
int timeout;
int conn_attempts;
time_t last_attempt;
Timer *timer;
/* the configured audio format */
struct audio_format audio_format;
struct shout_buffer buf;
};
extern const struct shout_encoder_plugin shout_mp3_encoder;
extern const struct shout_encoder_plugin shout_ogg_encoder;
#endif
#endif