From 1a954748a027aa5b4fc6c85b0ad96c2fa25d53b8 Mon Sep 17 00:00:00 2001 From: Hans-Kristian Arntzen Date: Tue, 8 Feb 2011 00:17:58 +0100 Subject: RoarAudio output plugin --- Makefile.am | 7 + NEWS | 1 + configure.ac | 17 +++ src/mixer/roar_mixer_plugin.c | 137 +++++++++++++++++ src/mixer_list.h | 1 + src/output/roar_output_plugin.h | 41 +++++ src/output/roar_plugin.c | 329 ++++++++++++++++++++++++++++++++++++++++ src/output_list.c | 4 + 8 files changed, 537 insertions(+) create mode 100644 src/mixer/roar_mixer_plugin.c create mode 100644 src/output/roar_output_plugin.h create mode 100644 src/output/roar_plugin.c diff --git a/Makefile.am b/Makefile.am index d4fec31ff..7b41d1c75 100644 --- a/Makefile.am +++ b/Makefile.am @@ -142,6 +142,7 @@ mpd_headers = \ src/output/httpd_client.h \ src/output/httpd_internal.h \ src/output/pulse_output_plugin.h \ + src/output/roar_output_plugin.h \ src/output/winmm_output_plugin.h \ src/page.h \ src/pcm_buffer.h \ @@ -673,6 +674,7 @@ OUTPUT_LIBS = \ $(LIBWRAP_LDFLAGS) \ $(AO_LIBS) \ $(ALSA_LIBS) \ + $(ROAR_LIBS) \ $(FFADO_LIBS) \ $(JACK_LIBS) \ $(OPENAL_LIBS) \ @@ -706,6 +708,11 @@ OUTPUT_SRC += src/output/alsa_plugin.c MIXER_SRC += src/mixer/alsa_mixer_plugin.c endif +if HAVE_ROAR +OUTPUT_SRC += src/output/roar_plugin.c +MIXER_SRC += src/mixer/roar_mixer_plugin.c +endif + if ENABLE_FFADO_OUTPUT OUTPUT_SRC += src/output/ffado_output_plugin.c endif diff --git a/NEWS b/NEWS index 356d06f6b..4bf0dc882 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ ver 0.17 (2011/??/??) - osx: allow user to specify other audio devices - raop: new output plugin - shout: add possibility to set url + - roar: new output plugin for RoarAudio * state_file: add option "restore_paused" diff --git a/configure.ac b/configure.ac index 89b5ce321..0d07555e0 100644 --- a/configure.ac +++ b/configure.ac @@ -121,6 +121,11 @@ AC_ARG_ENABLE(alsa, AS_HELP_STRING([--enable-alsa], [enable ALSA support]),, [enable_alsa=auto]) +AC_ARG_ENABLE(roar, + AS_HELP_STRING([--enable-roar], + [enable support for RoarAudio]),, + [enable_roar=auto]) + AC_ARG_ENABLE(ao, AS_HELP_STRING([--enable-ao], [enable support for libao]),, @@ -1221,6 +1226,16 @@ fi AM_CONDITIONAL(HAVE_ALSA, test x$enable_alsa = xyes) +dnl ----------------------------------- ROAR ---------------------------------- +MPD_AUTO_PKG(roar, ROAR, [libroar >= 0.4.0], + [ROAR output plugin], [libroar not found]) + +if test x$enable_roar = xyes; then + AC_DEFINE(HAVE_ROAR, 1, [Define to enable ROAR support]) +fi + +AM_CONDITIONAL(HAVE_ROAR, test x$enable_roar = xyes) + dnl ----------------------------------- FFADO --------------------------------- MPD_AUTO_PKG(ffado, FFADO, [libffado], @@ -1430,6 +1445,7 @@ AM_CONDITIONAL(ENABLE_WINMM_OUTPUT, test x$enable_winmm_output = xyes) dnl --------------------- Post Audio Output Plugins Tests --------------------- if test x$enable_alsa = xno && + test x$enable_roar = xno && test x$enable_ao = xno && test x$enable_ffado = xno && test x$enable_fifo = xno && @@ -1566,6 +1582,7 @@ results(id3,[ID3]) printf '\nPlayback support:\n\t' results(alsa,ALSA) +results(roar,ROAR) results(ffado,FFADO) results(fifo,FIFO) results(recorder_output,[File Recorder]) diff --git a/src/mixer/roar_mixer_plugin.c b/src/mixer/roar_mixer_plugin.c new file mode 100644 index 000000000..915f35e92 --- /dev/null +++ b/src/mixer/roar_mixer_plugin.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "output/roar_output_plugin.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +typedef struct roar_mpd_mixer +{ + /** the base mixer class */ + struct mixer base; + roar_t *self; +} roar_mixer_t; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +roar_mixer_quark(void) +{ + return g_quark_from_static_string("roar_mixer"); +} + +static struct mixer * +roar_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = g_new(roar_mixer_t, 1); + self->self = ao; + + mixer_init(&self->base, &roar_mixer_plugin); + + return &self->base; +} + +static void +roar_mixer_finish(struct mixer *data) +{ + roar_mixer_t *self = (roar_mixer_t *) data; + + g_free(self); +} + +static void +roar_mixer_close(G_GNUC_UNUSED struct mixer *data) +{ +} + +static bool +roar_mixer_open(G_GNUC_UNUSED struct mixer *data, + G_GNUC_UNUSED GError **error_r) +{ + return true; +} + +static int +roar_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = (roar_mixer_t *)mixer; + g_mutex_lock(self->self->lock); + if (self->self->vss && self->self->alive) + { + float l, r; + int error; + roar_vs_volume_get(self->self->vss, &l, &r, &error); + g_mutex_unlock(self->self->lock); + return (l + r) * 50; + } + else + { + g_mutex_unlock(self->self->lock); + return 0; + } +} + +static bool +roar_mixer_set_volume(struct mixer *mixer, unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + roar_mixer_t *self = (roar_mixer_t *)mixer; + g_mutex_lock(self->self->lock); + if (self->self->vss && self->self->alive) + { + assert(volume <= 100); + + int error; + float level = volume / 100.0; + + roar_vs_volume_mono(self->self->vss, level, &error); + g_mutex_unlock(self->self->lock); + return true; + } + else + { + g_mutex_unlock(self->self->lock); + return false; + } +} + +const struct mixer_plugin roar_mixer_plugin = { + .init = roar_mixer_init, + .finish = roar_mixer_finish, + .open = roar_mixer_open, + .close = roar_mixer_close, + .get_volume = roar_mixer_get_volume, + .set_volume = roar_mixer_set_volume, + .global = false, +}; diff --git a/src/mixer_list.h b/src/mixer_list.h index f284c2dc9..95ded5c23 100644 --- a/src/mixer_list.h +++ b/src/mixer_list.h @@ -28,6 +28,7 @@ extern const struct mixer_plugin software_mixer_plugin; extern const struct mixer_plugin alsa_mixer_plugin; extern const struct mixer_plugin oss_mixer_plugin; +extern const struct mixer_plugin roar_mixer_plugin; extern const struct mixer_plugin pulse_mixer_plugin; extern const struct mixer_plugin raop_mixer_plugin; extern const struct mixer_plugin winmm_mixer_plugin; diff --git a/src/output/roar_output_plugin.h b/src/output/roar_output_plugin.h new file mode 100644 index 000000000..08e272007 --- /dev/null +++ b/src/output/roar_output_plugin.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#ifndef __ROAR_OUTPUT_H +#define __ROAR_OUTPUT_H + +#include +#include + +typedef struct roar +{ + roar_vs_t * vss; + int err; + char *host; + char *name; + int role; + struct roar_connection con; + struct roar_audio_info info; + GMutex *lock; + volatile bool alive; +} roar_t; + +#endif diff --git a/src/output/roar_plugin.c b/src/output/roar_plugin.c new file mode 100644 index 000000000..f12f795c4 --- /dev/null +++ b/src/output/roar_plugin.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft + * Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "mixer_list.h" +#include "roar_output_plugin.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "roaraudio" + +static inline GQuark +roar_output_quark(void) +{ + return g_quark_from_static_string("roar_output"); +} + +static void +roar_configure(struct roar * self, const struct config_param *param) +{ + self->host = config_dup_block_string(param, "server", NULL); + self->name = config_dup_block_string(param, "name", "MPD"); + char *role = config_dup_block_string(param, "role", "music"); + if (role != NULL) + { + self->role = roar_str2role(role); + g_free(role); + } + else + self->role = ROAR_ROLE_MUSIC; +} + +static void * +roar_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + GMutex *lock = g_mutex_new(); + + roar_t * self = roar_mm_calloc(1, sizeof(*self)); + if (self == NULL) + { + g_set_error(error, roar_output_quark(), 0, "Failed to allocate memory"); + return NULL; + } + + self->lock = lock; + self->err = ROAR_ERROR_NONE; + roar_configure(self, param); + return self; +} + +static void +roar_close(void *data) +{ + roar_t * self = data; + g_mutex_lock(self->lock); + self->alive = false; + + if (self->vss != NULL) + roar_vs_close(self->vss, ROAR_VS_TRUE, &(self->err)); + self->vss = NULL; + roar_disconnect(&(self->con)); + g_mutex_unlock(self->lock); +} + +static void +roar_finish(void *data) +{ + roar_t * self = data; + + g_free(self->host); + g_free(self->name); + g_mutex_free(self->lock); + + roar_mm_free(data); +} + +static bool +roar_open(void *data, struct audio_format *audio_format, GError **error) +{ + roar_t * self = data; + g_mutex_lock(self->lock); + + if (roar_simple_connect(&(self->con), self->host, self->name) < 0) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to Roar server"); + g_mutex_unlock(self->lock); + return false; + } + + self->vss = roar_vs_new_from_con(&(self->con), &(self->err)); + + if (self->vss == NULL || self->err != ROAR_ERROR_NONE) + { + g_set_error(error, roar_output_quark(), 0, + "Failed to connect to server"); + g_mutex_unlock(self->lock); + return false; + } + + self->info.rate = audio_format->sample_rate; + self->info.channels = audio_format->channels; + self->info.codec = ROAR_CODEC_PCM_S; + + switch (audio_format->format) + { + case SAMPLE_FORMAT_S8: + self->info.bits = 8; + break; + case SAMPLE_FORMAT_S16: + self->info.bits = 16; + break; + case SAMPLE_FORMAT_S24: + self->info.bits = 24; + break; + case SAMPLE_FORMAT_S24_P32: + self->info.bits = 32; + audio_format->format = SAMPLE_FORMAT_S32; + break; + case SAMPLE_FORMAT_S32: + self->info.bits = 32; + break; + default: + self->info.bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; + } + audio_format->reverse_endian = 0; + + if (roar_vs_stream(self->vss, &(self->info), ROAR_DIR_PLAY, + &(self->err)) < 0) + { + g_set_error(error, roar_output_quark(), 0, "Failed to start stream"); + g_mutex_unlock(self->lock); + return false; + } + roar_vs_role(self->vss, self->role, &(self->err)); + self->alive = true; + + g_mutex_unlock(self->lock); + return true; +} + +static void +roar_cancel(void *data) +{ + roar_t * self = data; + + g_mutex_lock(self->lock); + if (self->vss != NULL) + { + roar_vs_t *vss = self->vss; + self->vss = NULL; + roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); + self->alive = false; + + vss = roar_vs_new_from_con(&(self->con), &(self->err)); + if (vss) + { + roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, &(self->err)); + roar_vs_role(vss, self->role, &(self->err)); + self->vss = vss; + self->alive = true; + } + } + g_mutex_unlock(self->lock); +} + +static size_t +roar_play(void *data, const void *chunk, size_t size, GError **error) +{ + struct roar * self = data; + ssize_t rc; + + if (self->vss == NULL) + { + g_set_error(error, roar_output_quark(), 0, "Connection is invalid"); + return 0; + } + + rc = roar_vs_write(self->vss, chunk, size, &(self->err)); + if ( rc <= 0 ) + { + g_set_error(error, roar_output_quark(), 0, "Failed to play data"); + return 0; + } + + return rc; +} + +static const char* +roar_tag_convert(enum tag_type type, bool *is_uuid) +{ + *is_uuid = false; + switch (type) + { + case TAG_ARTIST: + case TAG_ALBUM_ARTIST: + return "AUTHOR"; + case TAG_ALBUM: + return "ALBUM"; + case TAG_TITLE: + return "TITLE"; + case TAG_TRACK: + return "TRACK"; + case TAG_NAME: + return "NAME"; + case TAG_GENRE: + return "GENRE"; + case TAG_DATE: + return "DATE"; + case TAG_PERFORMER: + return "PERFORMER"; + case TAG_COMMENT: + return "COMMENT"; + case TAG_DISC: + return "DISCID"; + case TAG_COMPOSER: +#ifdef ROAR_META_TYPE_COMPOSER + return "COMPOSER"; +#else + return "AUTHOR"; +#endif + case TAG_MUSICBRAINZ_ARTISTID: + case TAG_MUSICBRAINZ_ALBUMID: + case TAG_MUSICBRAINZ_ALBUMARTISTID: + case TAG_MUSICBRAINZ_TRACKID: + *is_uuid = true; + return "HASH"; + + default: + return NULL; + } +} + +static void +roar_send_tag(void *data, const struct tag *meta) +{ + struct roar * self = data; + + if (self->vss == NULL) + return; + + g_mutex_lock(self->lock); + size_t cnt = 1; + struct roar_keyval vals[32]; + memset(vals, 0, sizeof(vals)); + char uuid_buf[32][64]; + + char timebuf[16]; + snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", + meta->time / 3600, (meta->time % 3600) / 60, meta->time % 60); + + vals[0].key = g_strdup("LENGTH"); + vals[0].value = timebuf; + + for (unsigned i = 0; i < meta->num_items && cnt < 32; i++) + { + bool is_uuid = false; + const char *key = roar_tag_convert(meta->items[i]->type, &is_uuid); + if (key != NULL) + { + if (is_uuid) + { + snprintf(uuid_buf[cnt], sizeof(uuid_buf[0]), "{UUID}%s", + meta->items[i]->value); + vals[cnt].key = g_strdup(key); + vals[cnt].value = uuid_buf[cnt]; + } + else + { + vals[cnt].key = g_strdup(key); + vals[cnt].value = meta->items[i]->value; + } + cnt++; + } + } + + roar_vs_meta(self->vss, vals, cnt, &(self->err)); + + for (unsigned i = 0; i < 32; i++) + g_free(vals[i].key); + + g_mutex_unlock(self->lock); +} + +const struct audio_output_plugin roar_output_plugin = { + .name = "roar", + .init = roar_init, + .finish = roar_finish, + .open = roar_open, + .play = roar_play, + .cancel = roar_cancel, + .close = roar_close, + .send_tag = roar_send_tag, + + .mixer_plugin = &roar_mixer_plugin +}; diff --git a/src/output_list.c b/src/output_list.c index 24b089e1f..75d24d63b 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -26,6 +26,7 @@ extern const struct audio_output_plugin null_output_plugin; extern const struct audio_output_plugin fifo_output_plugin; extern const struct audio_output_plugin pipe_output_plugin; extern const struct audio_output_plugin alsaPlugin; +extern const struct audio_output_plugin roar_output_plugin; extern const struct audio_output_plugin ao_output_plugin; extern const struct audio_output_plugin oss_output_plugin; extern const struct audio_output_plugin openal_output_plugin; @@ -54,6 +55,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_ALSA &alsaPlugin, #endif +#ifdef HAVE_ROAR + &roar_output_plugin, +#endif #ifdef HAVE_AO &ao_output_plugin, #endif -- cgit v1.2.3