diff options
Diffstat (limited to '')
45 files changed, 1755 insertions, 100 deletions
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 422264f53..0bbe231fd 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c index 42ece5a3a..33366d3b8 100644 --- a/src/output/ao_plugin.c +++ b/src/output/ao_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/ffado_output_plugin.c b/src/output/ffado_output_plugin.c index 723698ed0..1a4ec3919 100644 --- a/src/output/ffado_output_plugin.c +++ b/src/output/ffado_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/fifo_output_plugin.c b/src/output/fifo_output_plugin.c index f4217ec4d..8f2234dd7 100644 --- a/src/output/fifo_output_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -42,7 +42,7 @@ struct fifo_data { int input; int output; bool created; - Timer *timer; + struct timer *timer; }; /** diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 1119a7834..e2c49c6c8 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/httpd_client.h b/src/output/httpd_client.h index 7ebd0bbc0..739163f42 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index 277e70f11..3e6e9768d 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -65,10 +65,10 @@ struct httpd_output { GMutex *mutex; /** - * A #Timer object to synchronize this output with the + * A #timer object to synchronize this output with the * wallclock. */ - Timer *timer; + struct timer *timer; /** * The listener socket. diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 40ad05c3d..3f570c7b9 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c index 2767d4eb8..4df84fd23 100644 --- a/src/output/jack_output_plugin.c +++ b/src/output/jack_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c index 6cc8fa34e..be4c8dbc0 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c index 89abbd91f..f572959a0 100644 --- a/src/output/null_plugin.c +++ b/src/output/null_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ struct null_data { bool sync; - Timer *timer; + struct timer *timer; }; static void * @@ -82,7 +82,7 @@ null_play(void *data, G_GNUC_UNUSED const void *chunk, size_t size, G_GNUC_UNUSED GError **error) { struct null_data *nd = data; - Timer *timer = nd->timer; + struct timer *timer = nd->timer; if (!nd->sync) return size; diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c index 767b3eb17..23641c6c5 100644 --- a/src/output/openal_plugin.c +++ b/src/output/openal_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,7 +41,7 @@ struct openal_data { const char *device_name; ALCdevice *device; ALCcontext *context; - Timer *timer; + struct timer *timer; ALuint buffers[NUM_BUFFERS]; int filled; ALuint source; diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c index 9261b423c..d7df594d3 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c index 7639f3bd9..8091660ab 100644 --- a/src/output/osx_plugin.c +++ b/src/output/osx_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,11 @@ #define G_LOG_DOMAIN "osx" struct osx_output { + /* configuration settings */ + OSType component_subtype; + /* only applicable with kAudioUnitSubType_HALOutput */ + const char *device_name; + AudioUnit au; GMutex *mutex; GCond *condition; @@ -54,6 +59,26 @@ osx_output_test_default_device(void) return true; } +static void +osx_output_configure(struct osx_output *oo, const struct config_param *param) +{ + const char *device = config_get_block_string(param, "device", NULL); + + if (device == NULL || 0 == strcmp(device, "default")) { + oo->component_subtype = kAudioUnitSubType_DefaultOutput; + oo->device_name = NULL; + } + else if (0 == strcmp(device, "system")) { + oo->component_subtype = kAudioUnitSubType_SystemOutput; + oo->device_name = NULL; + } + else { + oo->component_subtype = kAudioUnitSubType_HALOutput; + /* XXX am I supposed to g_strdup() this? */ + oo->device_name = device; + } +} + static void * osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, G_GNUC_UNUSED const struct config_param *param, @@ -61,6 +86,7 @@ osx_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, { struct osx_output *oo = g_new(struct osx_output, 1); + osx_output_configure(oo, param); oo->mutex = g_mutex_new(); oo->condition = g_cond_new(); @@ -150,6 +176,95 @@ osx_render(void *vdata, } static bool +osx_output_set_device(struct osx_output *oo, GError **error) +{ + bool ret = true; + OSStatus status; + UInt32 size, numdevices; + AudioDeviceID *deviceids = NULL; + char name[256]; + unsigned int i; + + if (oo->component_subtype != kAudioUnitSubType_HALOutput) + goto done; + + /* how many audio devices are there? */ + status = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, + &size, + NULL); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine number of OS X audio devices: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* what are the available audio device IDs? */ + numdevices = size / sizeof(AudioDeviceID); + deviceids = g_malloc(size); + status = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, + &size, + deviceids); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X audio device IDs: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + + /* which audio device matches oo->device_name? */ + for (i = 0; i < numdevices; i++) { + size = sizeof(name); + status = AudioDeviceGetProperty(deviceids[i], 0, false, + kAudioDevicePropertyDeviceName, + &size, name); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to determine OS X device name " + "(device %u): %s", + (unsigned int) deviceids[i], + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + if (strcmp(oo->device_name, name) == 0) { + g_debug("found matching device: ID=%u, name=%s", + (unsigned int) deviceids[i], name); + break; + } + } + if (i == numdevices) { + g_warning("Found no audio device with name '%s' " + "(will use default audio device)", + oo->device_name); + goto done; + } + + status = AudioUnitSetProperty(oo->au, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &(deviceids[i]), + sizeof(AudioDeviceID)); + if (status != noErr) { + g_set_error(error, osx_output_quark(), status, + "Unable to set OS X audio output device: %s", + GetMacOSStatusCommentString(status)); + ret = false; + goto done; + } + g_debug("set OS X audio output device ID=%u, name=%s", + (unsigned int) deviceids[i], name); + +done: + if (deviceids != NULL) + g_free(deviceids); + return ret; +} + +static bool osx_output_open(void *data, struct audio_format *audio_format, GError **error) { struct osx_output *od = data; @@ -161,7 +276,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) ComponentResult result; desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentSubType = od->component_subtype; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; @@ -175,7 +290,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) status = OpenAComponent(comp, &od->au); if (status != noErr) { - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), status, "Unable to open OS X component: %s", GetMacOSStatusCommentString(status)); return false; @@ -184,12 +299,15 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) status = AudioUnitInitialize(od->au); if (status != noErr) { CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), status, "Unable to initialize OS X audio unit: %s", GetMacOSStatusCommentString(status)); return false; } + if (!osx_output_set_device(od, error)) + return false; + callback.inputProc = osx_render; callback.inputProcRefCon = od; @@ -200,7 +318,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) if (result != noErr) { AudioUnitUninitialize(od->au); CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), result, "unable to set callback for OS X audio unit"); return false; } @@ -241,7 +359,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) if (result != noErr) { AudioUnitUninitialize(od->au); CloseComponent(od->au); - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), result, "Unable to set format on OS X device"); return false; } @@ -256,7 +374,7 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) status = AudioOutputUnitStart(od->au); if (status != 0) { - g_set_error(error, osx_output_quark(), 0, + g_set_error(error, osx_output_quark(), status, "unable to start audio output: %s", GetMacOSStatusCommentString(status)); return false; diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c index 1d1aec7b1..111c654b9 100644 --- a/src/output/pipe_output_plugin.c +++ b/src/output/pipe_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c index 34d736546..9825785ee 100644 --- a/src/output/pulse_output_plugin.c +++ b/src/output/pulse_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h index 06e3aec43..2261175d5 100644 --- a/src/output/pulse_output_plugin.h +++ b/src/output/pulse_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/raop_output_plugin.c b/src/output/raop_output_plugin.c new file mode 100644 index 000000000..735aea976 --- /dev/null +++ b/src/output/raop_output_plugin.c @@ -0,0 +1,950 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "raop_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "raop_output_plugin.h" +#include "rtsp_client.h" +#include "glib_compat.h" + +#include <glib.h> +#include <unistd.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/rsa.h> +#include <openssl/engine.h> + +#ifndef WIN32 +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "raop" + +static struct raop_session_data *raop_session = NULL; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +raop_output_quark(void) +{ + return g_quark_from_static_string("raop_output"); +} + +static void +raop_session_free(struct raop_session_data *session) +{ + assert(session != NULL); + assert(session->raop_list == NULL); + + ntp_server_close(&session->ntp); + + if (session->data_mutex != NULL) + g_mutex_free(session->data_mutex); + + if (session->list_mutex != NULL) + g_mutex_free(session->list_mutex); + + if (raop_session->data_fd >= 0) + close(raop_session->data_fd); + + if (raop_session->ctrl.fd >= 0) + close(raop_session->ctrl.fd); + + g_free(session); +} + +static struct raop_session_data * +raop_session_new(GError **error_r) +{ + struct raop_session_data *session = g_new(struct raop_session_data, 1); + session->raop_list = NULL; + + session->data_mutex = g_mutex_new(); + session->list_mutex = g_mutex_new(); + + ntp_server_init(&session->ntp); + session->ctrl.port = 6001; + session->ctrl.fd = -1; + session->play_state.playing = false; + session->play_state.seq_num = (short) g_random_int(); + session->play_state.rtptime = g_random_int(); + session->play_state.sync_src = g_random_int(); + session->play_state.last_send.tv_sec = 0; + session->play_state.last_send.tv_usec = 0; + session->data_fd = -1; + + if (!RAND_bytes(session->encrypt.iv, sizeof(session->encrypt.iv)) || + !RAND_bytes(session->encrypt.key, sizeof(session->encrypt.key))) { + raop_session_free(session); + g_set_error(error_r, raop_output_quark(), 0, + "RAND_bytes error code=%ld", ERR_get_error()); + return NULL; + } + memcpy(session->encrypt.nv, session->encrypt.iv, sizeof(session->encrypt.nv)); + for (unsigned i = 0; i < 16; i++) { + printf("0x%x ", session->encrypt.key[i]); + } + printf("\n"); + AES_set_encrypt_key(session->encrypt.key, 128, &session->encrypt.ctx); + + memset(session->buffer, 0, RAOP_BUFFER_SIZE); + session->bufferSize = 0; + + return session; +} + +static struct raop_data * +new_raop_data(GError **error_r) +{ + struct raop_data *ret = g_new(struct raop_data, 1); + + ret->control_mutex = g_mutex_new(); + + ret->next = NULL; + ret->is_master = 0; + ret->started = 0; + ret->paused = 0; + + if (raop_session == NULL && + (raop_session = raop_session_new(error_r)) == NULL) { + g_mutex_free(ret->control_mutex); + g_free(ret); + return NULL; + } + + return ret; +} + +/* + * remove one character from a string + * return the number of deleted characters + */ +static int +remove_char_from_string(char *str, char c) +{ + char *src, *dst; + + /* skip all characters that don't need to be copied */ + src = strchr(str, c); + if (!src) + return 0; + + for (dst = src; *src; src++) + if (*src != c) + *(dst++) = *src; + + *dst = '\0'; + + return src - dst; +} + +/* bind an opened socket to specified hostname and port. + * if hostname=NULL, use INADDR_ANY. + * if *port=0, use dynamically assigned port + */ +static int bind_host(int sd, char *hostname, unsigned long ulAddr, + unsigned short *port, GError **error_r) +{ + struct sockaddr_in my_addr; + socklen_t nlen = sizeof(struct sockaddr); + struct hostent *h; + + memset(&my_addr, 0, sizeof(my_addr)); + /* use specified hostname */ + if (hostname) { + /* get server IP address (no check if input is IP address or DNS name) */ + h = gethostbyname(hostname); + if (h == NULL) { + if (strstr(hostname, "255.255.255.255") == hostname) { + my_addr.sin_addr.s_addr=-1; + } else { + if ((my_addr.sin_addr.s_addr = inet_addr(hostname)) == 0xFFFFFFFF) { + g_set_error(error_r, raop_output_quark(), 0, + "failed to resolve host '%s'", + hostname); + return -1; + } + } + my_addr.sin_family = AF_INET; + } else { + my_addr.sin_family = h->h_addrtype; + memcpy((char *) &my_addr.sin_addr.s_addr, + h->h_addr_list[0], h->h_length); + } + } else { + // if hostname=NULL, use INADDR_ANY + if (ulAddr) + my_addr.sin_addr.s_addr = ulAddr; + else + my_addr.sin_addr.s_addr = htonl(INADDR_ANY); + my_addr.sin_family = AF_INET; + } + + /* bind a specified port */ + my_addr.sin_port = htons(*port); + + if (bind(sd, (struct sockaddr *) &my_addr, sizeof(my_addr)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to bind socket: %s", + g_strerror(errno)); + return -1; + } + + if (*port == 0) { + getsockname(sd, (struct sockaddr *) &my_addr, &nlen); + *port = ntohs(my_addr.sin_port); + } + + return 0; +} + +/* + * open udp port + */ +static int +open_udp_socket(char *hostname, unsigned short *port, + GError **error_r) +{ + int sd; + int size = 30000; + + /* socket creation */ + sd = socket(PF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to create UDP socket: %s", + g_strerror(errno)); + return -1; + } + if (setsockopt(sd, SOL_SOCKET, SO_SNDBUF, (void *) &size, sizeof(size)) < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "failed to set UDP buffer size: %s", + g_strerror(errno)); + return -1; + } + if (bind_host(sd, hostname, 0, port, error_r)) { + close(sd); + return -1; + } + + return sd; +} + +static bool +get_sockaddr_by_host(const char *host, short destport, + struct sockaddr_in *addr, + GError **error_r) +{ + struct hostent *h; + + h = gethostbyname(host); + if (h) { + addr->sin_family = h->h_addrtype; + memcpy((char *) &addr->sin_addr.s_addr, h->h_addr_list[0], h->h_length); + } else { + addr->sin_family = AF_INET; + if ((addr->sin_addr.s_addr=inet_addr(host))==0xFFFFFFFF) { + g_set_error(error_r, rtsp_client_quark(), 0, + "failed to resolve host '%s'", host); + return false; + } + } + addr->sin_port = htons(destport); + return true; +} + +/* + * Calculate the current NTP time, store it in the buffer. + */ +static void +fill_int(unsigned char *buffer, uint32_t value) +{ + uint32_t be = GINT32_TO_BE(value); + memcpy(buffer, &be, sizeof(be)); +} + +/* + * Store time in the NTP format in the buffer + */ +static void +fill_time_buffer_with_time(unsigned char *buffer, struct timeval *tout) +{ + unsigned long secs_to_baseline = 964697997; + double fraction; + unsigned long long_fraction; + unsigned long secs; + + fraction = ((double) tout->tv_usec) / 1000000.0; + long_fraction = (unsigned long) (fraction * 256.0 * 256.0 * 256.0 * 256.0); + secs = secs_to_baseline + tout->tv_sec; + fill_int(buffer, secs); + fill_int(buffer + 4, long_fraction); +} + +static void +get_time_for_rtp(struct play_state *state, struct timeval *tout) +{ + unsigned long rtp_diff = state->rtptime - state->start_rtptime; + unsigned long add_secs = rtp_diff / 44100; + unsigned long add_usecs = (((rtp_diff % 44100) * 10000) / 441) % 1000000; + tout->tv_sec = state->start_time.tv_sec + add_secs; + tout->tv_usec = state->start_time.tv_usec + add_usecs; + if (tout->tv_usec >= 1000000) { + tout->tv_sec++; + tout->tv_usec = tout->tv_usec % 1000000; + } +} + +/* + * Send a control command + */ +static bool +send_control_command(struct control_data *ctrl, struct raop_data *rd, + struct play_state *state, + GError **error_r) +{ + unsigned char buf[20]; + int diff; + int num_bytes; + struct timeval ctrl_time; + + diff = 88200; + if (rd->started) { + buf[0] = 0x80; + diff += NUMSAMPLES; + } else { + buf[0] = 0x90; + state->playing = true; + state->start_rtptime = state->rtptime; + } + buf[1] = 0xd4; + buf[2] = 0x00; + buf[3] = 0x07; + fill_int(buf + 4, state->rtptime - diff); + get_time_for_rtp(state, &ctrl_time); + fill_time_buffer_with_time(buf + 8, &ctrl_time); + fill_int(buf + 16, state->rtptime); + + num_bytes = sendto(ctrl->fd, (const void *)buf, sizeof(buf), 0, + (struct sockaddr *)&rd->ctrl_addr, + sizeof(rd->ctrl_addr)); + if (num_bytes < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "Unable to send control command: %s", + g_strerror(errno)); + return false; + } + + return true; +} + +static int rsa_encrypt(const unsigned char *text, int len, unsigned char *res) +{ + RSA *rsa; + gsize usize; + unsigned char *modulus; + unsigned char *exponent; + int size; + + char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + char e[] = "AQAB"; + + rsa = RSA_new(); + + modulus = g_base64_decode(n, &usize); + rsa->n = BN_bin2bn(modulus, usize, NULL); + exponent = g_base64_decode(e, &usize); + rsa->e = BN_bin2bn(exponent, usize, NULL); + g_free(modulus); + g_free(exponent); + size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); + + RSA_free(rsa); + return size; +} + +static int +raop_encrypt(struct encrypt_data *encryp, unsigned char *data, int size) +{ + // any bytes that fall beyond the last 16 byte page should be sent + // in the clear + int alt_size = size - (size % 16); + + memcpy(encryp->nv, encryp->iv, 16); + + AES_cbc_encrypt(data, data, alt_size, &encryp->ctx, encryp->nv, 1); + + return size; +} + +/* write bits filed data, *bpos=0 for msb, *bpos=7 for lsb + d=data, blen=length of bits field +*/ +static inline void +bits_write(unsigned char **p, unsigned char d, int blen, int *bpos) +{ + int lb, rb, bd; + lb =7 - *bpos; + rb = lb - blen + 1; + if (rb >= 0) { + bd = d << rb; + if (*bpos) + **p |= bd; + else + **p = bd; + *bpos += blen; + } else { + bd = d >> -rb; + **p |= bd; + *p += 1; + **p = d << (8 + rb); + *bpos = -rb; + } +} + +static bool +wrap_pcm(unsigned char *buffer, int bsize, int *size, unsigned char *inData, int inSize) +{ + unsigned char one[4]; + int count = 0; + int bpos = 0; + unsigned char *bp = buffer; + int i, nodata = 0; + bits_write(&bp, 1, 3, &bpos); // channel=1, stereo + bits_write(&bp, 0, 4, &bpos); // unknown + bits_write(&bp, 0, 8, &bpos); // unknown + bits_write(&bp, 0, 4, &bpos); // unknown + if (bsize != 4096 && false) + bits_write(&bp, 1, 1, &bpos); // hassize + else + bits_write(&bp, 0, 1, &bpos); // hassize + bits_write(&bp, 0, 2, &bpos); // unused + bits_write(&bp, 1, 1, &bpos); // is-not-compressed + if (bsize != 4096 && false) { + // size of data, integer, big endian + bits_write(&bp, (bsize >> 24) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 16) & 0xff, 8, &bpos); + bits_write(&bp, (bsize >> 8) & 0xff, 8, &bpos); + bits_write(&bp, bsize&0xff, 8, &bpos); + } + while (1) { + if (inSize <= count * 4) nodata = 1; + if (nodata) break; + one[0] = inData[count * 4]; + one[1] = inData[count * 4 + 1]; + one[2] = inData[count * 4 + 2]; + one[3] = inData[count * 4 + 3]; + +#if BYTE_ORDER == BIG_ENDIAN + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); +#else + bits_write(&bp, one[1], 8, &bpos); + bits_write(&bp, one[0], 8, &bpos); + bits_write(&bp, one[3], 8, &bpos); + bits_write(&bp, one[2], 8, &bpos); +#endif + + if (++count == bsize) break; + } + if (!count) return false; // when no data at all, it should stop playing + /* when readable size is less than bsize, fill 0 at the bottom */ + for(i = 0; i < (bsize - count) * 4; i++) { + bits_write(&bp, 0, 8, &bpos); + } + *size = (int)(bp - buffer); + if (bpos) *size += 1; + return true; +} + +static bool +raopcl_connect(struct raop_data *rd, GError **error_r) +{ + unsigned char buf[4 + 8 + 16]; + char sid[16]; + char sci[24]; + char act_r[17]; + char *sac=NULL, *key = NULL, *iv = NULL; + char sdp[1024]; + int rval = false; + unsigned char rsakey[512]; + struct timeval current_time; + unsigned int sessionNum; + int i; + + + gettimeofday(¤t_time,NULL); + sessionNum = current_time.tv_sec + 2082844804; + + RAND_bytes(buf, sizeof(buf)); + sprintf(act_r, "%u", (unsigned int) g_random_int()); + sprintf(sid, "%u", sessionNum); + sprintf(sci, "%08x%08x", *((int *)(buf + 4)), *((int *)(buf + 8))); + sac = g_base64_encode(buf + 12, 16); + rd->rtspcl = rtspcl_open(); + rtspcl_set_useragent(rd->rtspcl, "iTunes/8.1.1 (Macintosh; U; PPC Mac OS X 10.4)"); + rtspcl_add_exthds(rd->rtspcl, "Client-Instance", sci); + rtspcl_add_exthds(rd->rtspcl, "DACP-ID", sci); + rtspcl_add_exthds(rd->rtspcl, "Active-Remote", act_r); + if (!rtspcl_connect(rd->rtspcl, rd->addr, rd->rtsp_port, sid, error_r)) + goto erexit; + + i = rsa_encrypt(raop_session->encrypt.key, 16, rsakey); + key = g_base64_encode(rsakey, i); + remove_char_from_string(key, '='); + iv = g_base64_encode(raop_session->encrypt.iv, 16); + remove_char_from_string(iv, '='); + sprintf(sdp, + "v=0\r\n" + "o=iTunes %s 0 IN IP4 %s\r\n" + "s=iTunes\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 96\r\n" + "a=rtpmap:96 AppleLossless\r\n" + "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=rsaaeskey:%s\r\n" + "a=aesiv:%s\r\n", + sid, rtspcl_local_ip(rd->rtspcl), rd->addr, NUMSAMPLES, key, iv); + remove_char_from_string(sac, '='); + // rtspcl_add_exthds(rd->rtspcl, "Apple-Challenge", sac); + if (!rtspcl_announce_sdp(rd->rtspcl, sdp, error_r)) + goto erexit; + // if (!rtspcl_mark_del_exthds(rd->rtspcl, "Apple-Challenge")) goto erexit; + if (!rtspcl_setup(rd->rtspcl, NULL, + raop_session->ctrl.port, raop_session->ntp.port, + error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->control_port, + &rd->ctrl_addr, error_r)) + goto erexit; + + if (!get_sockaddr_by_host(rd->addr, rd->rtspcl->server_port, + &rd->data_addr, error_r)) + goto erexit; + + if (!rtspcl_record(rd->rtspcl, + raop_session->play_state.seq_num, + raop_session->play_state.rtptime, + error_r)) + goto erexit; + + rval = true; + + erexit: + g_free(sac); + g_free(key); + g_free(iv); + return rval; +} + +static int +difference (struct timeval *t1, struct timeval *t2) +{ + int ret = 150000000; + if (t1->tv_sec - t2->tv_sec < 150) { + ret = (t1->tv_sec - t2->tv_sec) * 1000000; + ret += t1->tv_usec - t2->tv_usec; + } + return ret; +} + +/* + * With airtunes version 2, we don't get responses back when we send audio + * data. The only requests we get from the airtunes device are timing + * requests. + */ +static bool +send_audio_data(int fd, GError **error_r) +{ + int i = 0; + struct timeval current_time, rtp_time; + struct raop_data *rd = raop_session->raop_list; + + get_time_for_rtp(&raop_session->play_state, &rtp_time); + gettimeofday(¤t_time, NULL); + int diff = difference(&rtp_time, ¤t_time); + if (diff > 0) + g_usleep(diff); + + gettimeofday(&raop_session->play_state.last_send, NULL); + while (rd) { + if (rd->started) { + raop_session->data[1] = 0x60; + } else { + rd->started = true; + raop_session->data[1] = 0xe0; + } + i = sendto(fd, (const void *)(raop_session->data + raop_session->wblk_wsize), + raop_session->wblk_remsize, 0, (struct sockaddr *) &rd->data_addr, + sizeof(rd->data_addr)); + if (i < 0) { + g_set_error(error_r, raop_output_quark(), errno, + "write error: %s", + g_strerror(errno)); + return false; + } + if (i == 0) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "disconnected on the other end"); + return false; + } + rd = rd->next; + } + raop_session->wblk_wsize += i; + raop_session->wblk_remsize -= i; + + return true; +} + +static void * +raop_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + G_GNUC_UNUSED const struct config_param *param, + GError **error_r) +{ + const char *host = config_get_block_string(param, "host", NULL); + if (host == NULL) { + g_set_error_literal(error_r, raop_output_quark(), 0, + "missing option 'host'"); + return NULL; + } + + struct raop_data *rd; + + rd = new_raop_data(error_r); + if (rd == NULL) + return NULL; + + rd->addr = host; + rd->rtsp_port = config_get_block_unsigned(param, "port", 5000); + rd->volume = config_get_block_unsigned(param, "volume", 75); + return rd; +} + +static bool +raop_set_volume_local(struct raop_data *rd, int volume, GError **error_r) +{ + char vol_str[128]; + sprintf(vol_str, "volume: %d.000000\r\n", volume); + return rtspcl_set_parameter(rd->rtspcl, vol_str, error_r); +} + + +static void +raop_output_finish(void *data) +{ + struct raop_data *rd = data; + + if (rd->rtspcl) + rtspcl_close(rd->rtspcl); + + g_mutex_free(rd->control_mutex); + g_free(rd); +} + +#define RAOP_VOLUME_MIN -30 +#define RAOP_VOLUME_MAX 0 + +int +raop_get_volume(struct raop_data *rd) +{ + return rd->volume; +} + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r) +{ + int raop_volume; + bool rval; + + //set parameter volume + if (volume == 0) { + raop_volume = -144; + } else { + raop_volume = RAOP_VOLUME_MIN + + (RAOP_VOLUME_MAX - RAOP_VOLUME_MIN) * volume / 100; + } + g_mutex_lock(rd->control_mutex); + rval = raop_set_volume_local(rd, raop_volume, error_r); + if (rval) rd->volume = volume; + g_mutex_unlock(rd->control_mutex); + + return rval; +} + +static void +raop_output_cancel(void *data) +{ + //flush + struct key_data kd; + struct raop_data *rd = (struct raop_data *) data; + int flush_diff = 1; + + rd->started = 0; + if (rd->is_master) { + raop_session->play_state.playing = false; + } + if (rd->paused) { + return; + } + + g_mutex_lock(rd->control_mutex); + static char rtp_key[] = "RTP-Info"; + kd.key = rtp_key; + char buf[128]; + sprintf(buf, "seq=%d; rtptime=%d", raop_session->play_state.seq_num + flush_diff, raop_session->play_state.rtptime + NUMSAMPLES * flush_diff); + kd.data = buf; + kd.next = NULL; + exec_request(rd->rtspcl, "FLUSH", NULL, NULL, 1, + &kd, NULL, NULL); + g_mutex_unlock(rd->control_mutex); +} + +static bool +raop_output_pause(void *data) +{ + struct raop_data *rd = (struct raop_data *) data; + + rd->paused = true; + return true; +} + +/** + * Remove the output from the session's list. Caller must not lock + * the list_mutex. + */ +static void +raop_output_remove(struct raop_data *rd) +{ + struct raop_data *iter = raop_session->raop_list; + struct raop_data *prev = NULL; + + g_mutex_lock(raop_session->list_mutex); + while (iter) { + if (iter == rd) { + if (prev != NULL) { + prev->next = rd->next; + } else { + raop_session->raop_list = rd->next; + } + if (rd->is_master && raop_session->raop_list != NULL) { + raop_session->raop_list->is_master = true; + } + rd->next = NULL; + rd->is_master = false; + break; + } + prev = iter; + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + + if (raop_session->raop_list == NULL) { + raop_session_free(raop_session); + raop_session = NULL; + } +} + +static void +raop_output_close(void *data) +{ + //teardown + struct raop_data *rd = data; + + raop_output_remove(rd); + + g_mutex_lock(rd->control_mutex); + exec_request(rd->rtspcl, "TEARDOWN", NULL, NULL, 0, + NULL, NULL, NULL); + g_mutex_unlock(rd->control_mutex); + + rd->started = 0; +} + + +static bool +raop_output_open(void *data, struct audio_format *audio_format, GError **error_r) +{ + //setup, etc. + struct raop_data *rd = data; + + g_mutex_lock(raop_session->list_mutex); + if (raop_session->raop_list == NULL) { + // first raop, need to initialize session data + unsigned short myport = 0; + raop_session->raop_list = rd; + rd->is_master = true; + + raop_session->data_fd = open_udp_socket(NULL, &myport, + error_r); + if (raop_session->data_fd < 0) + return false; + + if (!ntp_server_open(&raop_session->ntp, error_r)) + return false; + + raop_session->ctrl.fd = + open_udp_socket(NULL, &raop_session->ctrl.port, + error_r); + if (raop_session->ctrl.fd < 0) { + ntp_server_close(&raop_session->ntp); + raop_session->ctrl.fd = -1; + g_mutex_unlock(raop_session->list_mutex); + return false; + } + } + g_mutex_unlock(raop_session->list_mutex); + + audio_format->format = SAMPLE_FORMAT_S16; + if (!raopcl_connect(rd, error_r)) { + raop_output_remove(rd); + return false; + } + + if (!raop_set_volume(rd, rd->volume, error_r)) { + raop_output_remove(rd); + return false; + } + + g_mutex_lock(raop_session->list_mutex); + if (!rd->is_master) { + rd->next = raop_session->raop_list; + raop_session->raop_list = rd; + } + g_mutex_unlock(raop_session->list_mutex); + return true; +} + +static size_t +raop_output_play(void *data, const void *chunk, size_t size, + GError **error_r) +{ + //raopcl_send_sample + struct raop_data *rd = data; + size_t rval = 0, orig_size = size; + + rd->paused = false; + if (!rd->is_master) { + // only process data for the master raop + return size; + } + + g_mutex_lock(raop_session->data_mutex); + + if (raop_session->play_state.rtptime <= NUMSAMPLES) { + // looped over, need new reference point to calculate correct times + raop_session->play_state.playing = false; + } + + while (raop_session->bufferSize + size >= RAOP_BUFFER_SIZE) { + // ntp header + unsigned char header[] = { + 0x80, 0x60, 0x00, 0x00, + // rtptime + 0x00, 0x00, 0x00, 0x00, + // device + 0x7e, 0xad, 0xd2, 0xd3, + }; + + + int count = 0; + int copyBytes = RAOP_BUFFER_SIZE - raop_session->bufferSize; + + if (!raop_session->play_state.playing || + raop_session->play_state.seq_num % (44100 / NUMSAMPLES + 1) == 0) { + struct raop_data *iter; + g_mutex_lock(raop_session->list_mutex); + if (!raop_session->play_state.playing) { + gettimeofday(&raop_session->play_state.start_time,NULL); + } + iter = raop_session->raop_list; + while (iter) { + if (!send_control_command(&raop_session->ctrl, iter, + &raop_session->play_state, + error_r)) + goto erexit; + + iter = iter->next; + } + g_mutex_unlock(raop_session->list_mutex); + } + + fill_int(header + 8, raop_session->play_state.sync_src); + + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, copyBytes); + raop_session->bufferSize += copyBytes; + chunk = ((const char *)chunk) + copyBytes; + size -= copyBytes; + + if (!wrap_pcm(raop_session->data + RAOP_HEADER_SIZE, NUMSAMPLES, &count, raop_session->buffer, RAOP_BUFFER_SIZE)) { + g_warning("unable to encode %d bytes properly\n", RAOP_BUFFER_SIZE); + } + + memcpy(raop_session->data, header, RAOP_HEADER_SIZE); + raop_session->data[2] = raop_session->play_state.seq_num >> 8; + raop_session->data[3] = raop_session->play_state.seq_num & 0xff; + raop_session->play_state.seq_num ++; + + fill_int(raop_session->data + 4, raop_session->play_state.rtptime); + raop_session->play_state.rtptime += NUMSAMPLES; + + raop_encrypt(&raop_session->encrypt, raop_session->data + RAOP_HEADER_SIZE, count); + raop_session->wblk_remsize = count + RAOP_HEADER_SIZE; + raop_session->wblk_wsize = 0; + + if (!send_audio_data(raop_session->data_fd, error_r)) + goto erexit; + + raop_session->bufferSize = 0; + } + if (size > 0) { + memcpy(raop_session->buffer + raop_session->bufferSize, chunk, size); + raop_session->bufferSize += size; + } + rval = orig_size; + erexit: + g_mutex_unlock(raop_session->data_mutex); + return rval; +} + +const struct audio_output_plugin raopPlugin = { + .name = "raop", + .init = raop_output_init, + .finish = raop_output_finish, + .open = raop_output_open, + .play = raop_output_play, + .cancel = raop_output_cancel, + .pause = raop_output_pause, + .close = raop_output_close, + .mixer_plugin = &raop_mixer_plugin, +}; diff --git a/src/output/raop_output_plugin.h b/src/output/raop_output_plugin.h new file mode 100644 index 000000000..210237179 --- /dev/null +++ b/src/output/raop_output_plugin.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_OUTPUT_RAOP_PLUGIN_H +#define MPD_OUTPUT_RAOP_PLUGIN_H + +#include "ntp_server.h" +#include "rtsp_client.h" + +#include <glib.h> +#include <stdbool.h> +#include <sys/time.h> +#include <openssl/aes.h> + +struct play_state { + bool playing; + unsigned short seq_num; + unsigned int rtptime; + unsigned int sync_src; + unsigned int start_rtptime; + struct timeval start_time; + struct timeval last_send; +}; + +/*********************************************************************/ + +enum pause_state { + NO_PAUSE = 0, + OP_PAUSE, + NODATA_PAUSE, +}; + +#define MINIMUM_SAMPLE_SIZE 32 + +#define RAOP_FD_READ (1<<0) +#define RAOP_FD_WRITE (1<<1) + +/*********************************************************************/ + +struct encrypt_data { + AES_KEY ctx; + unsigned char iv[16]; // initialization vector for aes-cbc + unsigned char nv[16]; // next vector for aes-cbc + unsigned char key[16]; // key for aes-cbc +}; + +/*********************************************************************/ + +struct raop_data { + struct rtspcl_data *rtspcl; + const char *addr; // target host address + short rtsp_port; + struct sockaddr_in ctrl_addr; + struct sockaddr_in data_addr; + + bool is_master; + struct raop_data *next; + + unsigned volume; + + GMutex *control_mutex; + + bool started; + bool paused; +}; + +/*********************************************************************/ + +struct control_data { + unsigned short port; + int fd; +}; + +/*********************************************************************/ + +#define NUMSAMPLES 352 +#define RAOP_BUFFER_SIZE NUMSAMPLES * 4 +#define RAOP_HEADER_SIZE 12 +#define ALAC_MAX_HEADER_SIZE 8 +#define RAOP_MAX_PACKET_SIZE RAOP_BUFFER_SIZE + RAOP_HEADER_SIZE + ALAC_MAX_HEADER_SIZE + +// session +struct raop_session_data { + struct raop_data *raop_list; + struct ntp_server ntp; + struct control_data ctrl; + struct encrypt_data encrypt; + struct play_state play_state; + + int data_fd; + + unsigned char buffer[RAOP_BUFFER_SIZE]; + size_t bufferSize; + + unsigned char data[RAOP_MAX_PACKET_SIZE]; + int wblk_wsize; + int wblk_remsize; + + GMutex *data_mutex; + GMutex *list_mutex; +}; + +//static struct raop_session_data *raop_session; + +/*********************************************************************/ + +bool +raop_set_volume(struct raop_data *rd, unsigned volume, GError **error_r); + +int +raop_get_volume(struct raop_data *rd); + +#endif diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c index 10d64106c..070ef4b08 100644 --- a/src/output/recorder_output_plugin.c +++ b/src/output/recorder_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify 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 <roaraudio.h> +#include <glib.h> + +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..f9d44a3d8 --- /dev/null +++ b/src/output/roar_plugin.c @@ -0,0 +1,324 @@ +/* + * 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 <glib.h> +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + + +#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/shout_plugin.c b/src/output/shout_plugin.c index 35efd9fc7..7a4d70e70 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -277,6 +277,13 @@ my_shout_init_driver(const struct audio_format *audio_format, goto failure; } + value = config_get_block_string(param, "url", NULL); + if (value != NULL && shout_set_url(sd->shout_conn, value)) { + g_set_error(error, shout_output_quark(), 0, + "%s", shout_get_error(sd->shout_conn)); + goto failure; + } + { char temp[11]; memset(temp, 0, sizeof(temp)); diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 22c583805..c0d38defb 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/winmm_output_plugin.c b/src/output/winmm_output_plugin.c index b9687874d..e93a9f6c5 100644 --- a/src/output/winmm_output_plugin.c +++ b/src/output/winmm_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h index 39507275a..10f0bb6b1 100644 --- a/src/output/winmm_output_plugin.h +++ b/src/output/winmm_output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_all.c b/src/output_all.c index 4e0b2eb22..7f4694a8b 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,6 +27,7 @@ #include "buffer.h" #include "player_control.h" #include "mpd_error.h" +#include "notify.h" #ifndef NDEBUG #include "chunk.h" @@ -100,7 +101,7 @@ audio_output_config_count(void) } void -audio_output_all_init(void) +audio_output_all_init(struct player_control *pc) { const struct config_param *param = NULL; unsigned int i; @@ -121,7 +122,7 @@ audio_output_all_init(void) /* only allow param to be NULL if there just one audioOutput */ assert(param || (num_audio_outputs == 1)); - if (!audio_output_init(output, param, &error)) { + if (!audio_output_init(output, param, pc, &error)) { if (param != NULL) MPD_ERROR("line %i: %s", param->line, error->message); @@ -460,17 +461,17 @@ audio_output_all_check(void) } bool -audio_output_all_wait(unsigned threshold) +audio_output_all_wait(struct player_control *pc, unsigned threshold) { - player_lock(); + player_lock(pc); if (audio_output_all_check() < threshold) { - player_unlock(); + player_unlock(pc); return true; } - player_wait(); - player_unlock(); + player_wait(pc); + player_unlock(pc); return audio_output_all_check() < threshold; } diff --git a/src/output_all.h b/src/output_all.h index a579bf5f1..4eeb94f13 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -32,13 +32,14 @@ struct audio_format; struct music_buffer; struct music_chunk; +struct player_control; /** * Global initialization: load audio outputs from the configuration * file and initialize them. */ void -audio_output_all_init(void); +audio_output_all_init(struct player_control *pc); /** * Global finalization: free memory occupied by audio outputs. All @@ -127,7 +128,7 @@ audio_output_all_check(void); * @return true if there are less than #threshold chunks in the pipe */ bool -audio_output_all_wait(unsigned threshold); +audio_output_all_wait(struct player_control *pc, unsigned threshold); /** * Puts all audio outputs into pause mode. Most implementations will diff --git a/src/output_api.h b/src/output_api.h index 8e002dd48..302a0cdc9 100644 --- a/src/output_api.h +++ b/src/output_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_command.c b/src/output_command.c index 825884e8e..3988f350a 100644 --- a/src/output_command.c +++ b/src/output_command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -50,7 +50,7 @@ audio_output_enable_index(unsigned idx) ao->enabled = true; idle_add(IDLE_OUTPUT); - pc_update_audio(); + pc_update_audio(ao->player_control); ++audio_output_state_version; @@ -79,7 +79,7 @@ audio_output_disable_index(unsigned idx) idle_add(IDLE_MIXER); } - pc_update_audio(); + pc_update_audio(ao->player_control); ++audio_output_state_version; diff --git a/src/output_command.h b/src/output_command.h index fab015c3f..eda30acc8 100644 --- a/src/output_command.h +++ b/src/output_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_control.c b/src/output_control.c index f8c5cd873..7ddcb8b19 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -38,6 +38,11 @@ enum { struct notify audio_output_client_notify; +/** + * Waits for command completion. + * + * @param ao the #audio_output instance; must be locked + */ static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { @@ -47,20 +52,43 @@ static void ao_command_wait(struct audio_output *ao) } } -static void ao_command(struct audio_output *ao, enum audio_output_command cmd) +/** + * Sends a command to the #audio_output object, but does not wait for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void ao_command_async(struct audio_output *ao, + enum audio_output_command cmd) { assert(ao->command == AO_COMMAND_NONE); ao->command = cmd; g_cond_signal(ao->cond); +} + +/** + * Sends a command to the #audio_output object and waits for + * completion. + * + * @param ao the #audio_output instance; must be locked + */ +static void +ao_command(struct audio_output *ao, enum audio_output_command cmd) +{ + ao_command_async(ao, cmd); ao_command_wait(ao); } -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) +/** + * Lock the #audio_output object and execute the command + * synchronously. + */ +static void +ao_lock_command(struct audio_output *ao, enum audio_output_command cmd) { - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - g_cond_signal(ao->cond); + g_mutex_lock(ao->mutex); + ao_command(ao, cmd); + g_mutex_unlock(ao->mutex); } void @@ -78,9 +106,7 @@ audio_output_enable(struct audio_output *ao) audio_output_thread_start(ao); } - g_mutex_lock(ao->mutex); - ao_command(ao, AO_COMMAND_ENABLE); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_ENABLE); } void @@ -97,9 +123,7 @@ audio_output_disable(struct audio_output *ao) return; } - g_mutex_lock(ao->mutex); - ao_command(ao, AO_COMMAND_DISABLE); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_DISABLE); } static void @@ -305,28 +329,11 @@ void audio_output_finish(struct audio_output *ao) assert(ao->fail_timer == NULL); if (ao->thread != NULL) { - g_mutex_lock(ao->mutex); assert(ao->allow_play); - ao_command(ao, AO_COMMAND_KILL); - g_mutex_unlock(ao->mutex); + ao_lock_command(ao, AO_COMMAND_KILL); g_thread_join(ao->thread); + ao->thread = NULL; } - if (ao->mixer != NULL) - mixer_free(ao->mixer); - - ao_plugin_finish(ao->plugin, ao->data); - - g_cond_free(ao->cond); - g_mutex_free(ao->mutex); - - if (ao->replay_gain_filter != NULL) - filter_free(ao->replay_gain_filter); - - if (ao->other_replay_gain_filter != NULL) - filter_free(ao->other_replay_gain_filter); - - filter_free(ao->filter); - - pcm_buffer_deinit(&ao->cross_fade_buffer); + audio_output_destruct(ao); } diff --git a/src/output_control.h b/src/output_control.h index f0e317d6e..f58a113e6 100644 --- a/src/output_control.h +++ b/src/output_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ struct audio_output; struct audio_format; struct config_param; struct music_pipe; +struct player_control; static inline GQuark audio_output_quark(void) @@ -38,6 +39,7 @@ audio_output_quark(void) bool audio_output_init(struct audio_output *ao, const struct config_param *param, + struct player_control *pc, GError **error_r); /** diff --git a/src/output_finish.c b/src/output_finish.c new file mode 100644 index 000000000..ac7f7e0c2 --- /dev/null +++ b/src/output_finish.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2011 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_internal.h" +#include "output_plugin.h" +#include "mixer_control.h" +#include "filter_plugin.h" + +#include <assert.h> + +void +audio_output_destruct(struct audio_output *ao) +{ + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->thread == NULL); + + if (ao->mixer != NULL) + mixer_free(ao->mixer); + + ao_plugin_finish(ao->plugin, ao->data); + + g_cond_free(ao->cond); + g_mutex_free(ao->mutex); + + if (ao->replay_gain_filter != NULL) + filter_free(ao->replay_gain_filter); + + if (ao->other_replay_gain_filter != NULL) + filter_free(ao->other_replay_gain_filter); + + filter_free(ao->filter); + + pcm_buffer_deinit(&ao->cross_fade_buffer); +} diff --git a/src/output_init.c b/src/output_init.c index 96f87f512..c52dcc8cd 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -127,8 +127,12 @@ audio_output_load_mixer(void *ao, const struct config_param *param, bool audio_output_init(struct audio_output *ao, const struct config_param *param, + struct player_control *pc, GError **error_r) { + assert(ao != NULL); + assert(pc != NULL); + const struct audio_output_plugin *plugin = NULL; GError *error = NULL; @@ -250,6 +254,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, ao->command = AO_COMMAND_NONE; ao->mutex = g_mutex_new(); ao->cond = g_cond_new(); + ao->player_control = pc; ao->data = ao_plugin_init(plugin, &ao->config_audio_format, diff --git a/src/output_internal.h b/src/output_internal.h index 7102ea5cd..eba3aed91 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -217,6 +217,12 @@ struct audio_output { GCond *cond; /** + * The player_control object which "owns" this output. This + * object is needed to signal command completion. + */ + struct player_control *player_control; + + /** * The #music_chunk which is currently being played. All * chunks before this one may be returned to the * #music_buffer, because they are not going to be used by @@ -248,4 +254,7 @@ audio_output_command_is_finished(const struct audio_output *ao) return ao->command == AO_COMMAND_NONE; } +void +audio_output_destruct(struct audio_output *ao); + #endif diff --git a/src/output_list.c b/src/output_list.c index 8238f581b..75d24d63b 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,10 +26,12 @@ 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; extern const struct audio_output_plugin osxPlugin; +extern const struct audio_output_plugin raopPlugin; extern const struct audio_output_plugin solaris_output_plugin; extern const struct audio_output_plugin pulse_output_plugin; extern const struct audio_output_plugin mvp_output_plugin; @@ -53,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 @@ -65,6 +70,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_OSX &osxPlugin, #endif +#ifdef ENABLE_RAOP_OUTPUT + &raopPlugin, +#endif #ifdef ENABLE_SOLARIS_OUTPUT &solaris_output_plugin, #endif diff --git a/src/output_list.h b/src/output_list.h index d72bc224b..3deb31c00 100644 --- a/src/output_list.h +++ b/src/output_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_plugin.h b/src/output_plugin.h index 36e17ed1b..72b519d13 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -52,7 +52,7 @@ struct audio_output_plugin { * none is configured * @param param the configuration section, or NULL if there is * no configuration - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors * @return NULL on error, or an opaque pointer to the plugin's * data @@ -72,7 +72,7 @@ struct audio_output_plugin { * fail: if an error occurs during that, it should be reported * by the open() method. * - * @param error_r location to store the error occuring, or + * @param error_r location to store the error occurring, or * NULL to ignore errors * @return true on success, false on error */ @@ -89,7 +89,7 @@ struct audio_output_plugin { * * @param audio_format the audio format in which data is going * to be delivered; may be modified by the plugin - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors */ bool (*open)(void *data, struct audio_format *audio_format, @@ -119,7 +119,7 @@ struct audio_output_plugin { /** * Play a chunk of audio data. * - * @param error location to store the error occuring, or NULL + * @param error location to store the error occurring, or NULL * to ignore errors * @return the number of bytes played, or 0 on error */ diff --git a/src/output_print.c b/src/output_print.c index 7a747ad2f..483648ca2 100644 --- a/src/output_print.c +++ b/src/output_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_print.h b/src/output_print.h index 5ad7e34c7..e02f4e9f5 100644 --- a/src/output_print.h +++ b/src/output_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_state.c b/src/output_state.c index e1187b951..7bcafb36b 100644 --- a/src/output_state.c +++ b/src/output_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_state.h b/src/output_state.h index 962ccd97a..320a3520a 100644 --- a/src/output_state.h +++ b/src/output_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_thread.c b/src/output_thread.c index bf56ca971..c36ba5f4f 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ #include "filter/convert_filter_plugin.h" #include "filter/replay_gain_filter_plugin.h" #include "mpd_error.h" +#include "notify.h" #include <glib.h> @@ -535,7 +536,7 @@ ao_play(struct audio_output *ao) ao->chunk_finished = true; g_mutex_unlock(ao->mutex); - player_lock_signal(); + player_lock_signal(ao->player_control); g_mutex_lock(ao->mutex); return true; diff --git a/src/output_thread.h b/src/output_thread.h index 1ee0856f2..5ad9a7527 100644 --- a/src/output_thread.h +++ b/src/output_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2010 The Music Player Daemon Project + * Copyright (C) 2003-2011 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify |