/* * 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 "roar_output_plugin.h" #include "output_api.h" #include "mixer_list.h" #include "roar_output_plugin.h" #include #include #include #include #include #include #include #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "roaraudio" 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; static inline GQuark roar_output_quark(void) { return g_quark_from_static_string("roar_output"); } static int roar_output_get_volume_locked(struct roar *roar) { if (roar->vss == NULL || !roar->alive) return -1; float l, r; int error; if (roar_vs_volume_get(roar->vss, &l, &r, &error) < 0) return -1; return (l + r) * 50; } int roar_output_get_volume(struct roar *roar) { g_mutex_lock(roar->lock); int volume = roar_output_get_volume_locked(roar); g_mutex_unlock(roar->lock); return volume; } static bool roar_output_set_volume_locked(struct roar *roar, unsigned volume) { assert(volume <= 100); if (roar->vss == NULL || !roar->alive) return false; int error; float level = volume / 100.0; roar_vs_volume_mono(roar->vss, level, &error); return true; } bool roar_output_set_volume(struct roar *roar, unsigned volume) { g_mutex_lock(roar->lock); bool success = roar_output_set_volume_locked(roar, volume); g_mutex_unlock(roar->lock); return success; } 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"); const char *role = config_get_block_string(param, "role", "music"); self->role = role != NULL ? roar_str2role(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) { struct roar *self = g_new0(struct roar, 1); self->lock = g_mutex_new(); self->err = ROAR_ERROR_NONE; roar_configure(self, param); return self; } static void roar_finish(void *data) { roar_t * self = data; g_free(self->host); g_free(self->name); g_mutex_free(self->lock); g_free(self); } 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_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_cancel_locked(struct roar *self) { if (self->vss == NULL) return; 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 == NULL) return; if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY, &(self->err)) < 0) { roar_vs_close(vss, ROAR_VS_TRUE, &(self->err)); g_warning("Failed to start stream"); return; } roar_vs_role(vss, self->role, &(self->err)); self->vss = vss; self->alive = true; } static void roar_cancel(void *data) { roar_t * self = data; g_mutex_lock(self->lock); roar_cancel_locked(self); 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 };